Installing claude-memory integration¶
Use
claude-memory install(andclaude-memory install --upgradeto bring an existing install up to date);--dry-runshows the plan first. It does every step below (binary, env file, database, Ollama, namespaces, hooks, MCP, skills, CLAUDE.md block, and on macOS the launchd jobs) andclaude-memory doctorchecks the result. The manual steps below are the reference for what it does.
Everything under integration/ is produced by this repo but installs
into user-level config (~/.local/bin, ~/.config/claude-memory,
~/.claude/, ~/Library/LaunchAgents, your shared CLAUDE.md). None of it
is applied automatically — every step below is something you run
yourself. Follow them in order.
Check the result at any time with claude-memory doctor (after step
1): a read-only health check of every step below — env file, Postgres
(connection, pgvector, schema), Ollama, MCP registration, hooks, skills,
CLAUDE.md block, namespaces and the launchd jobs. It never changes
anything, prints a fix: line under each failing check, exits 1 when a
check fails (--strict: also on warnings) and has --json for scripts.
0. Prerequisites¶
- The server Postgres is already deployed and reachable over Tailscale
(
DEPLOY.md). - Go toolchain installed (for
make build/make install).
1. Build and install the binary¶
cd /path/to/claude-memory
make install
# -> installs to $HOME/.local/bin/claude-memory
Confirm $HOME/.local/bin is on your PATH (or at least that the hook
wrappers below, which use an absolute path, don't need it to be).
2. Create the laptop env file¶
Run this in a regular terminal (not via Claude Code's ! prefix — read -s
needs an interactive TTY). The password is typed hidden and never lands in
shell history or the chat:
mkdir -p ~/.config/claude-memory
read -rs "?APP_DB_PASSWORD: " PW && echo && umask 077 && printf \
'MEMORY_PG_DSN=postgresql://claude_memory:%s@<POSTGRES_BIND_IP>:5432/claude_memory\nMEMORY_OLLAMA_URL=http://127.0.0.1:11434\nMEMORY_EMBED_MAX_TOKENS=2048\nMEMORY_PR_INGEST_REPOS=%s\n' \
"$PW" "$HOME/work/acme" > ~/.config/claude-memory/env && unset PW \
&& chmod 600 ~/.config/claude-memory/env && echo "env ok"
Replace <POSTGRES_BIND_IP> with the server's Tailscale IP (DEPLOY.md).
(read -rs "?prompt" is zsh syntax; in bash use read -rsp "prompt: " PW.)
If the password contains @ : / %, percent-encode it in the DSN.
config.LoadFromFile refuses the file if it is group/world-readable.
Check it without revealing the password:
sed -E 's#(claude_memory:)[^@]*@#\1****@#' ~/.config/claude-memory/env
2a. Set up namespaces¶
Records are partitioned by namespace (a company, a side project, ...), so facts from one project don't crowd out another's. This is a local, single-user database: namespaces are for relevance, not security, and an occasional cross-project memory is harmless. This step needs no database, DSN or Ollama — only the binary from step 1.
Create the mapping file (~/.config/claude-memory/namespaces.yaml, mode
0600) with one of:
# map project areas to namespaces right away (globs: ** = any depth, ~ = home)
claude-memory namespaces init work='~/work/acme/**' pet-game='~/src/pet-game/**'
# or start with only the shared default and add projects later
claude-memory namespaces init
Directories matching no mapping use default: — global unless you pass
--default NAME (e.g. init --default work to make one project the
catch-all). Nothing is special-cased in code; any name works
(lowercase letters, digits, -, _).
Upgrading from a pre-namespace install: the migration backfilled
existing records to work. Map your Acme directories to it or those
records stay invisible: claude-memory namespaces add work '<path>/**'.
Check the result:
claude-memory namespaces which ~/work/acme/billing-service # -> work
claude-memory namespaces which /tmp # -> global (the default)
integration/namespaces.example.yaml shows the file format if you prefer to
edit it by hand.
3. Install and configure Ollama¶
Follow integration/ollama.md in full: brew install ollama, brew
services start ollama, ollama pull bge-m3, and its health check (keep-alive
is sent per request by claude-memory, no plist edit). Do this before step 7's verification
— the hook round-trip needs a warm model to hit its latency budget.
4. Register the MCP server¶
Follow integration/mcp-registration.md:
claude mcp add --scope user claude-memory -- "$HOME/.local/bin/claude-memory" serve
5. Merge the hooks into ~/.claude/settings.json¶
Copy the wrapper scripts somewhere stable and executable:
mkdir -p ~/.claude/hooks/claude-memory
cp integration/hooks/user-prompt-submit.sh ~/.claude/hooks/claude-memory/
cp integration/hooks/session-end.sh ~/.claude/hooks/claude-memory/
chmod 755 ~/.claude/hooks/claude-memory/*.sh
Then merge (don't overwrite) the hooks block from
integration/settings.snippet.json into ~/.claude/settings.json. If
~/.claude/settings.json already has a top-level "hooks" key, append
these two entries (UserPromptSubmit, SessionEnd) into its existing
arrays for those events instead of replacing the key outright — merge
by hand or with jq -s '.[0] * .[1]' against a throwaway copy, then
review the diff before saving over the real file.
6. Paste the CLAUDE.md snippet¶
Open integration/claude-md-snippet.md, copy everything from the ---
separator onward, and paste it into your shared CLAUDE.md under its own
heading. This is a manual copy — review it like any other edit to a
shared file before committing.
7. Copy the skills¶
mkdir -p ~/.claude/skills
cp -r integration/skills/remember ~/.claude/skills/
cp -r integration/skills/memory-digest ~/.claude/skills/
8. (Optional) Load the launchd jobs (PR ingest + cleanup)¶
Skip this to run ingest-pr / cleanup by hand (see USAGE.md); the
first ingest-pr run spends two haiku calls per PR over the last 30 days.
claude-memory install does this step (see the note at the top). By
hand: create ~/.local/state/claude-memory, then write one plist per job
to ~/Library/LaunchAgents/io.github.claude-memory.<job>.plist from
the Go template integration/launchd/job.plist.tmpl (fields: Label
io.github.claude-memory.<job>, Program the absolute path of the
installed claude-memory, Args [<job>], PATH the directories of
claude, git and az plus /usr/bin:/bin, Hour/Minute 7:15 for
cleanup and 7:00 for ingest-pr, LogPath
~/.local/state/claude-memory/<job>.log). The jobs run the binary
directly; it reads ~/.config/claude-memory/env itself, so there is no
wrapper script (the old run-with-env.sh wrapper sources the env file,
which breaks passwords containing $ or &). Then load each one:
launchctl bootout gui/$(id -u)/io.github.claude-memory.cleanup 2>/dev/null || true
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/io.github.claude-memory.cleanup.plist
# the same for ingest-pr
launchctl list | grep io.github.claude-memory
Set MEMORY_PR_INGEST_REPOS in ~/.config/claude-memory/env (step 2)
to the comma-separated local repo paths (or root directories) you want
ingest-pr to scan, if you haven't already. Azure DevOps repos need az
logged in, GitHub repos gh auth login, GitLab repos glab auth login
(install adds their directories to the job PATH when they are installed;
re-run install --upgrade after installing one later). See USAGE.md
"PR ingest" for the trusted-author rule and per-namespace pr_ingest.
9. Verify¶
Start with claude-memory doctor: every check should be pass or
info. The manual checks below exercise the same paths end to end.
After verifying, see USAGE.md for day-to-day operation.
MCP tool list (no Claude Code needed — talks to the binary directly):
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke-test","version":"0"}}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| "$HOME/.local/bin/claude-memory" serve
# Expect the 7 memory_* tools, nothing else.
Hook round-trip (UserPromptSubmit): store something first so there
is a match to find, then simulate the hook's stdin:
source ~/.config/claude-memory/env
"$HOME/.local/bin/claude-memory" seed --file seed/facts.yaml --dry-run # sanity-parses, doesn't write
echo '{"prompt":"what is the k8s label length limit","cwd":"'"$PWD"'"}' \
| ~/.claude/hooks/claude-memory/user-prompt-submit.sh
# Expect a JSON {"hookSpecificOutput":{"hookEventName":"UserPromptSubmit",
# "additionalContext":"<header line>\n- [memory] <title> (repo: <repo>, id: <id>)\n..."}}
# (at most 3 cards, title/repo/id only), once seed/facts.yaml (or a real
# memory_store) has something to match. No output at all otherwise.
SessionEnd backgrounding: confirm it returns fast and never blocks:
time (echo '{"transcript_path":"/nonexistent","cwd":"'"$PWD"'","session_id":"smoke"}' \
| ~/.claude/hooks/claude-memory/session-end.sh)
# real time should be well under 100ms; exit code 0 either way.
End-to-end in Claude Code: open a new session in any
repo, type a prompt related to something you've stored, and confirm the
injected context card appears with only title/repo/id — this confirms
the hooks are actually wired through ~/.claude/settings.json, not
just runnable standalone.
Uninstall¶
# MCP
claude mcp remove --scope user claude-memory
# launchd
launchctl unload ~/Library/LaunchAgents/io.github.claude-memory.ingest-pr.plist
launchctl unload ~/Library/LaunchAgents/io.github.claude-memory.cleanup.plist
rm ~/Library/LaunchAgents/io.github.claude-memory.*.plist
# hooks: remove the UserPromptSubmit/SessionEnd entries you merged into
# ~/.claude/settings.json by hand, then:
rm -rf ~/.claude/hooks/claude-memory
# skills
rm -rf ~/.claude/skills/remember ~/.claude/skills/memory-digest
# CLAUDE.md: manually remove the pasted section from your shared CLAUDE.md
# binary + config (keep ~/.config/claude-memory/env and namespaces.yaml if
# you plan to reinstall later; otherwise remove them too)
rm ~/.local/bin/claude-memory ~/.local/bin/claude-memory-run-with-env.sh
Ollama itself (brew services stop ollama, brew uninstall ollama) is
left running by design — other tools on the laptop may depend on it;
stop it manually if you're sure nothing else uses it.
Using namespaces¶
Day to day you don't think about them: the namespace is resolved
automatically from the project directory each time — by the MCP server (the
directory Claude Code was started in), the prompt hook (the prompt's
directory), session extraction (the session's directory) and ingest-pr
(each repo's path). Search covers the current namespace plus global;
listing, dedup and lookups by id never leave the current namespace.
| I want to... | Do this |
|---|---|
| list all namespaces and their paths | claude-memory namespaces list [--json] |
| see what a directory maps to | claude-memory namespaces which [DIR] |
| add a project / more paths | claude-memory namespaces add NAME 'GLOB' ['GLOB'...] |
| force a namespace for one repo | set MEMORY_NAMESPACE in that repo's .claude/settings.json under env (beats the file) |
| share a stack-generic fact everywhere | memory_store with namespace: "global" (e.g. a Go or Docker gotcha) |
| start over | claude-memory namespaces init --force ... |
The most specific matching path wins (~/work/acme/infra/** beats
~/work/acme/**). When no namespace can be chosen — no file, no match,
an unreadable file — work lands in global instead of failing.
Staleness warnings¶
A record remembers the commit it was written at (commit_sha) and the files
it is about. When a card, memory_search result or memory_get result is for
the repo you are standing in and any of those files differ at the current
HEAD, it is marked: the hook card ends with
⚠ code changed since this was recorded (N commits), and search/get carry
stale_hint (+ stale_commits). Verify the record against the code, then
memory_update it (a content/files change re-baselines it; or pass
commit_sha alone) or memory_feedback(outdated).
- Needs
gitonPATH. Nocommit_sha, no files, another repo, or an unknown commit means "unchecked": no marker, no error. - Inline and session records get
commit_sha = HEADonly when their files have no uncommitted changes at write time; otherwise they stay unchecked until a cleanmemory_update. PR records use the PR's merge commit. - The check is capped:
MEMORY_STALE_TIMEOUT_HOOK(default 50ms, hook) andMEMORY_STALE_TIMEOUT(default 500ms, MCP server) in~/.config/claude-memory/env. The hook also stops at its own latency budget. Per-session verdicts are cached under~/.local/state/claude-memory/stale-cache/.
Importing existing knowledge¶
claude-memory import automem|insights loads auto-memory files and
INSIGHTS.md entries as candidates (integration/USAGE.md, "Importing
existing knowledge"). Run it with --dry-run first (no database, Ollama or env
file needed). It needs migration 0004, which the new binary applies by itself at
the next session start (serve, extract --run, ingest-pr); you do not run
migrate for it. Rolling back means reinstalling the old binary, which ignores
the new column and index. Unreviewed imported candidates are deleted by the
cleanup TTL and a later import re-creates them, so run claude-memory review
within the candidate TTL (MEMORY_CANDIDATE_TTL, default 180 days; the
import prints it).
Usage events and stats¶
The hook appends one card_injected event per card to
~/.local/state/claude-memory/events/spool.jsonl (a single local write, never a
database call; capped at 10 MB). serve, extract --run, ingest-pr and
cleanup drain it into the events table (migration 0003); a file the
database rejected is kept as spool.*.failed. Until the new schema is applied
(run claude-memory migrate once after upgrading, before restarting Claude Code sessions: several serve processes starting at once can race on CREATE TABLE)
the events simply wait in the spool. claude-memory stats reports on them
(integration/USAGE.md, "Usage stats"); events older than 365 days are
removed by cleanup.