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

documentum: documents and code woven into one index

What it does

Three engines, one index, one process

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.

01Hybrid document search

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.

02Live inventories

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.

03Code graph

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.

04Office & PDF, natively

.docx, .pptx with speaker notes, .xlsx, .pdf are converted to markdown in-process and chunked like any other document. No Python, no external service.

05Secrets stay out

Every file is scanned before chunking and embedding. A file holding a key is quarantined — never indexed, never served — and listed by status.

06Pluggable stores

Local by default. Attach to Qdrant, federate other read-only stores or another documentum instance, weight and merge their results.

How it fits

Sources → chunks → lexical + dense → fused, capped, sourced

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 ┘

Documents

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.

Code

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.

Ranking

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.

Measured, not guessed

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.

ConfigurationMRRhit @1hit @5missed
Hybrid, embedding server (qwen3-embedding 0.6b), tuned prompt template0.795 [0.739–0.848]71 %92 %8 / 100
Same, generic prompt template0.75467 %88 %12 / 100
Built-in embedder, fully offline (128-d)≈ 0.51works 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

Designed for the model reading it

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 toolUse 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

Two commands

  1. Set up a workspace

    $ 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.

  2. Ask it yourself

    $ 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

One documentum.toml, nothing machine-specific in the code

Put 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:

A local or LAN server — no credentials

[embed]
backend = "ollama"
urls = ["http://gpu-box:11434", "http://localhost:11434"]   # tried in order
model = "qwen3-embedding:0.6b"

An API with a key — OpenAI or any compatible server

[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

Prebuilt binaries, checksum-verified

Releases are built by GitHub Actions for every platform and published with a SHA256SUMS file; the installer refuses an unverified binary.

Linux & macOS

$ curl -fsSL https://documentum.dev/install.sh | bash

Windows: the native build is on its way — use WSL meanwhile.

From source

$ 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.