# Aim Init

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

- Skill: `djalmajr/aim-init` (Agent Skill, multi-file: 9 files)
- Install (CLI): `npx skillmds@latest add djalmajr/aim-init`
- Raw SKILL.md: https://api.skillmd.com/api/skills/djalmajr/aim-init/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: djalmajr (https://skillmd.com/u/djalmajr)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/djalmajr/aim-init

---


# 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](references/usage.md).
> **Server side** (install a new instance, upgrade the engine, providers, consumer keys,
> backup/restore) is [aim-ops](../aim-ops/SKILL.md) — 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:

```bash
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.
   ```toml
   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](templates/mcp-entry.json.tmpl)).
   Prefer the CLI installer so each agent gets its native global config:
   ```bash
   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):
   ```bash
   # 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:

| 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/`](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:
```bash
# 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:
   ```bash
   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`):
   ```bash
   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:
   ```json
   {
     "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

…(truncated)
