The checklist and `--jail=LIST` emitted only enabled toggles, so the user's own ai-jail config (e.g. a global `~/.ai-jail` enabling `docker`) could still mount what an unchecked row or an explicit list left out, and the summary line misreported it. - Interactive checklist: `marked_choices` passes every row the user saw, checked as `--X` and unchecked as `--no-X`. - `--jail=LIST` (including `none`): after the named entries, every visible checklist row the list did not name is forced off with `--no-X`. Rows that are not visible (absent credentials, CLI-only toggles) are never forced. - Bare `--jail` is unchanged: smart-default rows only, the rest left to the user's ai-jail config, because no selection was shown. - `JailToggleChoice::implied` marks rows forced off by omission, so the summary names the user's own `no-X` entries and says "everything else in the checklist off" for the rest. - Tests: an adversarial unit test (unchecked docker row and `--jail=none` yield `--no-docker` / `--no-*` for every visible row, with bare `--jail` as the control); parse, checklist, summary and end-to-end expectations updated, the latter platform-aware for the Linux-only rows. - Docs: design §5 semantics, cookbook, support matrix, CHANGELOG. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
18 KiB
ai-memory cookbook
A task-oriented cheat sheet: "I want to do X" → how. For the full reference see
ARCHITECTURE.md; for install see install.md
(including the macOS menu bar app);
for the tool-routing table see usage.md.
What ai-memory is, in one paragraph
ai-memory gives your AI coding agents long-term, cross-session memory. It runs
as one server; your agent talks to it over MCP (tools like memory_query,
memory_write_page) and over lifecycle hooks that automatically capture what you
do. Memory is a markdown wiki in git (the source of truth, hand-editable) plus
a derived SQLite index for search. Everything is scoped per project
(workspace, project), resolved from your working directory. It works with no
LLM at all (capture + full-text search + rule-based summaries); adding a provider
enables consolidation and auto-improvement.
Everyday tasks (through your agent, over MCP)
You mostly just talk to your agent; it calls the right tool. Common ones:
| You want | Say to your agent | Tool it uses |
|---|---|---|
| Recall prior work | "Have we discussed X?" / "what did we decide about Y?" | memory_query |
| Catch up after time away | "Where did we leave off?" / "catch me up" | handoff block / memory_explore |
| Remember a durable fact | "Remember that we always use pnpm here" | memory_write_page |
| Remember until a date | "Remember this until the migration ships" | memory_write_page + expires_at |
| A standing rule for every project | "Always: never force-push" | memory_write_page with scope: "global" |
| Read a specific saved page in full | "Read the _rules/deploy page" |
memory_read_page |
| Save context for the next session | "Wrap up — save context for next time" | memory_handoff_begin |
| See project stats | "How much memory do we have?" | memory_status / memory_briefing |
You rarely write pages by hand: lifecycle hooks capture prompts and tool calls automatically, and (with an LLM) consolidation compiles them into pages.
Recipe: keep a rule a project must follow
"Remember: before touching the payment code, read the PCI notes."
Your agent calls memory_write_page and it lands as a durable wiki page (routed
under _rules/ when it's a rule). Next session, memory_query surfaces it, and
if the project's .ai-memory.toml opts into the on-start brief, rules are
prepended to the agent's context automatically. To make it apply to all your
projects, ask for it as a global rule (scope: "global").
Recipe: have a project read a specific document before implementing
Two ways, depending on where the document lives:
- It's already a wiki page (you saved it, or imported it — below): tell the
agent "read the
norms/gdpr-retentionpage before implementing" — it callsmemory_read_pagewith that path and works from the full body. - It's an external file (a norm/spec on disk): save it as a page first
("remember this spec as
norms/gdpr-retention", paste or point at it), then reference it as in (1). Large references are best split into a page per document so retrieval can pull just the relevant one.
Recipe: import an existing knowledge base (e.g. OKF norms/laws/specs)
ai-memory's wiki is OKF (Obsidian-compatible markdown + frontmatter). To bring an existing body of documents in as project memory:
- A few documents: save each as a durable page via your agent
(
memory_write_page) or the CLI (ai-memory write-page), one page per document, under a stable prefix likenorms/…. Then any project can read a specific one before implementing (recipe above). - A whole OKF/Obsidian vault or an export from another tool: use the
companion importer (see
companion-crates.md) — it ingests OMC and external-conversation exports into the store.ai-memory export-okfis the inverse (export your wiki as an OKF bundle), useful to see the exact on-disk shape your documents should take. - The wiki lives at
<data_dir>/wiki/<workspace>/<project>/…and is plain markdown in git, so you can also drop files in and let the file watcher + reindex pick them up. Keep them under a clear prefix and commit.
Once the material is saved as pages in the right scope, any project can search
it (memory_query) and read a specific document in full (memory_read_page)
before implementing against it.
Recipe: control what gets kept, aged, or consolidated
By default nothing you have to think about: memory decays on a single gentle
curve, and an upgrade to 2.4 changes no scores and evicts nothing. When you do
want to tune aging, it is all opt-in and reversible — the original of anything
compacted or merged stays in git and the supersession chain, recoverable with
ai-memory restore-page.
- Keep something forever: pin it. A pinned page is exempt from the forget-sweep regardless of tier or age. Semantic and procedural pages never decay either — only working/episodic memory ages.
- Make a note expire on a deadline: ask your agent to remember it "until
" (an
expires_at); the forget-sweep deletes it when the time passes, no matter how often it was read. A TTL outranks pinning. - Tune how long each tier lasts: set per-tier half-lives in the project's
.ai-memory.toml[decay.half_life_days](e.g. keep episodic history longer, working-tier scratch shorter). Omit it for the default single curve. - Compact instead of evict, and de-duplicate:
[decay] compact_cold_episodickeeps a cold page's durable facts (paths, error codes, decisions) and drops the prose;[decay] dedup_cold_clusterscollapses near-duplicate cold pages into one survivor. Both are zero-LLM, off by default, and supersede rather than delete. - Keep used memory longer: nothing to configure — a page you open, search, or reach through a related-pages walk is reinforced automatically and resists decay.
- Surface likely contradictions: run
memory_lint(through your agent or the CLI); with embeddings configured it flags pairs of same-topic pages that look like they conflict, advisory only. Similarity reads shared vocabulary as much as disagreement, so a single-domain or single-language store yields mostly candidate pairs: read each finding as a pair to check, not as a defect. If that noise is too high, raisecontradiction_band_min(config.tomlorAI_MEMORY_CONTRADICTION_BAND_MIN, default0.4) — on such a store the band measures domain proximity more than conflict, so a higher floor trims same-domain-but-unrelated pairs. - Let an LLM consolidate on idle ("dream"): with a provider and an embedder
configured,
[dream] enabledturns on a background pass that rewrites clusters of cold notes into single coherent pages while you're idle and cancels the moment you return. It is off by default, never deletes a source, and is gated on an internal recall eval before it could ever become default behavior.
Recipe: two agents / two repos working together
- Continuity across agents in the same project (quit Claude Code, open Codex in the same repo): automatic. A handoff is captured at session end and the next session's on-start hook prepends it. Ask "where did we leave off?".
- Ask an agent in another project to do something without loading that
project's context here: cross-project messaging — "send project-b a request to
add the export endpoint" (
memory_message_send), and over there "check my inbox" (memory_message_pop). Seeagent-messaging.md.
Recipe: several accounts or an external launcher
Give each account or provider its own config home and pass it with --env, so
the harness, its hooks and MCP, and transcript import all use that one home:
ai-memory run --env CLAUDE_CONFIG_DIR="$HOME/.claude-work" claude
ai-memory run --env CODEX_HOME="$HOME/.codex-work" codex # the dir must exist
--envand--env-filebelong toai-memory run, so they go before the harness name.- A launcher or orchestrator that starts harnesses with its own environment
(provider keys, an account's config dir) can write it to a file and pass
--env-file <path>, oneKEY=VALUEper line. Values are taken literally, so use absolute paths in the file. - Auto-wire runs once per config home, so the second account gets its hooks +
MCP on its own first launch. To wire one by hand, export the variable for the
installers:
CLAUDE_CONFIG_DIR="$HOME/.claude-work" ai-memory install-hooks --agent claude-code --apply, then the same forinstall-mcp --client claude-code --apply.
Recipe: run an unsupervised agent safely (--yolo + ai-jail)
--yolo maps to each harness's dangerous-mode flag (Claude Code →
--dangerously-skip-permissions), which runs every tool call with no
confirmation. ai-memory run --yolo adds guardrails around that, gated
entirely on a real interactive terminal (stdin and stderr both TTYs), so
scripts, hooks, and CI are never prompted:
ai-memory run --yolo claude
- The warning. Before the agent spawns, you are asked to confirm:
Enter/y/yesproceeds (the default);n/noaborts before anything launches. - The ai-jail offer. If ai-jail
is usable — on Linux/macOS, installed on
PATH(or~/.local/bin/ai-jail), with its sandbox backend present (bwrapon Linux,sandbox-execon macOS) — and you are not already inside it, a second question offers to re-run the session inside it. When it is not usable (or on Windows) there is no second question; the run just proceeds. Accepting re-execs the original command underai-jail --network --agent-state --env <NAME>... --, forwarding only the credential/config environment variables that are already set (server/hook URL,CLAUDE_CONFIG_DIR, provider API keys, etc.) —--networkkeeps the loopback ai-memory server reachable while still sandboxing the filesystem. Declining keeps the run unsandboxed (your choice, already warned). - Choosing what the jail gets. After you accept the offer, a checklist
lists what this host can mount, pre-marked so
Enterdoes the friendly thing: every credential that exists (~/.config/gh,~/.aws,~/.kube,~/.config/gcloud,~/.docker/config.json), SSH keys + agent when youroriginis an SSH remote, and worktree metadata in a linked worktree. The Docker socket (grants host root), GPU, display, Pictures, and Tailscale are listed unchecked. Type row numbers to flip them (2 4), orall/none. A mounted credential is usable by the unsupervised agent, so uncheck what it should not touch. What you see is what you get: unchecked rows are passed as--no-X, so they stay off even if your global~/.ai-jailenables them. - Skipping the questions.
ai-memory run --jail claudere-runs inside ai-jail straight away, turning on those pre-marked defaults and leaving everything else to your own ai-jail config — with or without--yolo, and in scripts too; it fails rather than running unjailed if ai-jail is not usable.--jail=github,ssh,no-miseis exact: the listed toggles (no-Xforces one off), with every other checklist row forced off;allturns every row on andnoneturns every row off.--no-jailnever jails and skips the offer (the--yolowarning stays). There are no bare--github-style flags on purpose: they would collide with the harness's own flags (Claude Code has a--worktree). The credential mounts need ai-jail 2.5.0; toggles your installed ai-jail lacks are hidden, and naming one is an error. - A project
.ai-jailwins. If the directory you launch from has its own.ai-jail, ai-jail loads it as-is: no checklist, and a bare--jailadds no toggles;--jail=…still applies its list on top. A project file cannot enable credentials (ai-jail treats it as untrusted), so to have them mounted automatically there, enable them in your global~/.ai-jailor pass--jail=github,…. ai-memory always passes--no-save-config, so a jailed run never writes its own flags into your repository's.ai-jail. - Already inside ai-jail. Both prompts are skipped and the run proceeds
directly —
ai-jail ai-memory run … --yolosees no extra friction. Detection is Linux (ai-sandboxhostname) / macOS (PS1starting with(jail)); it fails open (shows the warning) when undetectable, never open to skipping it silently. - Claude "true yolo".
--true-yoloincludes everything--yolodoes (ai-memory run claude --true-yolois enough; adding--yolotoo is harmless) and, for Claude, also forcesbypassPermissionsover anydefaultModein your settings. For other harnesses it is the same as--yolo.claude_true_yolo = trueinconfig.toml/AI_MEMORY_CLAUDE_TRUE_YOLO=trueapplies the Claude extra to every--yololaunch, never to a run without it. It cannot remove your ownaskrules: Claude Code honors explicitpermissions.askrules (and its built-in command-safety checks) in every mode, so a rule likeBash(docker run *)in~/.claude/settings.jsonstill pauses the run. For a pause-free sandbox, drop thoseaskentries —denyrules block without pausing, so they can stay. Best paired with ai-jail. - Passing extra env, e.g. a GitHub token.
ai-memory run claude --yolo --env GH_TOKEN="$(gh auth token)"forwards it into the jailed agent (needs ai-jail 2.4.2 or later when you accept the jail offer). This hands a sandboxed agent your token, so only do it for work you'd trust it with; it is deliberately never forwarded automatically.
See design-yolo-safety-ai-jail.md for the
full contract.
Recipe: send different repositories to different servers
One machine, several organisations, each with its own ai-memory server. Register each server once under a name, with the directory it is allowed to serve, then let each repository's marker pick it:
ai-memory server add team-a --url https://memory-a.example.com --root ~/work/team-a --auth-token-stdin
ai-memory server add team-b --url https://memory-b.example.com --root ~/work/team-b --auth-token-stdin
# ~/work/team-b/.ai-memory.toml
workspace = "team-b"
server = "team-b"
Repositories without a server key keep using the server install-hooks
configured. The marker only ever names a profile; a profile that does not
resolve drops the event rather than sending it anywhere else. Details, the
fail-closed rules, and which integrations support it:
marker-file.md.
Recipe: run the server on a Mac
Use the menu bar app when you want one .app that starts the server and
opens the existing tools (web UI, ai-memory status, config, logs). It does
not replace those tools with a second dashboard.
./companions/ai-memory-macos/build.sh
open "companions/ai-memory-macos/dist/AI Memory.app"
Drag AI Memory.app to /Applications, then Install & Start Server
from the menu extra. Wire an agent with the bundled binary:
BIN="/Applications/AI Memory.app/Contents/Resources/runtime/ai-memory"
"$BIN" install-mcp --client claude-code --apply
"$BIN" install-hooks --agent claude-code --apply
Memory stays in ~/Library/Application Support/ai-memory. Replacing the
.app does not rewrite it. Full paths (tarball, source, Docker, launchd):
macos.md.
From the terminal (CLI)
Your agent runs most of these for you; run and continue are how you start it:
ai-memory run <harness> # launch a harness, hooks + MCP auto-wired
ai-memory continue # resume the newest managed checkout
ai-memory workstreams # list this checkout's managed workstreams
ai-memory status # counts, paths, health
ai-memory doctor # is every harness that ran here captured?
ai-memory backfill # import prior local history into an empty store
ai-memory write-page … # save a durable page
ai-memory handoffs # list open handoffs for the project
ai-memory message list # cross-project inbox
ai-memory server list # server profiles a marker can route to
ai-memory export-okf … # export the wiki as an OKF bundle
ai-memory serve # run the server
When it isn't doing what you expect
- Nothing is being remembered: hooks may not be installed.
ai-memory run <harness>installs its hooks + MCP on the first launch per harness, ai-memory version and config home, and again afterai-memory uninstall. If that harness already launched throughrunand its hooks went missing another way (removed by hand, a failed first wire), install them by hand:ai-memory install-hooks --agent <your-agent> --applyandai-memory install-mcp --client <client> --apply. Then checkai-memory status/ai-memory doctor. - Only some agents are being remembered: run
ai-memory doctor. It lists every harness that has local sessions in this project and whether the server captured them — so a harness you rotated in without installing its hook (a silent gap: it keeps its own local history while capturing nothing) shows up as a warning with the exactinstall-hookscommand to fix it. - I just installed hooks in a project I've worked in for a while: the first
time you open the project after installing, ai-memory imports your existing
local session history once (bounded, sanitized on the server, only into an
empty store) so it isn't starting from nothing. It runs automatically in the
background; you can trigger or preview it yourself with
ai-memory backfill(--dry-runto see what it would import), or turn it off withAI_MEMORY_BACKFILL_ON_START=false. - A search misses something you saved: confirm the scope — memory is per-project; a page saved in project A is not returned in project B unless it was written to the global scope or you query with an explicit scope.
- You want an LLM feature (consolidation, digests): set a provider
(
AI_MEMORY_LLM_PROVIDER=…); without one, capture + search still work.