Shared project memory for coding agents such as Claude Code and Gemini Antigravity.
Memory is plain Markdown in docs/memory/, committed to the repository.
No server, database, API keys or OpenClaw required.
The core principle: the Markdown files are the source of truth; the CLI is only a helper.
Every file can be read and edited by hand, any agent that reads AGENTS.md follows the same
protocol, and per-agent automation (hooks) is an addition, not a requirement.
┌────────────────────────────────────────────┐
│ agent-memory / shared-memory (CLI) │
│ install · read · save · search · handoff · │
│ test · status · doctor │
└────────────────────────────────────────────┘
┌───────────────────────────────┐ ┌───────────────────────────────┐
│ Claude Code │ │ Gemini Antigravity │
│ CLAUDE.md + .claude/settings │ │ AGENTS.md + .agents/hooks.json│
│ SessionStart · Stop · SessEnd │ │ SessionStart · Stop │
│ /memory-handoff, /memory-save │ │ /memory-handoff, /memory-save │
└───────────────┬───────────────┘ └───────────────┬───────────────┘
└────────────────┐ ┌──────────────┘
▼ ▼
┌────────────────────────────────────────────┐
│ docs/memory/ (Markdown, in git) │
│ project · decisions · learnings · handoff │
└────────────────────────────────────────────┘
The tool never touches Claude's (~/.claude/…) or Gemini's (~/.gemini/…) internal memory
directories. Memory therefore stays auditable, can be reviewed in pull requests, and moves to
another machine with a git clone.
1. The CLI, once per machine. Requires Node.js 18.17 or later. No other dependencies.
npm install -g github:andriest/agent-memory-cli # once this repo is pushed to GitHub
# or from a local copy:
git clone https://github.com/andriest/agent-memory-cli.git ~/tools/agent-memory-cli && npm install -g ~/tools/agent-memory-cliThree equivalent command names are installed: shared-memory, agent-memory and amem.
2. A repository: one command, once.
shared-memory install . # or: shared-memory install ~/projects/my-appThat is the whole setup. Then start claude or agy in the project: the shared memory, the
project instructions and the previous handoff load automatically. Review the changes and
commit them so the setup travels with the repository:
git add docs/memory .agent-memory.json AGENTS.md CLAUDE.md .claude .agents
git commit -m "Add shared agent memory"On the first run install does everything:
- Creates the shared memory in
docs/memory/and the config.agent-memory.json. - Wires both agents to it: the memory protocol in
AGENTS.md, session hooks, and the/memory-handoffand/memory-savecommands for each agent. - Connects their instruction files without moving your content. Claude Code skips
AGENTS.mdwhenever aCLAUDE.mdexists, soCLAUDE.mdgets an@AGENTS.mdimport. Antigravity does not readCLAUDE.md, so its session-start hook includesCLAUDE.md's own instructions; Claude's hook does the same forGEMINI.md. - Imports Claude Code's private auto memory for this project (personal
usermemories and anything that looks like a secret are skipped). This happens once: the time is recorded asclaude.importedAt, so later installs never re-add entries you deleted. - Turns off Claude Code's private auto memory for the project (
"autoMemoryEnabled": false), so new knowledge goes to the shared memory. An explicit value you already set is kept.
For a repository where each branch has its own rules, run shared-memory install . --per-branch
on each branch, after listing branch aliases (see
Repositories where each branch has its own rules).
Only that branch's memories are imported, and the choice is saved in that branch's config.
install is idempotent. It is safe to re-run, for example after upgrading the tool, and it
never overwrites memory content or your own hooks and settings. shared-memory doctor .
checks the result. Files it touches:
| File | For | Contents |
|---|---|---|
.agent-memory.json |
all | Project configuration (see Configuration) |
docs/memory/project.md |
all | Optional curated context: overview, stack, commands, architecture, conventions. Not needed when CLAUDE.md/AGENTS.md already describe the project |
docs/memory/decisions.md |
all | Decisions and why they were made (newest at the bottom) |
docs/memory/learnings.md |
all | Gotchas, root causes, environment quirks |
docs/memory/handoff.md |
all | Handoffs between sessions, newest first |
docs/memory/status.md |
all | Local snapshot: last session and last test run (gitignored) |
CLAUDE.md |
Claude Code | An @AGENTS.md import between markers (the rest of the file is untouched) |
.claude/settings.json |
Claude Code | Project hooks SessionStart, Stop, SessionEnd; "autoMemoryEnabled": false |
.claude/commands/memory-{handoff,save}.md |
Claude Code | /memory-handoff, /memory-save |
AGENTS.md |
Antigravity (+ other agents) | The memory protocol block |
.agents/hooks.json |
Antigravity IDE & CLI | Workspace hooks: SessionStart, Stop |
.agents/workflows/memory-{handoff,save}.md |
Antigravity | /memory-handoff, /memory-save |
Options:
--agents claudeor--agents antigravityinstalls a single agent. The choice is saved in the config. With Claude Code alone,CLAUDE.mdgets the protocol itself instead of an import.--per-branchimports only the current branch's Claude memories (see above).--skip-importskips the Claude memory import.--memory-onlycreates the memory without any agent integration.--dir .memoryuses a different memory location.
uninstall removes the integrations, including the autoMemoryEnabled setting it added, and
restores your files exactly as they were. Memory files and the config are kept.
| When | Claude Code | Antigravity | What happens |
|---|---|---|---|
| Session start | SessionStart |
SessionStart |
Memory context is injected: latest handoff, status, project.md, recent decisions and learnings, and extra files from the config, within context.maxChars. After clear or compact it is injected again; a resumed session only gets it again if the memory changed in the meantime. The working-tree state is recorded as the baseline. |
| Agent finishes a reply | Stop |
Stop |
If this session changed files since it started or since the last handoff, the agent gets a short reminder to write a handoff with --session <id> (and, only if relevant, to save a decision or learning, or run the configured tests). |
| Session closes | SessionEnd |
(every Stop) |
A deterministic snapshot (no model involved) is written to status.md: agent, time, branch, uncommitted changes, and whether a handoff was written. |
The Stop reminder is designed to stay out of the way:
- Only when code changed. Questions and answers without file changes never trigger it, and
neither do changes inside
docs/memory/. - Once per code state. If nothing new changed, the agent is not asked again.
- Rate limited. The default is
session.remindIntervalMinutes: 20. - Feedback, not an error. In Claude Code the reminder is returned as
hookSpecificOutput.additionalContext, which the transcript labels "Stop hook feedback". Adecision: "block"would be labelled "Stop hook error" even though nothing failed. In Antigravity it is a{"decision": "continue", "reason": …}system message. - Loop-safe. Claude uses
stop_hook_active(and Claude Code's own cap on consecutive continuations). Antigravity is only reminded whenfullyIdleis true and there is no error. - One entry per session. A handoff with the same
--sessionreplaces that session's entry, sohandoff.mddoes not pile up. - Short. About 450 characters with one required command, because every reminder costs the
agent an extra turn. Tests are only mentioned when
tests.commandis set. - Skippable. For trivial changes the agent can reply with one line and stop.
If a session ends without a handoff, the next session receives the warning "⚠ The previous session ended without a handoff" together with the list of uncommitted changes.
Set "session": { "remindIntervalMinutes": 60 } to be reminded less often,
"session": { "onStop": "off" } to disable the reminder, or "onEnd": "off" to disable the
snapshot. To disable it for one agent only, give a mode per agent; agents you leave out keep
"remind":
"session": { "onStop": { "claude": "remind", "antigravity": "off" } }With the reminder off, a session that ends without a handoff is still recorded in
status.md and flagged to the next session.
The shared memory costs tokens, so it is kept small:
- Always in context: the memory protocol in
AGENTS.md, about 2,600 characters (roughly 700 tokens). With Claude Code's own auto memory turned off, Claude no longer loads itsMEMORY.mdindex, which is usually about the same size or larger. - Once per session: the session-start context, at most
context.maxChars(8,000 characters by default, roughly 2,000–2,500 tokens). Lower it, orcontext.recent, for a smaller footprint. - Per reminder: a short message plus the extra turn in which the agent writes the handoff.
This is the largest variable cost; raise
session.remindIntervalMinutesor setsession.onStop: "off"to reduce it. - Free:
import,search,status,doctorand test recording run locally without a model.
shared-memory read # context summary (the same one hooks inject)
shared-memory read handoff # latest handoff (--all for every entry)
shared-memory status # last session + last test run
shared-memory search pnpm ci # each argument is a term; "quote a phrase" to match it
shared-memory search auth --in decisions --full
shared-memory save decision "Use pnpm workspaces" \
--body "Faster installs; the monorepo needs workspaces." --tags tooling,deps --agent claude
shared-memory save learning "Vitest needs --pool=forks on CI" --body - --agent antigravity < notes.md
shared-memory test # run tests.command and record the result
shared-memory test "npm run test:unit" # or any other command
shared-memory handoff --agent claude --summary "Login form wired to the API" \
--done "POST /login endpoint" --next "Add refresh tokens" --blockers "API error format?"handoff automatically includes the git branch, the last test run, and the list of uncommitted
changes. The memory folder and sensitive paths are left out.
If Claude Code has already built up auto memory for a project
(~/.claude/projects/<project>/memory/), import it once so every agent can use it:
shared-memory import claude # show the plan; nothing is written
shared-memory import claude --apply # write it
git diff docs/memory # review, edit or delete entries, then commit| Claude memory type | Imported into | Notes |
|---|---|---|
feedback |
decisions.md (tags imported, convention) |
How to work on this project |
project |
learnings.md (tags imported, project) |
Facts, findings, project state |
reference |
learnings.md (tags imported, reference) |
Pointers to external resources |
user |
skipped | Personal preferences stay private; shared memory is committed to git |
- Read-only on Claude's side. Claude's memory files are only read, never changed or deleted.
- Finds the directory like Claude Code does. It uses
autoMemoryDirectory(local, project or user settings),CLAUDE_CONFIG_DIR,CLAUDE_CODE_PROJECT_DIR_NAME, then~/.claude/projects/<encoded git root>/memory(shared by all worktrees). Use--from <dir>to point at it explicitly. - Safe to re-run. Every entry carries
source: claude-memory/<file>, so imported files are skipped.--updaterefreshes entries whose source memory changed since the import. - Converted to the shared format. Titles come from the memory's heading or the first clause of
its description (the full description is kept in the body).
[[links]]between memories become the linked entry's title, and headings inside a memory are demoted so they cannot break the file. Dates come from the memory'smodifiedtimestamp. - Filtered. Entries that look like secrets are skipped, and so are titles that already
exist.
--types feedback,projectlimits what is imported, and--jsonprints the plan for scripts. project.mdis not touched. It stays curated by hand. Promote stable facts into it yourself, or ask an agent to.- Hand edits are protected. Each imported entry records a
hash:of its own text. An entry you edited after importing is never overwritten by--updateor removed by--prune.
docs/memory/ lives in the repository, so every branch already has its own memory. Claude
Code's auto memory, however, is one directory for the whole repository, so it mixes the
rules of every branch. Use --branch to import only what belongs to the current branch:
shared-memory import claude --branch current # plan, with a reason per memory
shared-memory import claude --branch current --prune --apply # also remove earlier imports
# that belong to other branchesA memory is kept when it mentions this branch, mentions no branch at all (a general rule),
or mentions several branches including this one (a rule that lists them all). It is
skipped when it only mentions other branches. An explicit marker in the description or
first line (only on X, exclusively for X, or Indonesian KHUSUS/hanya + X) overrides that and assigns the
memory to X alone.
Branches are recognised by name from git branch (local and remote), and by their last path
segment (borealis for brand/borealis). Common names such as main only count in a branch
context (`main`, "branch main"). Notes often refer to a branch by another name, so
list aliases in .agent-memory.json:
"branches": {
"main": ["Core Site"],
"brand/aurora": ["Aurora Shop"],
"brand/borealis": ["Borealis Store"]
}Matching is done on the text of each memory, so review the reasons in the plan before
--apply. A general memory that happens to name another branch's channel will be skipped;
add it back by hand, or re-run without --branch and delete what you do not want.
--prune only removes entries that carry a source: claude-memory/… marker and were not
edited by hand.
After importing, Claude Code still keeps writing its private memory. To make the shared memory
the only one for the project, add "autoMemoryEnabled": false to .claude/settings.json.
The memory protocol in AGENTS.md/CLAUDE.md also tells every agent to keep project
knowledge in the shared memory.
Antigravity CLI has no plain-text memory store to import from: in version 1.4.9 its
per-conversation data is internal binary protobuf. Notes it writes into GEMINI.md,
AGENTS.md or .agents/rules/ are already project files, which both agents can read.
.agent-memory.json at the repository root. Every key is optional; missing keys fall back to
the defaults. shared-memory config prints the effective configuration. Invalid values are
rejected with a clear message; unknown keys only produce a warning.
sensitive.excludePaths hides these paths from handoffs and status, and matching files are never
read through context.files. A pattern without / matches the file name in any folder.
- Secret detection.
saveandhandoffrefuse content that looks like a private key, a GitHub, Slack or AWS token, ansk-…key, a JWT,password=…, a URL with an embedded password, or a pattern fromsensitive.patterns. Test output and test commands are redacted ([REDACTED]) before they are recorded. References such asprocess.env.API_KEYor${TOKEN}are allowed. - No duplicates. An existing title is refused so the old entry gets edited instead. Use
--forceto add it anyway. - Safe concurrent writes. Writes use a lock file and are atomic.
- Hooks never break a session. If the CLI is not installed, the memory does not exist, or an error occurs, the hook emits each agent's valid "do nothing" output and exits 0.
- Merge, never overwrite. Your hooks in
.claude/settings.jsonand.agents/hooks.json, including ones on the same events, are kept in their original order. Malformed JSON files are reported, not overwritten.
Tested on 2026-10-28 with real headless sessions (claude -p and agy -p), not just unit tests:
| Agent | Version | SessionStart (context injected) | Stop (reminder → handoff) | Session end (status) |
|---|---|---|---|---|
| Claude Code | 2.1.312 | ✅ | ✅ agent received the reminder | ✅ SessionEnd |
Antigravity CLI (agy) |
1.4.9 | ✅ | ✅ agent ran the tests and wrote a handoff | ✅ via Stop |
| Antigravity IDE | — | not tested | not tested | not tested |
The Claude Code Stop reminder was re-verified on 2026-10-29 after switching to
additionalContext: the agent received it, continued, and wrote the session's handoff with
--session. The "Stop hook feedback" label appears only in interactive sessions, so that part
comes from the Claude Code hooks documentation rather than from a headless test.
Instruction files were verified the same way: Claude Code read a codeword that existed only in
AGENTS.md (through the @AGENTS.md import inside the managed block), and Antigravity CLI read
a codeword that existed only in CLAUDE.md (through its session-start hook).
Notes:
- Antigravity IDE uses the same
.agents/hooks.jsonfile and format according to the documentation, but has not been tested directly. If hooks do not run in the IDE,AGENTS.md(supported since IDE 1.20.5) still tells the agent to runagent-memory read. - Antigravity
injectStepsformat. The documentation is inconsistent. The form that works inagy1.4.9 is{"injectSteps":[{"systemMessage":{"systemMessage":"…"}}]}. - Gemini CLI (not Antigravity) reads
GEMINI.mdby default. Add"context": { "fileName": ["AGENTS.md", "GEMINI.md"] }to.gemini/settings.json. - Windows. Hook commands use
shsyntax (command -v …). Claude Code runs them through Git Bash. These hooks have not been tested with Antigravity on Windows.
Every entry is a level-2 section. The metadata line is optional, so entries written by hand are still understood by the tool:
## 2026-10-28 · Use pnpm workspaces
> agent: claude · tags: tooling, deps
What was decided and why. Alternatives that were rejected.status.md and .state.json are local only (gitignored), because they describe this machine's
working tree. Anything that should be shared across machines belongs in a handoff.
npm test # node:test, no dependencies (41 tests)Code layout:
src/cli.mjs— commands, argument parsing, and per-agent hook adapters (stdin/stdout formats)src/session.mjs— session logic (start/stop/end) and the test runnersrc/memory.mjs— memory operations, context, search, handoffssrc/config.mjs— defaults and validation for.agent-memory.jsonsrc/integrations.mjs— install/uninstall/inspect for Claude Code and Antigravitysrc/git.mjs— git status, working-tree fingerprint, sensitive path filtersrc/templates.mjs— all text written into target repositoriessrc/markdown.mjs— entry parser and managed blockssrc/secrets.mjs— secret detection and redactionsrc/importers/claude.mjs— one-way import of Claude Code auto memory
{ "dir": "docs/memory", // memory location "agents": ["claude", "antigravity"], // integrations installed by `install` "context": { "maxChars": 8000, // hard limit for the injected context (Claude truncates hook output over 10,000) "recent": 5, // number of recent decisions/learnings "sections": ["handoff", "status", "project", "decisions", "learnings", "files"], // selection & order "files": [], // extra repository files, e.g. ["docs/architecture.md"] "agentInstructions": true // give each agent the instruction file it does not read itself }, "handoff": { "keep": 10, // handoffs kept in handoff.md; the rest go to archive/handoffs.md "gitChanges": true // include branch & uncommitted changes }, "session": { "onStop": "remind", // "remind" | "off", or per agent: // { "claude": "remind", "antigravity": "off" } "remindIntervalMinutes": 20, "onEnd": "status" // "status" | "off" }, "tests": { "command": "" }, // e.g. "npm test"; used by `shared-memory test` "sensitive": { "patterns": [], // extra regexes to refuse, e.g. ["\\bCUST-\\d{6}\\b"] "allow": [], // literals known to be safe (false positives) "excludePaths": [".env", ".env.*", "*.pem", "*.key", "*.p12", "secrets/**", "credentials*"] }, "claude": { "disableAutoMemory": true, // install sets "autoMemoryEnabled": false "importOnInstall": "auto" // first install imports Claude memory: "auto" (per branch // when `branches` is set), "all", "branch" or "off"; // `importedAt` is recorded afterwards }, "branches": {} // branch aliases for `import claude --branch`, e.g. // { "brand/aurora": ["Aurora Shop"] } }