Metadata-Version: 2.5
Name: agent-cabinet
Version: 0.1.1
Summary: Give your agent a filing cabinet.
License: Apache-2.0
License-File: LICENSE
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Requires-Dist: jsonschema>=4.20
Requires-Dist: mcp>=2.0.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest-cov>=5; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# agent-cabinet

<p>
  <a href="https://github.com/Intelligible/agent-cabinet/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/Intelligible/agent-cabinet/actions/workflows/ci.yml/badge.svg"></a>
  <a href="https://pypi.org/project/agent-cabinet/"><img alt="PyPI" src="https://img.shields.io/pypi/v/agent-cabinet.svg"></a>
  <a href="https://pypi.org/project/agent-cabinet/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/agent-cabinet.svg"></a>
  <a href="./LICENSE"><img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-blue.svg"></a>
</p>

**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 ordinary text
files: each memory is a Markdown file with a short YAML header. You can
read, edit, or delete each memory directly, or query it later.

<details>
<summary>How is this different from Obsidian?</summary>

Obsidian is a great tool, and you can even tell `agent-cabinet` to [write into an Obsidian vault](#obsidian-vault)
instead of its own directory.

There are two main differences.

First, who's writing. Obsidian is 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](https://github.com/Intelligible/openreasoningcomponents)
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 that applications build on, not an application in itself.

</details>

## 60 second install

From your project's git repo, run:
```bash
pip install agent-cabinet
agent-cabinet init
```

`init` creates a `.cabinet` directory at your project's root and tells
your agent it exists.

## Usage

```bash
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.

<details>
<summary>See what's in your agent's cabinet</summary>

To see what's stored, you have two options:

- Open the `.cabinet` directory and read the files directly. Each entry
  is one `.md` file with YAML frontmatter. There's no export step, no
  hidden format, so you can just read it.
- Query it instead: `agent-cabinet` has built-in ranked search. See
  `agent-cabinet recall "<query>"` above.

</details>

<details>
<summary>Where `agent-cabinet` stores things</summary>

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:
- `.cabinet` is a directory built at `<project root>/.cabinet`.
- `AGENTS.md`/`CLAUDE.md`, with a short block added telling your agent
  `agent-cabinet` is available. `AGENTS.md` is created if it doesn't
  exist yet. `CLAUDE.md` is 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](#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`.

</details>

<details>
<summary>How a note becomes a fact</summary>

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](https://github.com/Intelligible/openreasoningcomponents),
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.)

Here's the actual file `remember("The DB is Postgres, not MySQL.")`
writes:

```yaml
---
id: example-project/the_db_is_postgres_not_mysql
type: domain_knowledge
scope: { source: example-project }
---
The DB is Postgres, not MySQL.
```

And this promotes it in place, once something verifies the claim:

```python
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)
```

Which overwrites the file in place to be:

```yaml
---
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.
```

</details>

<details>
<summary>Prefer MCP instead?</summary>

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 clients below can already call `agent-cabinet remember`/`recall`
directly as shell commands once you run `agent-cabinet init`).

**Claude Code**

```bash
claude mcp add agent-cabinet -- agent-cabinet mcp
```

**Cursor**

Add to `.cursor/mcp.json`:

```json
{ "mcpServers": { "agent-cabinet": { "command": "agent-cabinet", "args": ["mcp"] } } }
```

**Codex CLI**

```bash
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`.

</details>

<details>
<summary>Python library</summary>

The same `remember`/`recall` calls, direct from Python, the way you'd
embed `agent-cabinet` in your own service instead of shelling out:

```python
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.'
```

</details>

<details id="obsidian-vault">
<summary>Writing into an Obsidian vault</summary>

`--root` takes any directory, so pointing it at a folder inside a real
vault works as-is:

```bash
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.

</details>

## 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](https://arxiv.org/abs/2501.13956)
  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](https://mem0.ai/blog/ai-memory-confidence-score-what-it-is-and-how-it-works)
  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.md` and 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](https://github.com/zilliztech/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](https://github.com/agentic-box/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 `relations` and `metadata` instead.

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

## License

Apache-2.0.
