| name | aim-init |
| description | Initialize, migrate, or refresh a repo's ai-memory wiring: the .ai-memory.toml routing marker (workspace/project), the recall/write routing snippet in CLAUDE.md/AGENTS.md, the ai-memory MCP server entry, and the global capture hooks. Includes the qmd→ai-memory migration for repos still on the old wiki/qmd stack, and a refresh flow that detects CLI↔server version skew and re-stages hooks to parity. Use when the user asks to set up ai-memory in a project (greenfield or brownfield), wire the MCP, enable auto-capture, migrate off qmd, diagnose with doctor, or upgrade/refresh an existing setup (local CLI + hooks) to the server's version. |
| metadata | {"short-description":"Initialize/migrate a repo into ai-memory"} |
aim-init
Wire a repo into ai-memory so its sessions auto-capture and the agent recalls
durable knowledge. Works greenfield or brownfield (incl. migrating off the old
qmd/wiki/ stack).
ai-memory replaces the qmd wiki/ + local-index stack: knowledge lives server-side
in the ai-memory instance (recalled via the MCP), not in a per-repo wiki/ folder.
Full usage model in references/usage.md.
Route intent
- "como está?", "preciso migrar?", "doctor" → run doctor (read-only diagnosis).
- "configura ai-memory aqui", "liga a captura", "init" → install (greenfield).
- "migra do qmd", "tira o qmd e põe ai-memory" → migrate (brownfield).
- "atualiza/pareia o CLI", "refresca os hooks", "tá numa versão velha", "upgrade" → refresh
(detect the environment + bring the local CLI and hooks to parity with the server — no
marker/snippet/MCP changes).
Before writing anything, confirm the three routing parameters with the user — there can be
multiple ai-memory instances (e.g. a personal memory.example.dev, a separate org
instance), so never hardcode the endpoint:
- MCP endpoint — the ai-memory instance URL (e.g.
https://memory.example.dev/mcp) and
the server name to register it under (e.g. memory-personal, memory-acme). Ask which
instance this repo should talk to.
- Workspace — personal →
default; org repo → its own workspace (e.g. acmecorp).
- Project — defaults to the repo basename; let the user override.
The endpoint chosen here is what aim-query / aim-write will target later (by server name).
CLI prerequisite (no Docker wrapper by default)
aim-init needs the ai-memory CLI to install MCP entries, install hooks, and refresh the
routing snippet. Prefer a native CLI binary over the Docker wrapper:
- If
ai-memory is already on PATH, run ai-memory --version and compare it to the latest
upstream release (gh release view -R akitaonrails/ai-memory --json tagName -q .tagName, or
the releases/latest page). Keep it only if it's at a recent release — a CLI behind it misses
newer subcommands (e.g. auto-improve, pending-writes, curator) and hook fixes, so upgrade
it (see refresh). The server's exact .version lives at GET /admin/status, which needs
the admin token (a user's OIDC/JWT gets 401 there) — so the latest release is the practical
parity target; the connected MCP client also learns the server version from its initialize
handshake.
- If it is missing or stale, check the latest upstream release first:
https://github.com/akitaonrails/ai-memory/releases/latest.
- Linux: download the matching
ai-memory-linux-<arch>.tar.gz asset, verify its .sha256,
and install the binary into ~/.local/bin/ai-memory (or another user-owned PATH dir).
- Windows: download
ai-memory-windows-x86_64.zip, verify its .sha256, and follow the
bundled Windows docs.
- macOS/Darwin: download the matching
ai-memory-macos-<arch>.tar.gz asset
(ai-memory-macos-aarch64.tar.gz on Apple Silicon, -x86_64 on Intel), verify its
.sha256, and install the binary into ~/.local/bin/ai-memory. Fallback only if no asset
for your arch: cargo install --git https://github.com/akitaonrails/ai-memory --tag <latest-tag>.
- Use the Docker wrapper (
bin/ai-memory or the quick-start wrapper from GitHub) only when the
user explicitly wants Docker. Do not suggest it as the default local CLI on macOS or when the
user asks for a non-Docker setup.
After installing, verify:
command -v ai-memory
ai-memory --version
AI_MEMORY_SERVER_URL=https://memory.example.dev AI_MEMORY_AUTH_TOKEN=<token-or-env> ai-memory status --json
What a wired repo has
-
.ai-memory.toml at the repo root — stable, non-secret routing for captures + recall
to a workspace/project, and (optionally) the Keycloak/OIDC instance this repo onboards onto.
Tracking policy is contextual, not automatic:
- Private/team repo with shared routing: commit it so every developer gets the same
workspace/project and onboarding profile on clone. These values are instance config, NOT
credentials, and normally do not change after setup.
- Public repo, personal fork, or per-operator routing: keep it local via the repo-local
.git/info/exclude.
Do not add it to the global core.excludesfile, and do not run
git rm --cached .ai-memory.toml unless the user explicitly chooses local-only routing.
workspace = "default"
project = "<repo-name>"
[instance]
issuer = "https://kc.example/auth/realms/<realm>"
server_url = "https://memory.example.dev/wiki"
client_id = "ai-memory-cli"
agents = ["claude-code"]
-
Routing snippet in CLAUDE.md and AGENTS.md — the <!-- ai-memory:start -->…end -->
block. It drives proactive recall mid-session. The block's text is owned by the
ai-memory binary, not by this skill — obtain the current canonical block from the
source and write it between the markers; do not hand-maintain a copy here (a copy
drifts from the binary):
- Agent:
memory_install_self_routing (returns the block for your Write/Edit tool).
- CLI:
ai-memory install-instructions (writes CLAUDE.md; --target AGENTS.md).
The canonical block is already generic (no workspace/project/server names, no page
paths — the .ai-memory.toml marker scopes recall and the MCP auto-scopes), so it is safe
to commit even in a public repo. Whatever you do, never expand the committed block into a
"where things live" map that enumerates the knowledge base — that leaks the project's
internal information architecture. Cross-scope / shared-rules wiring belongs in
operator-global config (~/.claude/CLAUDE.md), not the per-repo block.
-
MCP server entry in .mcp.json (Claude), opencode.json (OpenCode), .codex/config.toml
(Codex) — points at the ai-memory instance (template: templates/mcp-entry.json.tmpl).
Prefer the CLI installer so each agent gets its native global config:
ai-memory install-mcp --client codex --server-url https://memory.example.dev/mcp --name memory-personal --apply
ai-memory install-mcp --client open-code --server-url https://memory.example.dev/mcp --name memory-personal --apply
ai-memory install-mcp --client claude-code --server-url https://memory.example.dev/mcp --name memory-personal --apply
MCP definitions are stable repo config when they contain only server names/URLs and no
credentials. A private/team repo MAY commit .mcp.json so every agent discovers the same
MCP endpoints on clone. Keep personal endpoints or auth-bearing entries (for example
Authorization headers, bearer tokens, or per-user secrets) in the agent's global/user
config, or keep a local-only .mcp.json via .git/info/exclude.
Do not add .mcp.json to the global core.excludesfile, and do not untrack an
existing .mcp.json automatically. Only run git rm --cached .mcp.json after the user
explicitly chooses local-only MCP config.
-
Auto-capture hooks are global (Claude settings, Codex ~/.codex/hooks.json,
OpenCode plugin config, plus staged hook scripts under the platform data dir:
~/.local/share/ai-memory/hooks/ on Linux,
~/Library/Application Support/ai-memory/hooks/ on macOS, and
%LOCALAPPDATA%\ai-memory\hooks\ on Windows). They are marker-gated: they fire in any
repo that has .ai-memory.toml. A repo needs no per-repo hook scripts — just the marker.
Install or refresh them per agent with the capture base URL, not the /mcp URL.
Hook auth — pick the mode that matches the server. This is the #1 cause of silent
capture failure: the wrong mode 401s on every drain, the spool fills, captures are lost.
There are three modes:
- Open hook routes (no auth on
/hook+/handoff) → install with no --auth-token.
- Static bearer — the server accepts a shared/per-user token on
/hook+/handoff
(e.g. AI_MEMORY_AUTH_TOKEN on a single-tenant engine, or a token from
ai-memory user add) → pass it via --auth-token. Embedded in each agent's hook config,
so treat that file as sensitive (chmod 600).
- OIDC / Keycloak gateway — a forwardAuth sidecar (
mcp-auth) that validates a JWT
and answers 401 WWW-Authenticate: Bearer resource_metadata=… (RFC 9728). A static hex
token is rejected (401) here — the gateway only accepts a Keycloak JWT, and the
engine's upstream bearer is proxy-injected, not client-sendable. Use a per-developer
OIDC device token: run ai-memory auth login oidc-device --issuer <oidc-issuer> --client-id <public-device-client> once (browser approval), then install hooks
without --auth-token — the hook's resolve_bearer loads the token from auth.json
and refreshes it headlessly. Full recipe in "Second / custom instance" below.
Quick test of which mode a server is in: curl -sI <server>/hook → 401 with a
WWW-Authenticate: Bearer resource_metadata= header means OIDC/Keycloak (device flow);
a plain 401/200 without that header means static-bearer/open.
Add --hooks-dir <ai-memory/hooks> when the binary can't locate its vendored scripts
(e.g. a cargo install build):
TOKEN=<HOOK_AUTH_TOKEN>
ai-memory install-hooks --apply --agent claude-code --server-url https://memory.example.dev --auth-token "$TOKEN"
ai-memory install-hooks --apply --agent claude-code --server-url https://memory.example.dev
ai-memory install-hooks --apply --agent codex --server-url https://memory.example.dev
ai-memory install-hooks --apply --agent open-code --server-url https://memory.example.dev
Modern install-hooks wires native hooks (ai-memory hook --event … calling the binary
directly) instead of shell scripts — the binary emits the correct per-agent stdout contract
(e.g. Claude Code's hookSpecificOutput.additionalContext JSON wrapper for handoff
injection), so there are no hand-patched hook scripts to drift. Do not hand-maintain a
custom _lib.sh overlay for auth (e.g. a Keycloak token-mint prepend) — the supported paths
are --auth-token (static-bearer servers) or auth login oidc-device + no---auth-token
(OIDC/Keycloak servers). The device token is not minted per event: it's stored once in
auth.json and refreshed headlessly at drain time.
On Codex, confirm ~/.codex/hooks.json contains the ai-memory lifecycle hooks and trust the
new hook commands when Codex prompts on the next start. Grok Build CLI (~/.grok/hooks/*.json)
is not yet a supported --agent — integrate manually, and note that its SessionStart ignores
hook stdout (no handoff injection; use the MCP memory_handoff_accept instead). (Legacy qmd
repos have per-repo wiki-reindex hooks; migration removes them.)
Hook config — per-platform paths + delivery model (macOS / Linux / Windows)
install-hooks and auth login oidc-device both default to the platform data dir
(dirs::data_local_dir()), where auth.json, the hook-spool/, and the staged hook scripts
live — so the OIDC device token lands exactly where the hooks look for it. Same flow on all
three OSes; only the paths differ:
| OS | data dir (auth.json, hook-spool/, hooks/) | Claude Code settings |
|---|
| macOS | ~/Library/Application Support/ai-memory | ~/.claude/settings.json |
| Linux | ~/.local/share/ai-memory | ~/.claude/settings.json |
| Windows | %LOCALAPPDATA%\ai-memory | %USERPROFILE%\.claude\settings.json |
Hooks are native (ai-memory hook --event … calls the binary directly — no shell spawn;
Windows uses the WindowsNative config with the same spool + OIDC-token fallback). Codex →
~/.codex/hooks.json; OpenCode → its plugin config. If the binary can't find its vendored
scripts (e.g. a cargo install build), pass --hooks-dir <data_dir>/hooks. auth.json is
sensitive → chmod 600 on POSIX (the CLI writes it that way).
Delivery is batched at session boundaries, not per event. Per-event hooks
(user-prompt-submit, pre/post-tool-use, pre-compact, stop) only enqueue to
<data_dir>/hook-spool (instant, fire-and-forget). The spool is drained on
session-start (clears prior-session backlog) and session-end (the main delivery
point). So "captures don't show up immediately" is normal — they flush at the next boundary.
resolve_bearer at drain time: explicit --auth-token wins (static); else the OIDC token
from auth.json (refreshed if near expiry); else anonymous. The spool drops entries after 8
failed attempts or 7 days, capped at 10000 files. Symptom of a wrong auth mode:
hook-spool filling toward 10000 with auth_mode: static entries that 401 forever (a static
token against a Keycloak gateway) → switch to device flow and clear the dead backlog.
Token lifetime (device flow): access token auto-refreshes; the refresh token lasts as long as
the issuer's offline-session idle window — after long inactivity, re-run auth login oidc-device. Verify state any time with ai-memory auth status.
Keycloak-gated instance — the agent drives onboarding (mac / Linux / Windows)
The developer should not hand-run setup scripts. When a dev needs hook
capture on a Keycloak/OIDC instance, you (the agent) drive it: gather the
inputs, check/create the device client, run the per-machine setup, and verify.
The dev only answers a couple of questions and approves a browser prompt once.
The scripts/ are your canonical, cross-platform command reference
(*.sh for macOS/Linux, *.ps1 for Windows) — invoke them, or run the
equivalent ai-memory commands directly.
Inputs you need — read them from the repo's .ai-memory.toml [instance] block
first (issuer / server_url / client_id / agents, see "What a wired repo has");
only ask the dev (or fall back to operator-global config) when that block is absent:
ISSUER — realm URL, e.g. https://kc.example/auth/realms/<realm>.
SERVER_URL — ai-memory server URL including any base path, e.g.
https://memory.example.dev or https://memory.example.dev/wiki.
CLIENT_ID — the device-flow client (default ai-memory-cli).
- which
AGENTS the dev uses (claude-code, codex, open-code, …).
When the [instance] block is present (e.g. committed in a private team repo), the agent
uses it and does NOT ask — git clone + aim-init is turnkey for the next developer.
1. Check the device-flow client exists (read-only):
GET <ISSUER>/.well-known/openid-configuration (confirm device_authorization_endpoint),
then POST client_id=<CLIENT_ID>&scope=openid to that endpoint. A device_code
→ ready. invalid_client (no such client) or unauthorized_client … device flow is disabled → the realm needs the client (step 2). The browser/MCP client is
PKCE-enforced and 400s with Missing parameter: code_challenge_method, so it
can't be reused — that's why a dedicated client exists.
2. Create the client IF missing — one-time per realm, needs Keycloak admin.
If you have Keycloak admin access, use scripts/kc-create-device-client.sh
(idempotent; public, device-only, no PKCE). For a containerized Keycloak run it
INSIDE the pod (it runs sh there regardless of your OS), reusing the pod's own
admin env so no secret leaves the cluster:
kubectl exec -i <keycloak-pod> -- sh -c \
'CLIENT_ID=ai-memory-cli KC_SERVER=http://localhost:8080/auth KC_REALM=<realm> \
KC_ADMIN_USER="$KEYCLOAK_ADMIN" KC_ADMIN_PASS="$KEYCLOAK_ADMIN_PASSWORD" sh -s' \
< scripts/kc-create-device-client.sh
Adjust KC_SERVER to …:8080 vs …:8080/auth per the realm's
KC_HTTP_RELATIVE_PATH (a 404 Not Found on login means flip it). If you lack
admin access, ask the operator to run it once, then resume.
3. Run the per-machine setup yourself — don't make the dev run a script. Per
the dev's OS:
- macOS/Linux:
ISSUER=… SERVER_URL=… CLIENT_ID=… AGENTS="…" sh scripts/dev-setup-hooks.sh
- Windows:
pwsh scripts/dev-setup-hooks.ps1 -Issuer … -ServerUrl … -Agents "…"
- Either way it runs
ai-memory auth login oidc-device --issuer <ISSUER> --client-id <CLIENT_ID> (the dev approves the printed URL in their browser
ONCE) then ai-memory install-hooks --apply --agent <each> --server-url <SERVER_URL> (NO --auth-token).
4. Verify: ai-memory auth status → oidc-device: logged in. The dev needs
the realm role the server checks (mcp:read/mcp:write) — the same one MCP
login already requires.
Record each instance's ISSUER/SERVER_URL/CLIENT_ID in operator-global
config (~/.claude/CLAUDE.md or a private runbook), NOT in this skill — the
scripts stay generic; these endpoints are operator/instance-specific.
Second / custom instance (e.g. a client Keycloak instance)
Some repos talk to two ai-memory instances — a personal one and a client/org one
that authenticates with OAuth/OIDC (Keycloak, RFC 9728) rather than (or in addition to) a
static bearer. Both are read + write; keep them in sync, with the personal instance as the
superset (it accumulates everything any custom instance has).
- Hook capture works against an OAuth/Keycloak instance too. A hook is headless and cannot run
an interactive OAuth PKCE flow per event, so it uses a stored OIDC device-flow token: run
ai-memory auth login oidc-device --issuer <oidc-issuer> --client-id <public-device-client>
once (browser approval), and the hook (resolve_bearer) loads it from auth.json and refreshes
it headlessly. Then install-hooks without --auth-token.
- A forwardAuth-JWT-only gateway rejects static bearers. If the gateway answers
401 WWW-Authenticate: Bearer resource_metadata=… (RFC 9728), a static hex token gets 401 —
the upstream bearer is proxy-injected, not client-sendable — so device flow is the only
headless option there. Only pass --auth-token when the instance genuinely accepts a static
HOOK_AUTH_TOKEN on /hook.
- Device-flow client requirements (issuer side): a public client with the
device-authorization grant enabled and no PKCE enforcement (PKCE has no place in the
device grant — there's no redirect; keep the browser/DCR/MCP client, which does enforce PKCE,
as a separate client so you don't have to weaken it). The user needs the realm role the
server checks (e.g.
mcp:read/mcp:write) — those ride into the JWT via the realm-role mapper,
not via a scope.
- Dual-capture: global hooks → personal instance; project-level hooks
(
.claude/settings.local.json) → the client instance. Each leg uses that instance's auth
mode (static bearer or OIDC device token — they're independent). With both wired, every lifecycle
event captures to both. (Or capture to one and reconcile via sync.)
- Dual-write durable pages: a
memory_write_page in a two-instance project goes to both
MCPs, same (workspace, project).
- Pass explicit
workspace+project on recall/write to the client instance — with per_actor
- the scope-bleed fail-closed fix, an un-scoped call lands on that instance's baked default. The
.ai-memory.toml marker gives the canonical scope.
- Keep in sync (bidirectional, on-demand): personal ⊇ everything; reconcile drift both ways
preserving
(workspace, project).
This operator-specific dual-instance wiring belongs in operator-global config
(~/.claude/CLAUDE.md or a shared memory rule), not the committed per-repo CLAUDE.md
ai-memory block — that block is generic + binary-owned, and in a shared/client repo it would
leak the dual-instance/capture setup and the project's information architecture.
doctor (read-only)
Report, without writing:
- Is the
ai-memory CLI available on PATH? What version? If missing/stale, report the native
install path from the latest upstream release — download the ai-memory-<os>-<arch>.tar.gz
asset (macOS ships ai-memory-macos-aarch64/-x86_64; Linux ai-memory-linux-aarch64/
-x86_64), verify its .sha256; cargo install only if no asset for your arch. Do not
fall back to the Docker wrapper unless the user wants Docker.
- Can the CLI reach the chosen server (
ai-memory status --json with the right
AI_MEMORY_SERVER_URL / auth env or flags)?
- CLI version parity. Compare
ai-memory --version (local) to the latest upstream release
(gh release view -R akitaonrails/ai-memory --json tagName -q .tagName). Flag a CLI behind it —
it lacks newer subcommands (auto-improve, pending-writes, curator) and hook fixes — and
recommend refresh. (The server's exact .version is admin-gated at /admin/status: a user's
OIDC/JWT gets 401, so don't rely on ai-memory status against the server for it; the latest
release is the checkable target, and the MCP initialize handshake also carries it.)
- Is there a
.ai-memory.toml? What workspace/project? Is its tracking policy intentional
(committed for shared stable routing, or ignored local-only)?
- Is the routing snippet present in CLAUDE.md / AGENTS.md?
- Does the snippet teach the search strategy? Flag a stale snippet that lacks the
"broaden when the current project misses" guidance — i.e. no mention of
scopes /
sibling projects, or no warning that memory_query returns snippets (not full page
bodies). Re-installing the current template (below) backfills it. This matters because
memory_query has no global search: an agent that searches only the current project
and stops will miss cross-cutting knowledge that lives in a sibling (infra/ops) project.
- Is an ai-memory MCP entry present? Check the agent's global config FIRST — the MCP is
usually installed there (
~/.claude.json top-level mcpServers, e.g. memory-personal;
~/.codex/config.toml; the OpenCode global config), then the per-repo .mcp.json /
opencode.json. Don't conclude "no recall" from an empty per-repo .mcp.json — a global
entry applies to every repo. If a per-repo .mcp.json IS used, inspect whether it contains
only stable non-secret definitions or auth material, then confirm its tracking policy matches
the repo's convention; tracked .mcp.json is valid for private/team repos with shared MCPs.
- For Codex, are global ai-memory hooks present in
~/.codex/hooks.json (SessionStart,
UserPromptSubmit, PreToolUse, PostToolUse, PreCompact, Stop) and do the staged
scripts exist under the platform ai-memory hooks dir?
- Are the hooks native + authenticated? Flag any agent still calling hand-patched shell
scripts under a custom overlay (e.g.
~/.config/ai-memory/hooks/ sourcing a Keycloak-mint
_lib.sh) instead of the binary's ai-memory hook --event … form — those drift and miss
upstream fixes (e.g. the hookSpecificOutput JSON wrapper + tab-escape fix that handoff
injection depends on). If the server gates /hook//handoff, confirm the hook carries the
right credential for that server's mode (otherwise captures 401 silently — the CLI
spools locally and returns {} regardless): a static-bearer server needs --auth-token;
an OIDC/Keycloak gateway (401 WWW-Authenticate: Bearer resource_metadata=…) needs a
device-flow token (auth login oidc-device) and the hooks installed without --auth-token
— a static token is rejected there. Re-run install-hooks --apply (with or without
--auth-token per mode) to migrate and remove any overlay dependency.
hook-spool at/near the 10000 cap — two DISTINCT causes; read auth_mode / attempts /
last_error on a few entries to tell them apart:
- Wrong auth (fixable):
auth_mode: static against a Keycloak/JWT gateway → attempts climb,
last_error shows 401, entries drop after 8 tries. Fix: device flow (auth login oidc-device)
install-hooks --apply without --auth-token, and clear the dead backlog.
- Drain cadence (NOT an auth bug):
auth_mode: oidc (correct), attempts: 0,
last_error: null — delivery works, but drains only fire at session-start / session-end, so
a marathon session exceeds MAX_SPOOL_FILES (10000) before it ends and the oldest are
evicted. Mitigate by draining at a boundary (start a fresh session, or run the session-start
hook manually to drain) or shorter sessions; the durable fix is mid-session incremental
draining (upstream — track the hook-spool delivery-reliability work). The spool is shared
across all agents on the same data dir, so parallel long sessions accumulate together.
- MCP auth (DCR): if an agent's MCP login shows Keycloak "Client not found", its cached
Dynamic-Client-Registration id was orphaned (realm recreated/migrated). Clear the stale entry
from the agent's MCP-auth cache (e.g. OpenCode's
~/.local/share/opencode/mcp-auth.json) and
re-auth to force a fresh DCR.
- MCP OAuth discovery failure: if a client's
reauth errors Could not discover OAuth endpoints from server response (it doesn't chase the RFC 9728 → 8414 discovery — seen with OMP's native MCP client), bridge the remote as a stdio server via npx -y mcp-remote <url> <port> in that client's own (git-excluded) config, keeping the committed .mcp.json portable type: http. If discovery+authorization then pass but the token POST 404s, the SDK looked up auth-server metadata at the domain root and dropped a path-based issuer's /<base>/realms/<realm> — a native client (Claude Code) connects via issuer-relative OIDC, so it's not a dead endpoint and not fixable by mcp-remote version/flags; fix with a server-side root .well-known/oauth-authorization-server (+ openid-configuration) alias to the realm, else use the native client. Full recipe: references/usage.md §MCP OAuth compatibility.
- Two-instance (e.g. client Keycloak) repo? If the repo registers a second MCP (a client/org
instance), confirm: both are treated as read+write with the personal instance as the
superset; durable writes are dual-written; if auto-capture to the client instance is wanted,
project-level hooks point at it with a static
HOOK_AUTH_TOKEN or a stored OIDC device-flow
token (ai-memory auth login oidc-device … → resolve_bearer refreshes headlessly) — capture is
feasible, not "impossible"; recall/write pass explicit workspace+project (per_actor
fail-closed). Flag any committed CLAUDE.md that hard-codes this dual-instance/capture wiring —
it belongs in operator-global config, not the shared per-repo block.
- Legacy qmd? Flag any of: a
qmd MCP entry, a wiki/ dir with CONVENTIONS.md, per-repo
*/hooks/wiki-reindex.sh, a "Wiki (wiki/)" block in CLAUDE.md/AGENTS.md, .wiki-guardrails.yml.
install (greenfield)
- Confirm workspace + project with the user.
- Ensure the native
ai-memory CLI is installed and functional (see "CLI prerequisite" above).
Install/upgrade from upstream release assets (macOS ships ai-memory-macos-<arch>.tar.gz,
Linux ai-memory-linux-<arch>.tar.gz; verify the .sha256); cargo install/source build
only if no asset for your arch. Avoid the Docker wrapper unless explicitly requested.
- Decide
.ai-memory.toml tracking policy from the repo context and user input. For a
private/team repo with stable shared routing, write it and leave it tracked/committable.
For public repos, personal forks, or per-operator routing, add it to the repo-local
.git/info/exclude. Do not use the global core.excludesfile, and do not untrack an
existing marker unless the user explicitly chooses local-only routing.
- Obtain the canonical routing block from the binary (
memory_install_self_routing, or
ai-memory install-instructions) and write it into CLAUDE.md and AGENTS.md (idempotent
— between the <!-- ai-memory:start -->/<!-- ai-memory:end --> markers; replace if
present). Don't paste a hand-maintained copy.
- Add the ai-memory MCP entry to the agent configs the repo uses. If the entry is stable and
contains no credentials, it may live in a tracked per-repo
.mcp.json; if it is personal or
auth-bearing, install it in the agent's global/user config or keep it local-only via
.git/info/exclude. Do not run git rm --cached .mcp.json unless the user explicitly asks
for local-only MCP config.
For Codex, prefer ai-memory install-mcp --client codex ... --apply so the server lands in
~/.codex/config.toml.
- Ensure global auto-capture hooks are installed for the active agents. For Codex, run
ai-memory install-hooks --agent codex --server-url https://memory.example.dev --apply and verify
~/.codex/hooks.json plus the staged ai-memory/hooks/codex scripts.
- Tell the user auto-capture is now live (global hooks + the new marker); recall is via the
session-start handoff + the routing snippet.
migrate (brownfield: qmd → ai-memory)
Run doctor first; then, with the user's confirmation:
- Marker — write
.ai-memory.toml (workspace/project) and preserve/choose the tracking
policy: commit stable shared routing in private/team repos; keep personal/public routing
local-only via .git/info/exclude.
- MCP — in
.mcp.json / opencode.json / .codex/config.toml, replace the qmd
server entry with the ai-memory entry. Preserve the repo's existing tracking policy; commit
stable non-secret server definitions when the repo intentionally shares MCPs, and keep
auth-bearing or personal endpoints local/global.
- CLAUDE.md / AGENTS.md — replace the "Wiki (
wiki/)" / qmd-MCP block with the routing
snippet. Drop instructions that tell the agent to query qmd or maintain wiki/.
- Remove ALL qmd-era artifacts. The old qmd/wiki setup installs more than hooks —
enumerate every one and remove it:
.wiki-guardrails.yml (guardrails config).
- Hooks —
.claude/hooks/wiki-*.sh (policy-check, reindex, drift-audit, suggest-ingest);
.codex/hooks/wiki-*.sh (policy-check, reindex, drift-audit, consider) + .codex/hooks.json;
.opencode/hooks/wiki-*.sh + .opencode/plugins/wiki-guardrails.js. Remove the matching
hook entries from .claude/settings.json, and [features] codex_hooks = true from
.codex/config.toml (it only existed to enable the codex wiki hooks).
opencode.json permission.skill."wiki-*" (swap to "aim-*" or drop).
- ANTIGRAVITY (if the repo uses it) — the managed instruction block + any
.antigravity*
hooks/config.
- Auto-capture now comes from the global ai-memory hooks — no per-repo hook scripts needed.
- Global QMD checkout/wrapper (operator-level, outside the repo) — the per-project wrapper
(
~/.local/share/skills/qmd/wrappers/<project>-qmd, or the legacy
~/.local/share/essential-skills/qmd/...) and the managed qmd checkout/cache are now
orphaned. Remove the wrapper; remove the shared checkout only if no other repo still uses qmd.
wiki/ content — leave it in place as history by default. If the user wants it in
ai-memory, ingest the markdown into the chosen workspace/project (one page per file,
redact any literal secrets) and then they can remove wiki/. Do not delete wiki/
without explicit confirmation.
- Re-run doctor to confirm: marker present + tracking policy intentional, snippet in
CLAUDE/AGENTS, ai-memory MCP wired with no committed secrets, and no qmd remnants (no
.wiki-guardrails.yml, no wiki-* hooks/plugins, no qmd MCP entry, no codex_hooks/
wiki-* permission leftovers).
refresh (upgrade CLI + re-stage hooks to parity)
For an already-wired environment that drifted from the server — CLI behind, or after the
server upgrades. No marker/snippet/MCP changes; detect what's installed and bring it to parity:
- Detect the current state (read-only): local
ai-memory --version vs the latest upstream
release (gh release view -R akitaonrails/ai-memory --json tagName -q .tagName; the server's
own .version is admin-gated at /admin/status → 401 for a user, so the release is the
checkable target); which agents have ai-memory hooks — ai-memory install-hooks --help lists the
canonical supported agents (don't hardcode a stale subset): claude-code
(~/.claude/settings.json), codex (~/.codex/hooks.json), gemini-cli (~/.gemini/settings.json),
antigravity-cli / agy (~/.gemini/config/hooks.json), grok (~/.grok/hooks/ai-memory.json),
cursor (~/.cursor/hooks.json), open-code (~/.config/opencode/plugins/), omp
(~/.omp/agent/extensions/), openclaw; and ai-memory auth status (is the OIDC device token valid?).
- Upgrade the CLI if behind the latest release (see "CLI prerequisite"): download the
ai-memory-<os>-<arch>.tar.gz asset, verify its .sha256, back up the current binary, and
install over ~/.local/bin/ai-memory. Confirm ai-memory --version is now ≥ the server's.
- Re-stage hooks for every detected agent:
ai-memory install-hooks --apply --agent <agent> --server-url <capture-url> (OIDC/Keycloak server → no --auth-token; static-bearer →
--auth-token). It re-verifies the staged scripts and refreshes the agent config (writes a
backup). Native hooks (ai-memory hook --event …) pick up the new binary automatically; this
refreshes the config + staged fallback scripts and drops any legacy _lib.sh overlay.
- Verify:
ai-memory --version matches the server; ai-memory auth status → logged in; the
agent configs still call ai-memory hook --event natively; <data_dir>/hook-spool is well
below the 10000 cap.
refresh does not touch the marker, routing snippet, or MCP entry (version-agnostic, and the
snippet is binary-owned) — run install / migrate for those. Hooks are global (one data
dir for all agents), so refreshing once updates every wired repo, not just the current one.
Verify
git check-ignore .ai-memory.toml → ignored (not committed).
- Routing snippet present once (between the markers) in CLAUDE.md + AGENTS.md.
- No
qmd MCP entry / wiki-reindex hooks remain (for migrate).
- A capture round-trips: a real agent session in the repo lands a page under the chosen
workspace/project (check via
aim-status / memory_status or /api/v1/projects).
Multi-user mode (optional)
The instructions above cover the single-operator case (one human, one ai-memory instance).
When multiple humans share an instance — or one operator wants strict per-repo
isolation between several parallel Claude Code windows — switch the engine to multi-user
mode and onboard each user explicitly. The ai-memory CLI already ships the workflow.
Pre-req: the engine must have [auth].token_pepper set (auto-generated by
ai-memory init; multi-user admin endpoints 503 otherwise). Confirm with
curl <endpoint>/admin/status or ai-memory status.
Per-user setup (canonical: one person, one identity)
-
Create the user on the engine (root token required). Token is printed exactly once:
ai-memory user add --username alice --email alice@home
Lifecycle subcommands: user list (no tokens surfaced), user expire/revive,
user rotate-token (issues fresh plaintext once).
-
Install hooks stamped with that user's bearer — one command per agent CLI the
person uses (claude-code, codex, cursor, gemini-cli, open-code, omp,
openclaw, antigravity-cli):
ai-memory install-hooks --apply \
--agent codex \
--as-user alice \
--auth-token <alice-token>
Use --agent claude-code or --agent open-code for those harnesses.
--apply mutates the agent config in place (idempotent; writes a timestamped backup).
Defaults to the global hook config (~/.claude/settings.json, ~/.codex/hooks.json,
OpenCode plugin config, …).
Pass --config-file ./.claude/settings.json to target project-level config instead
(see "tokens per context" below).
-
Wire the bearer into the MCP entry so MCP tool calls authenticate as the same
user the hooks do:
{
"mcpServers": {
"memory-personal": {
"type": "http",
"url": "https://memory.example.dev/mcp",
"httpHeaders": { "Authorization": "Bearer <alice-token>" }
}
}
}
ai-memory install-mcp --apply --auth-token <alice-token> writes this entry for you.
-
Run the engine in per_actor mode (chart: aiMemory.autoScope.mode: per_actor).
The active-project map keys by (user, session_id) with a user-only fallback —
Alice never inherits Bob's last project on a session-less probe, and writes from Alice
never publish to Bob's slot.
Tokens per context (parallel-session isolation without agent hooks)
Same person, two Claude Code windows open in different repos at the same time → MCP tool
calls don't forward the hook's session_id, so the engine collapses both windows onto
one user-only slot. Workaround: treat each repo as its own logical user.
ai-memory user add --username alice-foo --email alice@foo.local
ai-memory user add --username alice-bar --email alice@bar.local
In each repo's project-level .claude/settings.json (Claude Code merges project-level
over global), pin a distinct token:
{
"mcpServers": {
"memory-personal": {
"type": "http",
"url": "https://memory.example.dev/mcp",
"httpHeaders": { "Authorization": "Bearer <T-foo>" }
}
}
}
Install hooks scoped the same way:
cd ~/repo-foo && ai-memory install-hooks --apply \
--as-user alice-foo --auth-token T-foo \
--config-file ./.claude/settings.json
Now the two windows authenticate as distinct users to the engine; per_actor keying
isolates their slots automatically. Operational cost: N tokens per real person × M
repos that need isolation. The trade-off is acceptable when you really need parallel
isolation; otherwise the per-user setup above is enough.
Server-side scope-guard (defense in depth)
The engine ships an admission webhook chain. The ops chart includes an optional
scope-guard webhook that enforces per-user write boundaries — an ACL of
user → [(workspace pattern, project pattern), …]. Even if a token is pasted into the
wrong .ai-memory.toml or MCP config, the engine rejects the write at admission time:
webhooks:
scopeGuard:
enabled: true
rules:
alice: [{ workspace: "alice|shared", project: ".*" }]
client-x: [{ workspace: "client-x|shared", project: ".*" }]
Enabling scope-guard ⇒ the write's actor must be non-empty, or the ACL rejects
everything. The ACL keys on ActorContext.user. An OIDC client carries it in the
JWT (preferred_username), so multi-user instances match rules naturally. But a client
that authenticates hooks with the static HOOK_AUTH_TOKEN lands with an empty
actor unless you also set mcpAuth.hookAuth.username (→ HOOK_AUTH_USERNAME, the
username mcp-auth stamps on hook-token requests) to a user the ACL allows. Miss it and
every static-token hook capture is silently 403'd at admission — data loss visible
only in the engine logs (scope-guard: user '' not allowed to write_page …). Note
aiMemory.rootUsername does not cover this: it stamps the rung-1/MCP path, not the
hook path. So a single-operator static-token instance must set
mcpAuth.hookAuth.username (= the ACL user) when it enables scopeGuard; multi-user
instances rely on OIDC instead (never a shared hook username).
Reads are NOT gated — the engine has no read-side admission chain. For real read privacy
(adversarial users), run separate ai-memory instances per trust boundary.
Doctor check for multi-user
In addition to the single-user checks above, doctor for a multi-user install should
report:
[auth].token_pepper is set on the engine
ai-memory user list returns the expected users
- The agent MCP config (
~/.claude/settings.json, ~/.codex/config.toml, OpenCode config,
or project-level equivalent) has Authorization: Bearer …
- The active hook config is stamped with
--as-user (every hook call carries the user's
bearer, not the root token)
- If autoscope is
per_session and the MCP client doesn't forward session_id, warn
that the mode is silently degrading to single slot and recommend per_actor instead
See references/usage.md for the recall model (handoff per project,
auto-capture, the MCP tools) and aim-query / aim-write
for manual recall/write.