agent-cabinet
Give your agent a filing cabinet.
Ask an agent what it remembers about you and there's usually no way to check the answer. The memory lives inside a vector index or a database somewhere, opaque even when the agent gets it right.
agent-cabinet keeps everything your agent learns as an ordinary text file instead: a Markdown file with a short YAML header. You can read, edit, or delete it directly, or query it later.
How is this different from Obsidian?
Obsidian is a great tool, and you can even tell agent-cabinet to write into an Obsidian vault instead of its own directory.
There are two main differences.
First, who's writing. Obsidian's built for a person writing by hand, so its frontmatter is freeform: that flexibility is the point. agent-cabinet's frontmatter is structured and schema-validated instead, following the ORC spec for shaping this kind of claim. Nothing else catches a malformed or missing field for an agent writing unsupervised.
Second, Obsidian is an application first: extending it means writing a plugin that runs inside Obsidian itself, not linking a library into your own service, so another program can't just call into it directly. agent-cabinet is meant to be a dependency instead: something another program imports and builds on.
60 second install
From your project's git repo, run:
pip install agent-cabinet
agent-cabinet init
init creates a .cabinet directory at your
project's root and tells your agent it exists.
Usage
agent-cabinet remember "The user prefers dark mode." --kind preference --basis stated
agent-cabinet recall "theme preference" --json
Either you or your agent can run these directly.
See what's in your agent's cabinet
To see what's stored, you have two options:
- Open the
.cabinetdirectory and read the files directly. Each entry is one.mdfile with YAML frontmatter. There's no export step, no hidden format, so this always works. - Query it instead: agent-cabinet has built-in ranked
search. See
agent-cabinet recall "<query>"above.
Where agent-cabinet stores things
On init, agent-cabinet finds the project root by
walking up from your current directory until it finds
.git (or the current directory, if you're not in
a git repo yet).
This project root will then hold two new entries:
.cabinetis a directory built at<project root>/.cabinet.AGENTS.md/CLAUDE.md, with a short block added telling your agent agent-cabinet is available.AGENTS.mdis created if it doesn't exist yet.CLAUDE.mdis only ever updated, never created: adding one unasked would presume you're using Claude Code when you might not be.
Want .cabinet somewhere else, like inside an
Obsidian vault? Override with
--root <path> or the
AGENT_CABINET_ROOT environment variable. Note
this only moves .cabinet:
AGENTS.md/CLAUDE.md always go to
the project root found above, regardless of
--root.
id: example-project/the_db_is_postgres_not_mysql
type: domain_knowledge
scope: { source: example-project }
---
The DB is Postgres, not MySQL.
id: example-project/the_db_is_postgres_not_mysql
type: domain_knowledge
scope: { source: example-project }
basis: stated
links: [db-migration-plan]
---
The DB is Postgres, not MySQL.
id: example-project/the_db_is_postgres_not_mysql
type: domain_knowledge
scope: { source: warehouse-connection-check }
evidence: { confirmed_by: "SELECT version() -> PostgreSQL 16.2" }
provenance: { derivation: check_db_dialect, derivation_version: a1b2c3 }
---
The DB is Postgres 16.2, confirmed by direct connection.
How a note becomes a fact
Promoting a note to a fact is just adding evidence to the
same entry: call file() on the same
id with evidence/provenance
filled in and overwrite=True, and it replaces
the note in place. Those fields are optional on
file() too, though. What actually makes
something a fact is only ever whether they're filled in, not
which function wrote the entry.
remember() writes down what you tell it, exactly
as told. file() is for a claim something else has
already checked, usually a pipeline that computed it and can
show its work: a dict with an id, a
type, a scope saying where it came
from, and the statement itself required, plus
optional evidence and provenance
fields for showing that work. (This shape is called
ORC,
short for OpenReasoningComponents, in case you want other
tools to read the same files; you don't need to know that name
to use any of this.) This is the call that turns the note
above into the verified version:
cabinet.file({
"id": "example-project/the_db_is_postgres_not_mysql",
"type": "domain_knowledge",
"scope": {"source": "warehouse-connection-check"},
"statement": "The DB is Postgres 16.2, confirmed by direct connection.",
"evidence": {"confirmed_by": "SELECT version() -> PostgreSQL 16.2"},
"provenance": {"derivation": "check_db_dialect", "derivation_version": "a1b2c3"},
}, overwrite=True)
Prefer MCP instead?
The server is one command: agent-cabinet mcp. As
with most local MCP servers, it defaults to stdio. Your
client runs the command itself and owns the process, so
there's nothing you start or keep running yourself.
Here's how to point common clients at it (all three below can
already call agent-cabinet remember/recall
directly as shell commands once you run
agent-cabinet init).
claude mcp add agent-cabinet -- agent-cabinet mcp
Add a project file at .cursor/mcp.json:
{
"mcpServers": {
"agent-cabinet": {
"command": "agent-cabinet",
"args": ["mcp"]
}
}
}
codex mcp add agent-cabinet -- agent-cabinet mcp
Multiple clients. Want one server shared by
more than one client, or a client on another machine?
agent-cabinet mcp --transport http --port 7879
binds a port instead. You run and keep that one alive
yourself; clients point at
http://localhost:7879/mcp.
Python library
The same remember/recall calls, direct
from Python, the way you'd embed agent-cabinet in your own
service instead of shelling out:
from agent_cabinet import Cabinet
cabinet = Cabinet("./.cabinet", namespace="example-project")
cabinet.remember("User said the DB is Postgres, not MySQL.", basis="stated")
cabinet.recall("database")[0].body
# 'User said the DB is Postgres, not MySQL.'
Writing into an Obsidian vault
--root takes any directory, so pointing it at a
folder inside a real vault works as-is:
agent-cabinet init --root ~/MyVault/agent-notes
Every entry is a standard frontmatter Markdown file, so Obsidian shows it as a real note: Properties panel, full-text search, all of it.
Two things worth knowing. relations/links
are plain strings (acme/other), not Obsidian's
[[wikilink]] syntax, so they won't wire into
Obsidian's own graph view or backlinks panel on their own. And
hand-editing a note through Obsidian's Properties UI is exactly
the kind of edit this is built to tolerate: a required field
going missing, or the id property getting retyped,
produces a clean error for that one entry rather than breaking
recall() for the rest of the cabinet.
Opinions behind the creation and design of agent-cabinet
agent-cabinet is meant to be a foundation other systems build on, not a finished memory system. Most memory needs (versioning, multi-agent sync, semantic search) are use-case-specific, so the substrate underneath stays simple and deterministic instead of guessing at which one any given system will need. Every opinion below follows from that.
- Memories have upstream sources and downstream implications. In agent-cabinet, dependencies are declared explicitly, not extracted from conversation the way Zep does it, so an edge is never wrong, but it also only exists if something actually declared it: no hallucinated dependency, but no free discovery of one nobody wrote down either. Automatic discovery is a layer you can add on top, not something decided here.
- Confidence and evidence should be filed alongside memories. In agent-cabinet, a memory just records whether a belief was stated, inferred, observed, or assumed, not a numeric confidence score the way Mem0 computes one, so you get a category you can read instead of a number you just have to trust. A computed score, if you want one, is a layer to add on top.
- Enforcement beats convention. In
agent-cabinet, dedup, schema validation, and ranked search are
guarantees the code provides, not rules written into
AGENTS.mdand hoped for every session, so a forgotten rule can't let duplicate notes pile up or a malformed file break search unexpectedly: the common guarantee every use case would otherwise have to build for itself. - The search index is disposable; the file is the only source of truth. In agent-cabinet, results are ranked with BM25 over a small, rebuildable index, not a vector database the way Memsearch pairs markdown with one, so there's no embedding API key or vector store to run, and deleting the index by accident costs nothing: it just rebuilds from the files. Semantic search is a layer to add on top, not a dependency baked in for everyone.
- Verification enriches an entry in place; it doesn't
migrate it. In agent-cabinet, the same entry just
gets more evidence added directly, not a new linked record the
way
Memora
creates when it supersedes a claim, so there's never a
question of which of two records is current: there's only
ever one. A use case that wants lineage can build it on
relationsandmetadatainstead.
Limits
- Single writer. One directory, used by a person or a small team sharing a git repo. Grants, SSO, and sharing across an org are a separate product's job.