Add Memory to Claude Code¶
Claude Code is the reference wiring. It has the richest verified hook contract, a packaged two-command install, and it is the harness this integration's payload fixtures were captured from live.
Pre-release
popoto[mcp] is not on PyPI yet. Until a release ships, install from a
checkout: pip install -e '.[mcp]'. Hooks work on a bare
pip install popoto; the extra is only for the MCP tools.
Install¶
doctor must print redis reachable and retrieval lexical
(query-sensitive) before you go further. If Redis is not running:
Then either the plugin or the manual block. They do the same thing.
Plugin¶
Manual¶
Add to ~/.claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [
{ "hooks": [{ "type": "command", "command": "popoto-memory hook" }] }
],
"Stop": [
{ "hooks": [{ "type": "command", "command": "popoto-memory hook", "async": true }] }
]
}
}
Eight lines. "async": true on Stop keeps the write off the turn's
critical path.
To scope memory to one project rather than to ~/.claude/settings.json
globally, put the same block in that project's .claude/settings.json.
Restart Claude Code either way.
Verify¶
Have a conversation that establishes a fact, then start a new session and ask about it.
you: we deploy blue-green, and a failed health check rolls back automatically
claude: ...
(new session)
you: what happens if a deploy fails its health check?
claude: it rolls back automatically to the previous green environment
The recall happened because UserPromptSubmit fired, not because Claude
decided to look something up.
Then:
last assemble and last capture carry recent timestamps, records is
climbing, and failures reads none. Those three lines are the whole
health check.
What is stored, and where¶
In your Redis or Valkey, in the DefaultMemory:* keyspace, tagged with an
agent_id that defaults to the basename of your working directory. That tag
is honored as a read filter only on the composite-score retrieval path; the
shipped default lexical/BM25 path does not filter by it yet (#576), so two
projects on one Redis can retrieve each other's memories. Use a distinct
POPOTO_MEMORY_URL database per project for real isolation.
One turn becomes one record, verbatim. Issue #489 measured sentence-splitting extraction at 0.2078 judged accuracy against 0.3636 for raw ingestion on the same slice, so raw is the default here.
Nothing leaves your machine. There is no API key because there is nothing to authenticate to.
Inspect it directly:
Latency¶
Measured on an Apple M-series laptop, Python 3.12.13, redis 8.6.2 on
localhost, 50 records in scope, 25 subprocess invocations of the read hook:
p50 193 ms, p95 200 ms, max 211 ms, against a 400 ms p95 budget that
tests/test_integrations_latency.py enforces.
Nearly all of that is Python interpreter startup. The Redis work itself is 1-2 ms.
The MCP tools¶
The plugin also registers a stdio MCP server, or add it yourself:
memory_search, memory_save, memory_feedback, memory_status. These
are for deliberate use: searching for something specific mid-task, saving a
fact the turn text would not preserve, marking a memory wrong. Recall and
capture do not depend on them, and Claude never has to call one for memory
to work.
Tuning¶
Every setting is an environment variable and every one is optional. The full table is in Harness Integration. The three worth knowing:
export POPOTO_MEMORY_AGENT_ID=my-project # share one corpus across directories
export POPOTO_MEMORY_MAX_ITEMS=10 # default 5
export POPOTO_MEMORY_ENABLED=0 # kill switch, no config edit
Hooks inherit your shell environment, so exporting these in your shell profile is enough.
When it is not working¶
What doctor says |
What it means |
|---|---|
redis UNREACHABLE |
No server, or the wrong POPOTO_MEMORY_URL. It prints the fix. |
last activity never |
The hooks are not firing. Check that popoto-memory is on the PATH your harness sees, and that you restarted Claude Code. |
retrieval composite -- QUERY-BLIND |
Ranking is ignoring the prompt text. See Query-Blind Retrieval. |
FAILURES assemble=N |
Something is raising on the read path. The last five log lines print underneath. |
records 0 after several turns |
Capture is not running. Check the Stop hook specifically; UserPromptSubmit can work while Stop does not. |
Test a hook by hand, which is exactly what Claude Code does:
echo '{"hook_event_name":"UserPromptSubmit","session_id":"t","prompt":"how do deploys work?"}' \
| popoto-memory hook
You should get one line of JSON, or nothing at all if no memory matched. Never a traceback, and never a non-zero exit.
Uninstall¶
Your memories stay in your Redis until you delete them:
Verified against¶
claude 2.1.220, on macOS, 2026-08-07. The UserPromptSubmit and Stop
payloads in tests/fixtures/harness_payloads/claude_code_*.json were
captured from a live run of that version, and each file records the command
that reproduces the capture.