mcpField
A
aim-init

aim-init

github.com/djalmajrAgent workflows487 installsUpdated 5w ago
Repository

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

  1. 01Read-only setup diagnosis
  2. 02Greenfield ai-memory setup
  3. 03Migration from qmd
  4. 04Refresh CLI and hooks
  5. 05Configure project routing

Use cases

  1. 01Check whether a repository needs migration
  2. 02Enable automatic session capture
  3. 03Move a qmd repository to ai-memory
  4. 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-init

After installing, type/aim-initin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
usage.md
scripts/
dev-setup-hooks.ps1
dev-setup-hooks.sh
kc-create-device-client.sh
templates/
ai-memory.toml.example
ai-memory.toml.tmpl
mcp-entry.json.tmpl
routing-snippet.md.tmpl

Details

From the project README

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. 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:

  1. 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.
  2. Workspace — personal → default; org repo → its own workspace (e.g. acmecorp).
  3. 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:

  1. 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.
  2. 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>.
  3. 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

  1. .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>"
    
    # 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
    
  2. 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.

  3. 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.

  4. 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 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_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).
    • Consumer key (amk_, keys-only gateway) — the mcp-auth sidecar runs without an identity provider and resolves per-consumer keys locally (KEYS_DB). Each agent/CLI gets its own key (POST /keys with an admin-scoped key; issued by aim-ops keys), scoped read,write; pass it via --auth-token for hooks and as the MCP Authorization header. Attribution comes from the key's actor_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 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. GET <server>/keys/whoami → 200 with {identity, can_issue} means the keys-only gateway is present (consumer keys); 404 means the sidecar has no KEYS_DB.

    Add --hooks-dir <ai-memory/hooks> when the binary can't locate its vendored scripts (e.g. a cargo install build):

    # 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.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:

OSdata 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:

# 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) 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)

  1. Confirm workspace + project with the user.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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:

  1. 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.
  2. 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.
  3. 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/.
  4. 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.
  5. 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.
  6. 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.
  7. 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:

  1. 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?).
  2. 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.
  3. 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.
  4. 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.
  5. ≥ 2.0 notes: install-hooks --apply now stores the bearer in <data_dir>/auth-token (0600) and renders a resolveToken() helper in generated TypeScript integrations; the managed Agent Skills changed — refresh them with memory_install_self_routing (or ai-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/batch unchanged), 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 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)
  1. 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).

  2. 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).

  3. 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.

  4. 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 [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_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.

Comments

Markdown supported