Long-term memory for AI agents: local-first PKM with provenance and hybrid search.
Tacitus is an MCP (Model Context Protocol) server that turns a folder of Markdown notes into an agent-native knowledge base. It provides long-term memory for AI agents, described as local-first, with provenance, and includes hybrid search.
๐ ๏ธ Key Features
MCP server for agent-native knowledge bases
Converts Markdown note folders into an AI-usable knowledge base
Long-term memory focus: local-first with provenance
Hybrid search
๐ Use Cases
Persisting agent knowledge over time
Using a local Markdown PKM (personal knowledge management) workflow
Semantic/hybrid retrieval over a notes collection
โก Developer Benefits
Integrates with MCP via Model Context Protocol
Supports local-first knowledge storage patterns
Note-based source material (Markdown folder input)
โ ๏ธ Limitations
Described only at a high level: no tool names, endpoints, or configuration details provided in the supplied data.
Long-term memory for your AI agents โ local-first, with provenance.
Tacitus is an MCP server that turns any folder
of Markdown notes into an agent-native knowledge base. It gives AI agents
(Claude Code, Claude Desktop, and any MCP client) three things they actually need:
Memory with provenance โ typed, queryable long-term memory. Every fact
carries its source and is returned within a token budget; contradictions are
surfaced, not silently resolved.
Retrieval that fits the context window โ search returns ranked snippets
(never whole notes) under a token budget; get_note discloses progressively
(outline โ frontmatter โ full); the wikilink graph is a queryable API.
Hybrid lexical + semantic search, with an optional neural embedder.
Safe write-back โ propose a changeset, preview the diff, commit
atomically, and revert by version. Read-only scope forbids mutations; every
write is audited.
Notes stay as plain .md files in your folder. No cloud, no lock-in.
An agent remembering a fact with its source, recalling it under a token budget, proposing a diff, committing it, and reverting it
Recorded from a real session โ the memory id, changeset id, version id and audit
line above are what the binary actually returned (how).
Prefer a single, zero-dependency binary? The Rust server ships prebuilt for
macOS, Linux, and Windows on every release.
bash
# macOS / Linux โ installs `tacitus-mcp` into your Cargo bin dir
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/ionasrobert/tacitus-mcp-server/releases/latest/download/tacitus-mcp-installer.sh | sh
powershell
# Windows (PowerShell)
irm https://github.com/ionasrobert/tacitus-mcp-server/releases/latest/download/tacitus-mcp-installer.ps1 | iex
Or grab a .tar.xz / .zip for your platform from the
latest release.
Then point any MCP client at the binary instead of npx:
bash
claude mcp add tacitus -- tacitus-mcp /path/to/your/vault
The native binary is the flagship server (25 tools; the npm server has the
core 16 โ see the table below). Both share the same on-disk formats, so a
vault works with either. Set TACITUS_SCOPE=read-only to run the native
server without write permissions.
* Native-Rust-server first (the npm server will catch up):
properties_query โ Bases-like structured queries over YAML frontmatter
(filters eq|ne|contains|exists|not_exists|gt|lt|gte|lte, sort, select,
token_budget). Templates โ Markdown files in .tacitus/templates/ whose
{{var}} placeholders form a schema; substitution happens before YAML
parsing so numeric vars stay typed, {{date}}/{{time}}/{{datetime}}
auto-fill, and creation is versioned + audited like any agent write.
Tasks โ every checklist line (- [ ]) as a typed entity (done, due from
due:YYYY-MM-DD or ๐ , #tags), queryable and toggleable; toggling takes
the task text as a concurrency guard so a stale caller gets a CONFLICT
instead of flipping the wrong task. rename_note retargets every wikilink
that resolves to the note (alias/heading kept) in one atomic changeset โ
a single revert undoes the whole rename; delete_note is versioned too.
Every tool validates input with a schema and returns structured, actionable
errors ({ code, reason, suggestion }) rather than stack traces.
For developers: plugins & integrations
In Tacitus, a plugin is an MCP client โ the tool contract above is the
public API, with permission scoping, versioning, and audit built in.
docs/PLUGINS.md โ integration guide: connect an agent,
write a plugin in Python/TypeScript, embed the Rust engine, plugin patterns
TypeScript SDK โ @dashiro/tacitus-sdk: every tool as
a typed method, {code, reason, suggestion} errors thrown as
TacitusToolError:
Sandboxed WASM plugins (experimental) โ crate tacitus-plugins runs
guest wasm under Wasmtime with manifest-declared permissions (tool allowlist
scope), fuel and memory limits, no WASI: tacitus.callistools/call.
The native binary embeds the runtime: tacitus-mcp plugin list|run for cron
agents and scripts.
See docs/PLUGINS.md ยง5
docs/MCP_API.md โ full reference for all 25 tools
(params, returns, error codes)
Neural search (opt-in): TACITUS_EMBEDDER=ollama uses a local Ollama daemon
for embeddings (TACITUS_OLLAMA_EMBED_MODEL, default nomic-embed-text;
needs an Ollama with embedding support). Vectors cached in .tacitus/vectors/;
falls back to the deterministic hashing embedder when unavailable.
docs/SYNC.md โ Sync (beta): E2E-encrypted CRDT sync
between devices (tacitus-mcp sync init; sync status shows how much of
the relay quota a vault uses)
docs/DATA_FORMAT.md โ the on-disk format
(.tacitus/ internals, stable ids, note conventions)
examples/ โ three complete plugins (Python read-only
analyzer, Node daily-note cron agent, sandboxed WASM guest), tested against
the binary
What the sync relay can see
Two devices exchanging a note through a relay: plain Markdown on the devices, only base64 ciphertext in the relay's log
Left: two devices, plain Markdown. Right: everything the relay stores for that
vault. Those blobs were read out of a real log.jsonl after the recording โ
no note title, no path, no plaintext. The vault code is the key and never
leaves your devices; lose it and the relay's copy is undecryptable forever.
Semantic search (optional neural embeddings)
search defaults to hybrid mode (lexical + a deterministic, offline
embedder that catches morphological variants). For synonym/paraphrase matching,
opt into a neural embedder:
bash
npm i @huggingface/transformers
TACITUS_EMBEDDER=transformers npx @dashiro/tacitus-mcp-server /path/to/vault
Vectors are cached under .tacitus/vectors/. Falls back to the deterministic
embedder if the optional dependency or model isn't available.
Polyglot monorepo. The reference server (shipped on npm) is TypeScript in
packages/mcp-server. A native Rust server in crates/ provides a
single-binary, zero-runtime-deps build (crates/tacitus-core engine +
crates/tacitus-mcp rmcp server) โ a superset of the TS server (25 vs 16
tools). Its stable_id matches the TS engine byte-for-byte, so memory ids are
identical across both engines.
bash
# TypeScript server
npm ci
npm test# vitest
npm run typecheck
npm run lint
npm run build # tsup โ packages/mcp-server/dist
npm run eval# retrieval quality report# Rust server (native, single binary)
cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --check
cargo run -p tacitus-mcp -- /path/to/vault # runs the MCP server on stdio
cargo build --release # โ target/release/tacitus-mcp
Cross-platform release binaries are built and published to GitHub Releases by
cargo-dist (dist-workspace.toml +
.github/workflows/release.yml) on every v* tag.