About aim-init
aim-init is an agent skill for wiring a repository to an ai-memory instance, for teams and developers setting up or updating project memory. It guides diagnosis, installation, migration from qmd, and refresh of local CLI and hooks.
Key features
- 01Read-only setup diagnosis
- 02Greenfield ai-memory setup
- 03Migration from qmd
- 04Refresh CLI and hooks
- 05Configure project routing
Use cases
- 01Check whether a repository needs migration
- 02Enable automatic session capture
- 03Move a qmd repository to ai-memory
- 04Update local CLI and agent hooks
MCP support
The skill configures MCP server entries for supported clients to connect to an ai-memory instance. It also relies on ai-memory MCP for scoped recall.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ djalmajr/ skills --skill aim-initAfter installing, type/aim-initin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/usage.mdscripts/dev-setup-hooks.ps1dev-setup-hooks.shkc-create-device-client.shtemplates/ai-memory.toml.exampleai-memory.toml.tmplmcp-entry.json.tmplrouting-snippet.md.tmplDetails
From the project READMEaim-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-repowiki/folder. Full usage model in references/usage.md. Server side (install a new instance, upgrade the engine, providers, consumer keys, backup/restore) is aim-ops — this skill only wires clients to it.
Route intent
- "how's it looking?", "do I need to migrate?", "doctor" (also PT: "como está?", "preciso migrar?") → run doctor (read-only diagnosis).
- "set up ai-memory here", "turn on capture", "init" (also PT: "configura ai-memory aqui", "liga a captura") → install (greenfield).
- "migrate off qmd", "drop qmd and use ai-memory" (also PT: "migra do qmd", "tira o qmd e põe ai-memory") → migrate (brownfield).
- "update/pair the CLI", "refresh the hooks", "it's on an old version", "upgrade" (also PT: "atualiza/pareia o CLI", "refresca os hooks", "tá numa versão velha") → 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-memoryis already onPATH, runai-memory --versionand 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.versionlives atGET /admin/status, which needs the admin token (a user's OIDC/JWT gets401there) — so the latest release is the practical parity target; the connected MCP client also learns the server version from itsinitializehandshake. - 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.gzasset, 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.gzasset (ai-memory-macos-aarch64.tar.gzon Apple Silicon,-x86_64on 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>.
- Linux: download the matching
- Use the Docker wrapper (
bin/ai-memoryor 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.tomlat 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 globalcore.excludesfile, and do not rungit rm --cached .ai-memory.tomlunless the user explicitly chooses local-only routing.
workspace = "default" project = "<repo-name>" # Optional routing/policy (defaults shown; uncomment to change — commented = no-op): # project_strategy = "repo-root" # collapse worktrees/subdirs to the git root (default: basename) # drop_subagent_captures = "true" # accept-but-don't-store THIS project's subagent captures (default: off) # Optional: Keycloak/OIDC onboarding profile the agent reads to wire hooks # WITHOUT asking (see "Keycloak-gated instance"). Non-secret. The native hook # reads the routing/policy keys above but NOT this [instance] block, so the # block is inert at runtime — it exists purely so `aim-init` can self-serve # the device-flow setup. [instance] issuer = "https://kc.example/auth/realms/<realm>" server_url = "https://memory.example.dev/wiki" # ai-memory URL incl. base path client_id = "ai-memory-cli" # device-flow client agents = ["claude-code"] # agents to wire hooks for -
Routing snippet in
CLAUDE.mdandAGENTS.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(writesCLAUDE.md;--target AGENTS.md).
The canonical block is already generic (no workspace/project/server names, no page paths — the
.ai-memory.tomlmarker 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. - Agent:
-
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 --applyMCP definitions are stable repo config when they contain only server names/URLs and no credentials. A private/team repo MAY commit
.mcp.jsonso every agent discovers the same MCP endpoints on clone. Keep personal endpoints or auth-bearing entries (for exampleAuthorizationheaders, bearer tokens, or per-user secrets) in the agent's global/user config, or keep a local-only.mcp.jsonvia.git/info/exclude. Do not add.mcp.jsonto the globalcore.excludesfile, and do not untrack an existing.mcp.jsonautomatically. Only rungit rm --cached .mcp.jsonafter 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/mcpURL. 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 four 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_TOKENon a single-tenant engine, or a token fromai-memory user add) → pass it via--auth-token. Embedded in each agent's hook config, so treat that file as sensitive (chmod 600). - Consumer key (
amk_, keys-only gateway) — themcp-authsidecar runs without an identity provider and resolves per-consumer keys locally (KEYS_DB). Each agent/CLI gets its own key (POST /keyswith anadmin-scoped key; issued byaim-ops keys), scopedread,write; pass it via--auth-tokenfor hooks and as the MCPAuthorizationheader. Attribution comes from the key'sactor_user. Expect 403 on/admin/*with a non-admin key — by design, not a server fault. Rotation = issue new + revoke old. - OIDC / Keycloak gateway — a forwardAuth sidecar (
mcp-auth) that validates a JWT and answers401 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: runai-memory auth login oidc-device --issuer <oidc-issuer> --client-id <public-device-client>once (browser approval), then install hooks without--auth-token— the hook'sresolve_bearerloads the token fromauth.jsonand refreshes it headlessly. Full recipe in "Second / custom instance" below.
Quick test of which mode a server is in:
curl -sI <server>/hook→401with aWWW-Authenticate: Bearer resource_metadata=header means OIDC/Keycloak (device flow); a plain401/200without that header means static-bearer/open.GET <server>/keys/whoami→200with{identity, can_issue}means the keys-only gateway is present (consumer keys);404means the sidecar has noKEYS_DB.Add
--hooks-dir <ai-memory/hooks>when the binary can't locate its vendored scripts (e.g. acargo installbuild):# static-bearer server: TOKEN=<HOOK_AUTH_TOKEN> ai-memory install-hooks --apply --agent claude-code --server-url https://memory.example.dev --auth-token "$TOKEN" # OIDC/Keycloak server (after `auth login oidc-device`) OR open server — NO --auth-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.devModern
install-hookswires 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'shookSpecificOutput.additionalContextJSON wrapper for handoff injection), so there are no hand-patched hook scripts to drift. Do not hand-maintain a custom_lib.shoverlay for auth (e.g. a Keycloak token-mint prepend) — the supported paths are--auth-token(static-bearer servers) orauth login oidc-device+ no---auth-token(OIDC/Keycloak servers). The device token is not minted per event: it's stored once inauth.jsonand refreshed headlessly at drain time. On Codex, confirm~/.codex/hooks.jsoncontains 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 itsSessionStartignores hook stdout (no handoff injection; use the MCPmemory_handoff_acceptinstead). (Legacy qmd repos have per-repowiki-reindexhooks; migration removes them.) - Open hook routes (no auth on
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.devorhttps://memory.example.dev/wiki.CLIENT_ID— the device-flow client (defaultai-memory-cli).- which
AGENTSthe 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:
# macOS/Linux/WSL:
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
# Windows PowerShell: Get-Content scripts/kc-create-device-client.sh | kubectl exec -i <pod> -- sh -c '…'
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) thenai-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 fromauth.jsonand refreshes it headlessly. Theninstall-hookswithout--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-tokenwhen the instance genuinely accepts a staticHOOK_AUTH_TOKENon/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.
- A forwardAuth-JWT-only gateway rejects static bearers. If the gateway answers
- 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_pagein a two-instance project goes to both MCPs, same(workspace, project). - Pass explicit
workspace+projecton recall/write to the client instance — withper_actor- the scope-bleed fail-closed fix, an un-scoped call lands on that instance's baked default. The
.ai-memory.tomlmarker gives the canonical scope.
- the scope-bleed fail-closed fix, an un-scoped call lands on that instance's baked default. The
- 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-memoryCLI available onPATH? What version? If missing/stale, report the native install path from the latest upstream release — download theai-memory-<os>-<arch>.tar.gzasset (macOS shipsai-memory-macos-aarch64/-x86_64; Linuxai-memory-linux-aarch64/-x86_64), verify its.sha256;cargo installonly 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 --jsonwith the rightAI_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.versionis admin-gated at/admin/status: a user's OIDC/JWT gets401, so don't rely onai-memory statusagainst the server for it; the latest release is the checkable target, and the MCPinitializehandshake 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 thatmemory_queryreturns snippets (not full page bodies). Re-installing the current template (below) backfills it. This matters becausememory_queryhas 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.jsontop-levelmcpServers, 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.jsonIS 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.jsonis 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'sai-memory hook --event …form — those drift and miss upstream fixes (e.g. thehookSpecificOutputJSON 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-runinstall-hooks --apply(with or without--auth-tokenper mode) to migrate and remove any overlay dependency. hook-spoolat/near the 10000 cap — two DISTINCT causes; readauth_mode/attempts/last_erroron a few entries to tell them apart:- Wrong auth (fixable):
auth_mode: staticagainst a Keycloak/JWT gateway →attemptsclimb,last_errorshows 401, entries drop after 8 tries. Fix: device flow (auth login oidc-device)install-hooks --applywithout--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 atsession-start/session-end, so a marathon session exceedsMAX_SPOOL_FILES(10000) before it ends and the oldest are evicted. Mitigate by draining at a boundary (start a fresh session, or run thesession-starthook 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.
- Wrong auth (fixable):
- 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
reautherrorsCould 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 astdioserver vianpx -y mcp-remote <url> <port>in that client's own (git-excluded) config, keeping the committed.mcp.jsonportabletype: 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 bymcp-remoteversion/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_TOKENor a stored OIDC device-flow token (ai-memory auth login oidc-device …→resolve_bearerrefreshes headlessly) — capture is feasible, not "impossible"; recall/write pass explicitworkspace+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
qmdMCP entry, awiki/dir withCONVENTIONS.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-memoryCLI is installed and functional (see "CLI prerequisite" above). Install/upgrade from upstream release assets (macOS shipsai-memory-macos-<arch>.tar.gz, Linuxai-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.tomltracking 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 globalcore.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, orai-memory install-instructions) and write it intoCLAUDE.mdandAGENTS.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 rungit rm --cached .mcp.jsonunless the user explicitly asks for local-only MCP config. For Codex, preferai-memory install-mcp --client codex ... --applyso 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 --applyand verify~/.codex/hooks.jsonplus the stagedai-memory/hooks/codexscripts. - 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 theqmdserver 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 queryqmdor maintainwiki/. - 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 = truefrom.codex/config.toml(it only existed to enable the codex wiki hooks). opencode.jsonpermission.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 managedqmdcheckout/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 removewiki/. Do not deletewiki/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, nowiki-*hooks/plugins, no qmd MCP entry, nocodex_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 --versionvs the latest upstream release (gh release view -R akitaonrails/ai-memory --json tagName -q .tagName; the server's own.versionis admin-gated at/admin/status→401for a user, so the release is the checkable target); which agents have ai-memory hooks —ai-memory install-hooks --helplists 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; andai-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.gzasset, verify its.sha256, back up the current binary, and install over~/.local/bin/ai-memory. Confirmai-memory --versionis 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.shoverlay. - Verify:
ai-memory --versionmatches the server;ai-memory auth status→ logged in; the agent configs still callai-memory hook --eventnatively;<data_dir>/hook-spoolis well below the 10000 cap. - ≥ 2.0 notes:
install-hooks --applynow stores the bearer in<data_dir>/auth-token(0600) and renders aresolveToken()helper in generated TypeScript integrations; the managed Agent Skills changed — refresh them withmemory_install_self_routing(orai-memory install-instructions). Codex prompts Hooks need review on next start: trust them. A CLI ≥ 2.0 talks fine to a 1.3x server for hooks (/hook/batchunchanged), but upgrade the server first (aim-ops upgrade) and then clients, on the same day.
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
qmdMCP entry /wiki-reindexhooks 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_statusor/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 # → {"id": "...", "username": "alice", "token": "<COPY-THIS-NOW>"}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-codeor--agent open-codefor those harnesses.--applymutates 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.jsonto 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_actormode (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 [email protected] # token T-foo
ai-memory user add --username alice-bar --email [email protected] # token T-bar
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:
# values.yaml
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_pepperis set on the engineai-memory user listreturns the expected users- The agent MCP config (
~/.claude/settings.json,~/.codex/config.toml, OpenCode config, or project-level equivalent) hasAuthorization: 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_sessionand the MCP client doesn't forwardsession_id, warn that the mode is silently degrading to single slot and recommendper_actorinstead
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.