# Agent Memory Vault guards the Markdown and treats every backup path as single use

> A local-first memory system for Claude Code and Codex where Markdown stays the source of truth and SQLite, audit ledgers, and an optional Zvec index are all derived. The install path is the interesting part: two unambiguous modes, fail-closed combinations, and backups that refuse to be overwritten.

**mcncarl/agent-memory-vault** — Markdown-first shared memory vault for Claude Code and Codex with SQLite, Zvec, Git, closeout, and audit

- Repository: https://github.com/mcncarl/agent-memory-vault
- Stars: 314 · Forks: 44
- Language: Python
- License: MIT
- Published: 2026-09-18 · Updated: 2026-09-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/mcncarl-agent-memory-vault

## Markdown is the source of truth and the index is meant to be disposable

The design puts an ordinary Markdown directory at the center. Git history and rollback hang off it, SQLite provides metadata plus full-text search, and an optional EmbeddingGemma plus Zvec layer adds local semantic search. The stated rule is that every derived store can be rebuilt from Markdown.

That rule has one exception worth knowing about. The receipts trail lives outside the Markdown: `audit_decisions.sqlite` holds the decision ledger, and `closeout.jsonl` is a JSONL log under a logs directory. Those are not something you can regenerate by re-indexing a folder of notes, and the install path treats them that way: an upgrade only asks for an audit backup when the plan reports an existing audit ledger.

So the rebuild-from-Markdown promise covers retrieval, not accountability. If you care about the record of who approved a write, that record lives in the databases and the JSONL, not in your notes.

## Two install modes and a third combination that fails closed

The macOS and Linux entry point recognizes only two unambiguous situations. Both the private TOML config and the state database missing means a fresh install. Both present means an upgrade. Any half-present combination fails closed rather than guessing which one you meant.

The low-level primitives underneath carry the same warning in prose: the bootstrap script creates an independent Git repository only for a fresh private vault and commits the template baseline, and it should never be used as an upgrade step. The README says it outright. Running it against an existing vault is the mistake the split between fresh install and upgrade exists to prevent.

Two smaller switches follow the same logic. `migrate init` is only for a missing state database, and `--no-init-git` is for the case where you deliberately do not want Git at all. Any failed stage of an apply leaves the runtime non-ready, so a partial install is visible rather than silent.

## The plan runs on a different Python than the runtime it is planning for

The plan step always runs the migrator from the source checkout using an already installed Python 3.10 or newer, and `--python /absolute/python` selects which interpreter. That is deliberately not the installed runtime.

The consequence is stated plainly: the plan reads and reports a live version 1 TOML config and state database even when the installed version 1 Runtime does not contain `agent_memory_migrate.py`. So the thing that tells you what an upgrade will do may be reporting on a schema the installed runtime cannot even name.

The plan itself is read-only, which is what makes it safe to run against a vault you care about. The example sequence that pairs it with an apply is deliberately hookless, so the first thing you see is the disposition the migrator computed rather than any change to your agent runtime.

## Every upgrade path has to be new, and a macOS apply needs a second one

Upgrading means copying the plan's `disposition_template` into a private JSON file you have reviewed, then passing it as `--disposition-file` alongside new, non-existing `--config-backup` and `--state-backup` paths. When the plan reports an existing audit ledger, a new non-existing `--audit-backup` is required too.

Both SQLite backups are online snapshots and are never overwritten. That is the safety property, and it is also the operational annoyance: a fixed path from a previous run makes the next upgrade fail, so each upgrade invents three filenames. There is no rotation or cleanup path described for the backups themselves.

On macOS every apply additionally requires a separate, unused `--launchagent-backup-dir`, even in the example that passes `--no-host-hooks`. On Linux that flag does not exist. To install lifecycle hooks you replace `--no-host-hooks` with `--host codex` and or `--host claude` and pass a new `--hook-backup-dir` as well.

## INDEX.md is the one Markdown file an upgrade is allowed to write

An upgrade validates the existing roots plus `AGENTS.md` and `INDEX.md`, and it never installs template Markdown. Its single governed Markdown mutation is the initial machine-generated `INDEX.md` migration.

The preconditions for that write are strict, and they are the reason the design can promise derived stores are rebuildable. After state and audit readiness, every Markdown file in the vault must be clean and exactly bound to HEAD. Only then does it use the closeout capability and an isolated exact Git commit. Unrelated non-Markdown work is neither staged nor committed, so a dirty working tree with your own scratch edits does not get swept into the memory commit.

If any vault Markdown file has uncommitted changes, the migration does not proceed. That is the behavior you want before an agent touches your notes, and it is also why the workflow pushes read-only planning first and one explicit apply second.

## The env example sets one variable outside the namespace it declares

The header comment on the example environment file says Agent Memory Vault accepts the AGENT_MEMORY_ namespace only. Eleven lines down, the same file sets `MEMORY_ACTOR="codex"`, which is not in that namespace. Whether that is a leftover from an earlier layout or a deliberately exempt variable is not explained.

