local first · agent first · one binary
your docs, sorted and indexed for the agent that reads them
Your agent shouldn't dig through folders. documentum sorts and indexes your docs and code for it and serves back sourced passages. No lost context. Always-current docs.
$ curl -fsSL https://documentum.dev/install.sh | bash
Linux x86_64 & arm64 (static), macOS Intel & Apple silicon · installs to ~/.local/bin · no root · SHA-256 verified
What it does
The index is a single SQLite file next to your workspace. Everything runs on your machine; the embedding server is the only thing you may choose to make remote.
Dense vectors and a global BM25, fused by score with identifier-aware weighting. A port number, a container name or a domain is found as reliably as a paraphrased question.
Built-in collectors for Docker, systemd, listening ports, projects and scripts — or any command's markdown output. Tables are indexed row by row: “which port does X use?” gets the row, not a page.
A built-in extractor for Rust, TS/JS, Python, Go, Java, Kotlin and C#: symbols, callers and callees, bodies on demand, change impact of a diff, doc ↔ code links.
.docx, .pptx with speaker notes, .xlsx, .pdf are converted to markdown in-process and chunked like any other document. No Python, no external service.
Every file is scanned before chunking and embedding. A file holding a key is quarantined — never indexed, never served — and listed by status.
Local by default. Attach to Qdrant, federate other read-only stores or another documentum instance, weight and merge their results.
How it fits
files md/office/pdf ─► sections by heading ─┐ inventories (live) ─► one row = one chunk ├─► BM25 global (always local) ──┐ command output ─► markdown │ ├─► fusion α ─► caps ─► MCP / daemon / CLI code ─► symbols + call graph ┘ vectors ─► local | qdrant | peer ┘
Markdown, Word, PowerPoint (speaker notes included), Excel and PDF are converted to markdown in-process, then cut by H2/H3 headings. Each chunk keeps its breadcrumb (document > section), so the path and the title are searchable too. Tables and inventories are indexed one row per chunk. A file whose hash and mtime are unchanged is never re-read: sync is incremental.
A generic lexer, tuned per language by a small table, feeds a scope tree and per-language declaration recognizers. No full AST: just what navigation needs. Output: symbols with their qualified name and body, calls resolved through imports, doc comments attached to the symbol that follows, and doc ↔ code links.
Dense cosine plus a global BM25 (k1 1.5, b 0.75) scaled by the query: cosine + α · bm25 / bm25max. A query that looks like an identifier (a port, name.ext, @scope) gets α = 0.5; a natural-language question gets 0.2. Results are capped and always carry their source and line range.
A 100-question golden set, drawn from a real private workspace and kept out of the repository, gates every change to chunking, tokenisation, fusion, embeddings or stores. A mix of exact-identifier and natural-language questions, scored by MRR with a bootstrap confidence interval.
| Configuration | MRR | hit @1 | hit @5 | missed |
|---|---|---|---|---|
| Hybrid, embedding server (qwen3-embedding 0.6b), tuned prompt template | 0.795 [0.739–0.848] | 71 % | 92 % | 8 / 100 |
| Same, generic prompt template | 0.754 | 67 % | 88 % | 12 / 100 |
| Built-in embedder, fully offline (128-d) | ≈ 0.51 | works with no model server at all | ||
Hybrid score fusion beat reciprocal rank fusion on that set. A resident daemon answers a known question in about 20 ms.
Agent surface
Stable result schema, bounded output everywhere, one MCP server named documentum. The same operations exist on the CLI and on the daemon's HTTP API.
| MCP tool | Use it for |
|---|---|
inventory(kind, filter) | Exact rows — which port, which container, which domain — no vector search involved. |
search(query, tags, source_types, since, path_prefixes, max_chars) | Ranked passages with metadata and source lines. |
context(query, scope, max_tokens, page) | Docs and code together, paged within a token budget. |
map(prefix, depth) | One-line summary per document: read the map, open only what you need. |
annotate(path, summary, tags) | Leave what you learned for the next agent; visible immediately. |
status() | Freshness per source, store, quarantined and lost files. |
code_symbol(name) | Signature, doc comment and body of a symbol. |
code_callers · code_callees(name, depth) | Who calls it, what it calls — multi-hop. |
code_path(from, to) | Shortest call chain between two symbols. |
code_impact(diff | base, head) | Changed symbols → transitive callers → the docs that mention them. |
Quick start
$ cd my-workspace && documentum setup # indexes, then registers documentum in your agents
Claude Code, Codex, Cursor, VS Code, Zed, Windsurf… detected and configured. Restart them once: documentum appears among their tools (search, context, code_*). You never start the MCP server yourself — the editor runs documentum mcp in the background.
$ documentum search "how do I deploy the api?" $ documentum search "retention policy" --tag legal --type pdf --json
The first search starts a small background daemon that keeps the index warm: milliseconds per query afterwards. Offline embedder by default — for real semantic search point it at an embedding server, see below.
Configuration
documentum.toml, nothing machine-specific in the codePut a documentum.toml at the root of the workspace (or point $DOCUMENTUM_CONFIG at one). With no file at all, documentum works offline with its built-in embedder. For real semantic search, pick one of the two:
[embed] backend = "ollama" urls = ["http://gpu-box:11434", "http://localhost:11434"] # tried in order model = "qwen3-embedding:0.6b"
[embed] backend = "openai" # also LM Studio, vLLM, TEI, llama.cpp… urls = ["https://api.openai.com"] model = "text-embedding-3-small" api_key_env = "OPENAI_API_KEY" # the NAME of a variable, not the key
$ export OPENAI_API_KEY="sk-..." # in your shell profile or your secret manager
The key never goes in the file. documentum.toml can be committed and shared; the key stays in your environment. It is sent as Authorization: Bearer to the embedding server only. Same pattern for Qdrant and federated peers (api_key_env) and the daemon's HTTP API (http_token_env). Remote embedding is never implicit and always bounded (concurrency, batch). DOCUMENTUM_* variables override the file, --embeddings overrides both; every key is documented in documentum.example.toml.
Install
Releases are built by GitHub Actions for every platform and published with a SHA256SUMS file; the installer refuses an unverified binary.
$ curl -fsSL https://documentum.dev/install.sh | bash
Windows: the native build is on its way — use WSL meanwhile.
$ cargo install --git https://github.com/kerbart/documentum.git --locked
Pin a release with DOCUMENTUM_VERSION=v3.0.0, change the destination with DOCUMENTUM_BINDIR.