Everything around it is namespaced and specific: `AGENT_MEMORY_ROOT`, `AGENT_MEMORY_GIT_ROOT`, `AGENT_MEMORY_CONFIG_ROOT`, `AGENT_MEMORY_STATE_DB`, `AGENT_MEMORY_AUDIT_DB`, `AGENT_MEMORY_CLOSEOUT_LOG`, `AGENT_MEMORY_AUDIT_RUN_LOG`, `AGENT_MEMORY_AUDIT_REPORT`, `AGENT_MEMORY_INVARIANTS`, plus `AGENT_MEMORY_USER_ID`, `AGENT_MEMORY_AGENT_ID`, `AGENT_MEMORY_APP_ID`, and `AGENT_MEMORY_PYTHON`.

The default actor is worth noting for the multi-agent design. `AGENT_MEMORY_AGENT_ID="shared"` alongside `MEMORY_ACTOR="codex"` suggests one shared agent identity with a per-process actor, which is how three different hosts would avoid claiming each other's writes.

The example also notes that if the source checkout has neither `.env` nor a runtime TOML file, generated databases and logs stay in an ignored local `.agent-memory/` directory, so a second checkout will not quietly attach itself to an installed memory system you forgot about.

## An empty model revision next to a pinned dependency lock

The optional semantic layer is configured through eight more variables, and two of them are set in a way that undercuts the local-first claim at the edges:

```bash
export AGENT_MEMORY_EMBEDDING_MODEL="google/embeddinggemma-300m"
export AGENT_MEMORY_EMBEDDING_DIM="768"
export AGENT_MEMORY_EMBEDDING_DEVICE="cpu"
export AGENT_MEMORY_REQUIRE_LOCAL_MODEL="false"
export AGENT_MEMORY_MODEL_REVISION=""
```

`AGENT_MEMORY_REQUIRE_LOCAL_MODEL` is off by default, and `AGENT_MEMORY_MODEL_REVISION` is empty. So the default configuration does not insist on a local copy of the embedding model and does not pin which revision to fetch, even though the repository ships `requirements-vector.txt`, a `requirements-vector.lock`, a `benchmarks/` directory, and a `MODEL_MANIFEST` path for a verified local model.

The vector output directory is versioned in its own name, `zvec/memory_chunks_embeddinggemma_768`, which is a sensible way to keep a 300m model at 768 dimensions from colliding with a different one later.

An optional key for a later LLM-based closeout or reflection step is present in the example as a commented-out line, which is the only place an external model provider appears in the whole configuration surface. The vector stack, by contrast, is pointed at a local CPU.

## Session claims and content-bound intents are what stop two agents colliding

The feature list names six guarantees, and the mechanical ones are the reason to use this rather than a shared notes folder. Session-scoped claims prevent sessions from accidentally committing each other's changes. High-impact writes are validated with source checks, content-bound intents, approvals, and immutable receipts.

Content-bound intents matter because an approval given for one edit is not an approval for the next one. Binding the intent to the content means a write that differs from what was reviewed does not inherit the approval.

The components line up with that: `scripts/memoryctl` is the shared CLI both Claude Code and Codex use, `agent_memory_retrieve.py` does bounded and revalidated Markdown retrieval, `agent_memory_search.py` unifies keyword and optional vector search, and `agent_memory_index.py` owns the SQLite index. Retrieval is bounded and revalidated on the way in, not just on the way out.

## Conclusion

Agent Memory Vault is worth the setup cost if you actually run two agents against one body of notes, because the session claims, content-bound intents, and immutable receipts are the part a plain Markdown folder cannot do. Skip it if a single agent and a plain directory already work for you: the cost is Python 3.10+, a private config root, a separate launch agent backup directory on macOS, and a discipline of running plan before apply. Verify before you start that your Python can create the state database where you expect it, read docs/privacy.md to see what the audit ledger records about your own writes, and keep Markdown as the copy you can still read if the SQLite files are lost.

## FAQ

### What does Agent Memory Vault store, and where?

Markdown files in a private local vault are the source of truth. Git history, a SQLite metadata and full-text search index, and an optional EmbeddingGemma plus Zvec semantic index are all derived from it, so the vault is an ordinary directory that works with any editor and with Obsidian if you want one.

### Why does an Agent Memory Vault upgrade refuse to reuse a backup path?

Both SQLite backups are online snapshots and are never overwritten, so the config, state, and audit backup paths you pass must be new and non-existing. On macOS every apply also requires a separate unused launch agent backup directory.

### Can Agent Memory Vault upgrade an existing vault that is already installed?

Use `install-posix.py`. The lower-level `bootstrap.py` creates the Git repository and commits the template baseline for a fresh private vault only and should never be run against an existing vault. Any half-present combination of config and state database fails closed.

### Does Agent Memory Vault share memory safely between Claude Code and Codex?

It is built for that: one vault, one Git history, and one retrieval index shared by both, with session-scoped claims so sessions do not accidentally commit each other's changes and immutable receipts for high-impact writes. A third host, Ailu, appears alongside them in the design diagram.

### What does the Agent Memory Vault install require?

Python 3.10 or newer plus Git. Windows 10 and 11 use the PowerShell installer, which creates a private virtual environment, installs a verifiable runtime, initializes the vault and indexes, and runs the built-in checks.

## Sources

- [Issues](https://github.com/mcncarl/agent-memory-vault/issues)
- [License: MIT](https://github.com/mcncarl/agent-memory-vault/blob/main/LICENSE)
- [mcncarl/agent-memory-vault on GitHub](https://github.com/mcncarl/agent-memory-vault)
- [README](https://github.com/mcncarl/agent-memory-vault/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mcncarl-agent-memory-vault
