Files
ai-memory/docs/managed-workstreams.md

52 KiB

Managed cross-harness workstreams

ai-memory run is an opt-in launcher that lets one logical coding session move between Claude Code, Codex, OpenCode, OpenCode 2 beta, Pi, Crush, Kimi Code, Command Code, Kiro CLI v2/v3, OMP, Grok Build CLI, and Antigravity CLI. Direct agent launches keep their existing ai-memory behavior. There is no global mode toggle and no switch command: using run selects the current workstream and transparently creates or resumes the correct native session for the requested harness.

ai-memory run is the preferred way to start a harness — "if in doubt, run with ai-memory." Beyond session continuity, the first time it launches a given harness it auto-installs that harness's ai-memory hooks and MCP if they are not already wired, so capture and recall work without a separate install-hooks / install-mcp step (a common footgun: ai-memory run kimi used to capture nothing if the Kimi hooks were never installed). Auto-wire is idempotent and one-time per harness, binary version and install location (a second config home, such as another CLAUDE_CONFIG_DIR, gets its own first launch; ai-memory uninstall of hooks or MCP clears that record, so the next launch wires again), preserves unrelated user config, runs before the harness starts so it picks up the fresh hooks, and is best-effort — if an install fails it warns and still launches. Harnesses without installer support (Crush) are skipped; Pi wires hooks but has no MCP client to write. Turn it off with ai-memory run --no-autowire, AI_MEMORY_RUN_AUTOWIRE=false, or run_autowire = false in config; manual install-hooks / install-mcp remain available for harnesses you never launch through run.

The launcher resolves its executable name through PATH directly — it does not go through an interactive shell, so a claude defined only as a shell alias in .bashrc/.zshrc is invisible to it. If you switch Claude accounts by alias, put a same-named script or shim earlier on PATH instead (or pass --executable PATH, which also resolves a bare name through PATH), so the resolved claude process actually is the one you meant.

Multiple Claude accounts (e.g. Corporate and Personal). Any harness name starting with claude is accepted (claude-corp, claude-personal, ...) and always selects the Claude harness — the exact spelling never changes the agent kind, session store, or transcript import. Combine that wildcard with --executable to launch the right account's binary while keeping each account's managed workstream distinguishable in your shell history:

# ~/bin/claude-corp and ~/bin/claude-personal are wrapper scripts (earlier on
# PATH than the bare `claude`) that each exec the real claude binary with
# that account's config/credentials directory.
ai-memory run claude-corp --executable claude-corp --model opus
ai-memory run claude-personal --executable claude-personal

Only the --executable value matters for which binary actually runs; the positional name is free-form as long as it starts with claude, so pick whatever reads clearly to you.

cd /path/to/project

ai-memory run claude
# quit Claude Code, then continue the same logical workstream in Codex
ai-memory run codex --yolo
# return to Claude Code later; ai-memory supplies Claude's native --resume
ai-memory run claude --model opus
# any `claude*` name is accepted (see "Multiple Claude accounts" above)
ai-memory run claude-corp
# Kimi Code installs `kimi`; `kimi-cli` is accepted as a launcher alias
ai-memory run kimi-cli
# Command Code uses `command-code` on Unix and `cmdc` on native Windows
ai-memory run command-code
# Kiro defaults to v2; select its incompatible v3 engine explicitly once
ai-memory run kiro
ai-memory run kiro --v3
# or omit the harness and continue the newest usable session automatically
ai-memory run

Everything after the harness name is native argv except the wrapper-owned exact flags --yolo, --fresh, and --force-unlock. No -- separator is needed, and ai-memory does not maintain a second copy of each harness's option schema. Other wrapper options come first:

Portable events, handoffs, and project briefs are injected as explicitly delimited, untrusted historical data. Instruction-like text inside stored content is evidence only: agents must not execute commands, expose secrets, change permissions or policy, or use tools merely because that content asks. Current system/developer/user instructions, the canonical project instruction file, and the current checkout remain authoritative.

ai-memory run [--workspace NAME] [--project NAME]
              [--workstream NAME | --new NAME] [--executable PATH]
              [--yolo] [--fresh] [--force-unlock]
              [--env KEY=VALUE]... [--env-file PATH]
              [claude|claude*|codex|opencode|opencode2|pi|crush|omp|kimi|command-code|kiro|grok|antigravity]
              [native arguments...]

The default is the most recently selected workstream for the current repository and worktree, creating one named default on first use. --new NAME starts an independent line of work; --workstream NAME returns to one. These are optional branching controls, not harness-switch controls.

List the workstreams available to those selectors without launching a harness:

ai-memory workstreams [--workspace NAME] [--project NAME]
ai-memory workstreams --limit 50 --json

The list is scoped to the same (workspace, project, repository, worktree) identity as run, puts the current selection first, then orders by recent activity. Each row includes the linked harnesses and stable workstream id; the response does not expose checkout paths, repository fingerprints, or native session ids.

Names are chosen at --new time and can be corrected later:

ai-memory rename-workstream --from typo-nmae --to refactor-db
ai-memory rename-workstream --workstream-id 01a04092-… --to refactor-db

The two selectors are mutually exclusive; the id is the one workstreams prints. The rename is metadata only. Names are unique per checkout, so a destination another workstream already holds is refused rather than merged, and the destination is validated exactly like a --new name. Because the ledger, linked harnesses, and managed runs all key on the workstream id rather than its name, nothing else moves — including which workstream a bare ai-memory run resumes, and the listing order, both of which stay put because a rename deliberately does not touch selected_at or updated_at. A run that is already live keeps displaying the name it launched with until it exits.

Do you need this?

Probably not at first — hooks alone already carry most continuity.

  • Skip it when a handoff is all you want: you quit Claude Code mid-task, open Codex in the same directory, and the next session starts with "where you left off, what failed, what's open". That works with nothing but install-hooks; no ai-memory run involved.
  • Use it when you want the harness's own native resume (claude --resume / the picker) to survive a harness SWITCH — the managed ledger records the visible event stream portably, so ai-memory continue can reopen the same workstream in a different agent with the exact tool-call history, not just a summary.

If you never switch harnesses mid-workstream, the default path is simpler and loses you nothing.

Project-first launcher

ai-memory show reverses the usual cd then run flow: choose a local checkout, choose an installed managed harness, and launch from that checkout.

cd ~/Projects
ai-memory show

# Structured discovery only; never launches a harness.
ai-memory show --json
ai-memory show --json --no-scan

A successful managed prepare refreshes <data_dir>/client-projects.json, a private client-local registry keyed by a normalized, credential-free server URL and (workspace, project). The server's /api/v1/projects response supplies only project metadata; it never exposes or chooses a server-host checkout path. This lets a laptop and desktop map the same remote homeserver project to different local directories without syncing path precedence or conflicts.

By default the picker combines valid saved links with a bounded depth-1 scan of the current directory. The scan recognizes common project markers, ignores symlinks plus dependency/build directories, and resolves each candidate through the same marker and repository rules as run. --no-scan uses saved links only, while --workspace NAME filters both sources. Stale, retargeted, or scope-mismatched links are skipped and a successful later run repairs the entry. If the server is temporarily unavailable, saved links and scan results remain selectable, but managed run still fails closed if it cannot prepare a workstream before launching the agent.

Interactive mode begins with + New project. The launcher accepts a portable lowercase ASCII directory name, builds the marker, instruction routing, and Agent Skills in a hidden staging directory, and renames it into place only when all setup succeeds. --yolo, --fresh, and trailing native arguments apply to the selected harness. Non-terminal callers must use --json; JSON cannot be combined with launch arguments.

Continuing from anywhere

Bare ai-memory run continues the current checkout, but its workstream lookup is keyed by (workspace, project, repo fingerprint, worktree fingerprint), so the caller must already be in the project. ai-memory continue supplies the missing step and needs no cd:

ai-memory continue
ai-memory continue --workspace work

The checkout is chosen entirely on the client, from the linked_at stamp that every successful managed prepare writes to client-projects.json. The server is never asked which directory to use — it does not expose host paths, and a link can only be trusted after this host revalidates it.

Before launching, the newest link is rechecked twice: the recorded path must still canonicalize to itself (rejecting a directory that moved or was replaced by a symlink), and it must still resolve to the same (workspace, project) (rejecting a checkout that would file this session's memory under a different scope). A link failing either check is named on stderr and skipped, and the next-newest link is tried. A corrupt linked_at timestamp is also reported and never considered launchable. Falling through is never silent: the selected project and path are always printed before the harness starts.

Once a checkout is selected, the launch is exactly bare ai-memory run in that directory, including automatic harness selection. continue therefore accepts --workspace, --yolo, and --fresh, but not native harness arguments or --executable, whose meaning depends on a harness the user did not name.

Picking a workstream

ai-memory resume is the interactive counterpart to continue: it presents recent workstreams from every valid client-local managed checkout, then launches the selected named workstream without needing a cd first.

ai-memory resume
ai-memory resume --workspace work --limit 50

Use Up/Down (or j/k) to move between workstreams and Left/Right to cycle the launch harness for the highlighted row. Each row remembers its choice while you navigate. auto is the initial choice and preserves bare ai-memory run's discovery of the newest usable session; the remaining choices are supported harness executables detected in the host PATH. Enter launches the displayed workstream/harness combination, while Escape or q cancels.

The current selection for each checkout leads the picker, followed by recent activity. Each row identifies its workspace/project, activity age, selected launch harness, and already linked harnesses. Choosing a different harness is how an existing workstream can be continued in another agent. --yolo and --fresh are forwarded to the eventual managed launch.

The picker seeds discovery with the current checkout and paths from this host's private client-projects.json registry. It revalidates both the canonical path and resolved scope before it asks the server for that checkout's workstreams, and deduplicates the same workstream reached through both sources. The server receives only the repository/worktree fingerprints required for the existing checkout-local listing, never a host path. It needs an interactive terminal; scripts can continue to use ai-memory workstreams --json after selecting a checkout themselves.

Automatic harness selection

With no harness name, ai-memory run inspects checkout-local sessions for Claude Code, Codex, OpenCode, Pi, Crush, Kimi Code, Command Code, and both Kiro CLI engines. For an empty workstream it resumes the newest session automatically. For an established workstream, server state takes precedence: ai-memory resumes the most recently linked harness that still has a usable local session. It never chooses a newer but obsolete session from another harness merely because that file has a later timestamp. Kiro's v2 and v3 candidates share one server agent identity, but the selected native engine flavor remains exact. OMP, Grok, and Antigravity remain available explicitly but are not in the automatic pool. OpenCode 2 is likewise explicit-only (ai-memory run opencode2): it shares v1's session store, so listing both would duplicate every candidate.

OpenCode 2 sessions run inside a shared background service, so its plugin cannot see a managed run's environment the way in-process plugins do. Managed run opencode2 legs launch with the correct resume selectors, capture through hooks, and import the beta transcript — all verified live — but the ledger-delta acknowledgement (context_delivered) does not fire, because the accept path requires the run id on the session-start hook. The failure mode is redelivery, never loss: a later leg into another harness may receive the range again. Until the beta offers per-invocation plugin context, cross-harness continuity into opencode2 arrives through the ordinary handoff loop rather than the ledger delta.

Bare mode accepts wrapper options but not harness-native arguments or --executable, because their meaning depends on the selected harness. In a new directory with no session in the automatic pool, it exits without creating a workstream and suggests the explicit ai-memory run <harness> commands.

First managed launch

An otherwise-empty workstream may adopt one of the requested harness's existing native sessions. On an interactive launch, ai-memory inspects that harness's store without modifying it and lists up to eight recent sessions whose recorded working directory matches the current checkout. Choose one to resume it, press Enter to accept the newest candidate, or choose 0 to start a new session. Sessions from another checkout are never offered.

Adoption is only a bootstrap operation. Once any harness has linked a native session or contributed portable message/tool/compaction history, the workstream is established. If Claude established it and Codex has not joined it yet, for example, ai-memory run codex creates a fresh Codex session and injects the Claude workstream history. It does not inspect or select an older unrelated Codex session. Returning to Codex later resumes the Codex session already linked to that workstream.

Explicit native selectors always win. --new NAME always creates a fresh native session for the new workstream. Scripted/noninteractive invocations and launches without terminal input skip the chooser and start fresh. A launch that exits before producing either a native session or portable history does not consume the later adoption opportunity.

Before adding an ai-memory-owned resume selector, the launcher checks the exact linked id in the harness's native store without modifying it. If the transcript was deleted, cleared, or lost with a sandbox overlay, ai-memory starts a fresh native session and repoints the same workstream when that session is observed. An unreadable or malformed store is reported but is not mistaken for a missing session. Use ai-memory run --fresh <harness> to deliberately skip the linked session and the adoption chooser. --fresh cannot be combined with a native resume, continue, session, or fork selector.

What happens on each run

  1. The host client resolves the normal workspace/project scope and a stable repository plus worktree fingerprint. It opens a 90-second renewable lease. One writer may own a workstream at a time, so two terminals cannot silently race its native-session pointers or delivery cursors.
  2. Bare mode resolves the correct available harness. For an empty workstream, an explicit interactive adapter can offer matching local sessions for one-time adoption. Otherwise the adapter passes native arguments through in order and adds a create/resume selector only when the user did not supply one.
  3. AI_MEMORY_RUN_ID marks lifecycle hooks as managed. SessionStart links the actual native session and injects only the portable events that session has not seen. Crush, which has no SessionStart hook, receives the same bounded packet through a temporary options.global_context_paths entry. Kimi Code fires SessionStart but discards its stdout, so the kimi adapter's SessionStart hook only captures the event — it neither fetches nor links. The UserPromptSubmit hook issues the /handoff GET with the native session_id in the query; the server links the session and renders the packet atomically, and Kimi Code injects the hook's stdout as a user message before the turn. A pending single-use handoff remains additive: it is placed before the managed packet, and both delivery claims commit together only after the full handoff/packet/brief response is assembled. Direct launches continue to use the same handoff path without a managed packet.
  4. When the child exits, ai-memory reads the native transcript store without modifying it. A session named on the command line (or chosen before the spawn) is the one it reads. Otherwise a session linked during the run under its AI_MEMORY_RUN_ID, even the workstream's current one, is read when the native store holds it for this checkout; only without such a link does it look for the newest session in the checkout, which a concurrent launch there could own. Visible user/assistant messages, completed tool calls/results, compaction summaries, and a non-mutating Git checkpoint enter an append-only workstream ledger. Hidden reasoning and unsupported/private records are excluded and recorded as extraction-loss annotations. Each delivered workstream packet begins with a versioned origin marker. If Claude Code persists that packet and its Read tool returns it, the Claude transcript normalizer excludes the marked result instead of feeding delivered history into the ledger again. It also recognizes the pre-marker packet header for compatibility with existing native sessions.
  5. Imports use deterministic event ids, incremental source cursors, immutable sanitized JSONL segments, and bounded batches. A retry cannot duplicate history. The native process's exit code is preserved.
  6. Command Code, Kiro CLI and Antigravity CLI have no native session-end hook, so for them the run then finalizes the session itself (again after a resume), as ai-memory finalize-session --reopen would: summary, handoff and opt-in SessionEnd consolidation. It does this only for a session named on the command line, chosen before the spawn, or linked during the run, never for one it had to look for. If finalizing fails, it prints the exact finalize-session command to run instead.

The next harness receives a bounded recent delta because no agent context window can safely absorb an unbounded transcript. The complete visible ledger remains searchable from inside a managed agent process:

ai-memory workstream-search "scope resolver decision"
ai-memory workstream-search --limit 50 --json "failed migration"

AI_MEMORY_WORKSTREAM_ID supplies the id automatically inside the child. From another shell, pass --workstream-id <uuid> explicitly. Search results preserve the source harness, role, event sequence, and content. Historical tool activity is labelled completed evidence and must never be replayed as a pending call.

Native adapter behavior

Harness Fresh native session Returning native session Read-only source
Claude Code generated --session-id --resume <id> ~/.claude/projects/**/*.jsonl
Codex native default creation resume <id> ~/.codex/sessions/**/rollout-*.jsonl
OpenCode native default creation --session <id> ~/.local/share/opencode/opencode.db opened read-only
OpenCode 2 beta native default creation --session <id> same opencode.db as v1 (the beta channel keeps v1's filename; the beta adds session_v2/session_message tables beside v1's); launched via the opencode2 binary
Pi generated --session-id --session <id> ~/.pi/agent/sessions/**/*.jsonl
Crush native default creation --session <id> <data dir>/crush.db opened read-only: options.data_directory from Crush's JSON configs, else the closest .crush up to the git worktree root, else <cwd>/.crush
Kimi Code native default creation --session <id> $KIMI_CODE_HOME/sessions/*/*/agents/main/wire.jsonl
Command Code native default creation --session <uuid> ~/.commandcode/projects/*/<uuid>.jsonl
Kiro CLI v2 native default creation --resume-id <uuid> $KIRO_HOME/sessions/cli/<uuid>.jsonl (+ sibling <uuid>.json metadata)
Kiro CLI v3 native default creation with --v3 --v3 --resume-id <sess_uuid> $KIRO_HOME/sessions/<checkout-bucket>/<sess_uuid>/messages.jsonl (+ sibling session.json metadata)
OMP native default creation --resume=<id> <agent dir>/sessions/**/*.jsonl, or the XDG session directory described below
Grok Build CLI generated --session-id --resume <id> $GROK_HOME/sessions/*/*/chat_history.jsonl
Antigravity CLI native default creation --conversation <id> ~/.gemini/antigravity-cli/conversations/<id>.db metadata plus lifecycle-hook capture

OMP's agent directory is ~/.omp/agent for the default profile and ~/.omp/profiles/<name>/agent for a named profile. PI_CONFIG_DIR changes the .omp root relative to the user's home. The default profile can also use PI_CODING_AGENT_DIR. On Linux and macOS, sessions move to $XDG_DATA_HOME/omp/sessions, or $XDG_DATA_HOME/omp/profiles/<name>/sessions for a named profile, when that OMP directory exists and the agent directory has not been relocated.

Command Code v3 transcripts are self-describing and append-only. The adapter requires the UUID filename, header id, and canonical header cwd to agree before discovery or resume. Its 1.14.1 allowlist was checked against both the integrity-matched published package and a sanitized live fixture. It imports visible messages, compactions, and branch summaries, retains parentId as branch provenance, and excludes hidden thinking, images, harness-injected messages, provider/model metadata, custom/Mod records, and every sidecar. An unknown transcript version fails closed until audited. The default executable is command-code on Unix and cmdc on native Windows; commandcode, cmdc, and cmd are accepted launcher aliases. Exact user --session, --resume, --continue, and fork choices remain authoritative. The experimental unsandboxed Mod API is not used.

An explicit native selector such as Claude's --resume, OpenCode's --session, Codex's resume, or Antigravity's --conversation / --continue wins. ai-memory links the selected native session and resets an unrelated adapter cursor rather than assuming it belongs to the old session.

Crush has no hooks to link its session: a fresh Crush launch claims the one top-level session created while it ran (its title and sub-agent sessions do not count), and imports nothing, with a warning, when another launch on the same store created one too; resume that session with --session <id> to link it. --continue claims the one session it touched on the same terms. When the data directory lies outside the project, as a global data_directory does, other projects' sessions share it and Crush records no directory per session, so a run claims only a session that edited a file in this project (its sub-agents' edits count), and only when it is the one; otherwise it imports nothing, with the same warning. The store's real location decides, so a .crush symlinked to a shared directory is shared. Another project's session that edited a file here still counts as this project's. Pi and OMP --session-dir values and Crush --data-dir values are passed through unchanged and used as the read-only import root. Without --data-dir, ai-memory finds Crush's data directory as Crush does, except that it does not run a crushrc to read one set only there; pass --data-dir in that case. Native store environment overrides are also honored: CLAUDE_CONFIG_DIR, CODEX_HOME, XDG_DATA_HOME, PI_CODING_AGENT_SESSION_DIR, PI_CODING_AGENT_DIR, KIMI_CODE_HOME, KIRO_HOME, and GROK_HOME, plus OMP's PI_CONFIG_DIR. OMP profile selection follows a leading --profile, then OMP_PROFILE, then PI_PROFILE. A named profile ignores PI_CODING_AGENT_DIR. A --profile later in the command line is left to OMP; use OMP_PROFILE or --env OMP_PROFILE=<name> so ai-memory can resolve the same profile.

Blank directory overrides (empty or whitespace-only values) count as unset. Session import, hooks and MCP use their fallback paths, and ai-memory run removes those values from the child environment. Profile variables follow a different rule: an empty OMP_PROFILE selects the default profile and still takes precedence over PI_PROFILE. Export these in the environment ai-memory run itself sees, or pass them with --env (below), not only inside a harness wrapper script. ai-memory run resolves the native session directory, and auto-wires hooks and MCP, from that environment; if the harness writes its transcript under a custom CLAUDE_CONFIG_DIR that ai-memory run cannot see, the two disagree and the native transcript import fails. When you use per-account config directories, set the variable before invoking ai-memory run (or in the same wrapper that also runs it), so hook installation and native-session resolution agree.

A repeatable ai-memory run --env KEY=VALUE <harness> (and --env-file <path>, one KEY=VALUE per line, blank lines and # comments skipped) is the first-class alternative to the env KEY=VAL harness wrapper-alias pattern above: it is a wrapper-owned flag, so it must precede the harness name, and the resolved environment reaches the spawned harness process, ai-memory run's own native-session resolution, and first-launch auto-wire. Hooks, MCP and transcript import then follow the same config home, which is the CLAUDE_CONFIG_DIR-agreement requirement described above, without exporting the variable into the invoking shell first. A later --env overrides a same-key --env-file entry; neither expands nor interprets the value, so let the shell expand $HOME on the command line and write absolute paths in an --env-file. Manual install-hooks / install-mcp do not take --env; they read their own environment.

The Pi-family adapter also recognizes a complete .jsonl.<nonce>.tmp atomic-write file when a native process exits before renaming it; incomplete final JSONL records are never imported. Help, version, and known utility subcommands pass through without session flags. Claude/Pi/OMP print mode, Codex exec, OpenCode/Crush run, redirected input, and other noninteractive launches never open the adoption chooser.

ai-memory run --yolo <harness> and ai-memory run <harness> --yolo both use the harness's native dangerous mode. The translation is Claude Code --dangerously-skip-permissions, Codex --dangerously-bypass-approvals-and-sandbox, OpenCode --auto, Pi --approve, Crush --yolo, Kimi Code --yolo, Command Code --yolo, Kiro CLI v2 --trust-all-tools, Grok Build CLI --yolo (equivalent to its --always-approve option), and Antigravity CLI --dangerously-skip-permissions. Kiro v3 replaced the trust-all flag with permissions.yaml, so ai-memory prints a notice and adds no unverified flag. OMP currently needs no added flag. ai-memory does not add a duplicate when the translated native flag is already present.

Managed support is intentionally narrower than the general integration matrix. Gemini CLI, Devin CLI, Cursor, and other agents may have MCP or lifecycle-hook support without native managed resume. Contributors adding another managed harness must follow the managed-harness contribution protocol, including read-only extraction, pre-turn context delivery, migration invariants, deterministic tests, and an opt-in real-harness acceptance pass.

Codex shared-daemon recovery (#987)

Recent Codex releases run sessions through a shared background app-server daemon (codex agents lists it). The daemon keeps the environment it started with, and the lifecycle hooks it launches inherit that environment — including the AI_MEMORY_RUN_ID of whichever managed run auto-started it. When a later managed Codex SessionStart reports that finished id, ai-memory ignores it as authority and searches only the request's already-resolved repository, exact checkout cwd, and operator bucket. The server adopts a replacement only when exactly one live, undelivered Codex run is waiting there, and it selects plus links that run in one writer transaction. Multiple candidates, an active id whose scope, checkout, or owner does not match, and a run already linked by another session all fail closed.

Ordinary managed Codex launches therefore work with the shared daemon. For diagnosis, or when an intentionally concurrent pair of named workstreams makes recovery ambiguous, native arguments after the harness are still forwarded:

ai-memory run codex --no-daemon

--no-daemon makes that one session run without the shared background server even if one is already running; it is available on Codex's interactive and resume commands (checked on Codex 0.156). It remains a useful isolation switch, not a requirement for normal workstream continuity.

Installation and recovery

Managed runs need current ai-memory lifecycle hooks so SessionStart can receive the portable delta. Refresh them after upgrading:

ai-memory install-hooks --agent claude-code --apply
ai-memory install-hooks --agent codex --apply
ai-memory install-hooks --agent opencode --apply
ai-memory install-hooks --agent pi --apply
ai-memory install-hooks --agent omp --apply
ai-memory install-hooks --agent kimi-code --apply
ai-memory install-hooks --agent kiro-cli --apply

Kimi Code hooks installed as native ai-memory hook commands automatically pick up the current delivery behavior when the binary is upgraded. A script-fallback installation must rerun the Kimi Code install-hooks command after upgrading so its staged scripts are refreshed. Current hooks deliver handoffs at UserPromptSubmit; Kimi discards SessionStart stdout.

Known Kimi Code adapter limitations: subagent transcripts (agents/<id>/wire.jsonl other than main) are not imported in v1 and are recorded as an extraction-loss annotation; the session bucket directory name is a one-way hash of the working directory, so discovery always reads state.json's current cwd field or legacy workDir alias and never parses the bucket name. Conflicting aliases or a persisted id that disagrees with the session directory are rejected. Event ids derive from the SHA-256 of the raw wire.jsonl line, so two byte-identical lines — only possible with identical content in the same millisecond, because Kimi Code stamps each record with time — collapse into a single ledger event. The incremental cursor stores both the complete-record byte offset and a SHA-256 of that imported prefix. Normal appends resume at the saved offset; if Kimi rewrites wire.jsonl in place, ai-memory resets to the beginning and replays the file, with stable event ids deduplicating records already in the workstream. Legacy sessions that keep wire.jsonl directly in the session directory (the pre-agents/ layout the kimi session-store still reads through its stat fallback) are neither discovered nor imported in v1. The native contract was reverified against Kimi Code v0.34.0. The managed launcher accepts kimi, kimi-code, and kimi-cli; all three resolve the installed kimi executable.

Kiro's version-aware adapter was live-tested with authenticated Kiro CLI 2.16.2 in both engines. V2 uses UUID session IDs and the flat $KIRO_HOME/sessions/cli/<uuid>.json plus <uuid>.jsonl store with v1 Prompt, AssistantMessage, and ToolResults events. V3 uses incompatible sess_<uuid> IDs and nested $KIRO_HOME/sessions/<checkout-bucket>/<sess_uuid>/session.json plus messages.jsonl; accepted metadata is limited to schemaVersion = 1.0.0, dataModelVersion = 1, an exact directory/id match, and a workspacePaths entry resolving to the current checkout. The v3 visible-event allowlist is user text, assistant Say output, tool calls, and tool results. Session bookkeeping, hook records, usage summaries, turn boundaries, private assistant operations, malformed records, and unknown schema versions are not imported.

The engines can never cross-resume: exact store metadata is checked before a linked --resume-id is injected, and the incompatible engine flavor is also stored in the opaque incremental cursor. Explicit --v3, v3-only --mode, or --agent-engine v3 selects v3; explicit --agent-engine v2 selects v2; an unknown engine value remains passthrough instead of being guessed. Once a v3 session is linked, a later plain ai-memory run kiro recovers that engine transparently. Kiro CLI 2.16.2 wrote v3 sessions below the default ~/.kiro/sessions even when KIRO_HOME redirected other state, so ai-memory checks the configured v3 root first and that default root as a compatibility fallback. If a linked session exists only in the fallback, ai-memory removes KIRO_HOME for that one resume so Kiro can find the session; Kiro consequently uses its default-home v3 settings/hooks for that process, and first-launch auto-wire wires that default home rather than KIRO_HOME. Fresh launches and versions that store the session below the configured root keep KIRO_HOME unchanged. Every candidate still needs exact id, schema, and checkout metadata. The v2 --yolo translation is --trust-all-tools; an explicit narrower --trust-tools choice is never widened. V3 documents no equivalent CLI flag. See Kiro's current session management and v3 compatibility references.

Grok needs no ai-memory hook installation for managed delivery either. Grok ignores SessionStart stdout and its UserPromptSubmit hook is passive, so the launcher fetches the bounded context packet from the server and passes it through Grok's native --rules flag, which appends the text to that session's system prompt. Delivery is acknowledged only after the child spawns. Because --rules is single-use in Grok's argument parser, a natively supplied --rules/--append-system-prompt wins and the packet stays undelivered until a later managed run can accept it. Grok can rewrite chat_history.jsonl in place on rewind, so the import cursor stores a prefix hash and replays from the beginning when it no longer matches, with content-hash event ids deduplicating records already in the workstream. Sibling session files (events.jsonl, updates.jsonl, rewind_points.jsonl) carry harness internals and are never read as transcripts; discovery reads summary.json's info.cwd and never parses the URL-encoded bucket name. The managed launcher accepts grok and grok-build. The native contract was verified against Grok Build CLI v0.2.111.

Antigravity keeps one SQLite database per conversation at ~/.gemini/antigravity-cli/conversations/<conversation-id>.db, so the id is the file name and no scan is needed to locate one. The workspace a conversation was opened on comes from trajectory_metadata_blob, a protobuf message whose first field holds a nested message whose first field is the workspace file:// URI; only those two fields are read. A database that does not carry them — an older or newer agy — is skipped rather than failing the listing. Note the recorded workspace is the directory agy was launched from, not a checkout root, so a conversation started one level up is not offered inside a subdirectory.

agy accepts no caller-chosen id for a new conversation, so a fresh launch injects no selector and the id is linked by the hooks or discovered after exit; a linked resume passes --conversation <id>. --continue / -c is treated as an explicit user choice and is never overridden. --yolo maps to --dangerously-skip-permissions. Step payloads are undocumented, unversioned protobuf blobs, so ai-memory does not decode conversation text: the visible-event ledger for this harness comes from lifecycle-hook capture, and transcript export fails with a message saying so. The managed launcher accepts antigravity, antigravity-cli, and agy. The native contract was verified against Antigravity CLI v1.1.7. Antigravity is not part of the no-argument auto-detection set; name it explicitly.

Crush needs no ai-memory hook installation for managed mode. The launcher reads its one-time context from the server, copies the global Crush JSON the launch would read ($CRUSH_GLOBAL_CONFIG/crush.json, else $XDG_CONFIG_HOME/crush/crush.json, else ~/.config/crush/crush.json, with --env entries first and a blank value counting as unset) into a private temporary directory, appends an ephemeral context path, and points the child at that directory with CRUSH_GLOBAL_CONFIG. When that JSON lists no global_context_paths, the launcher first adds the CRUSH.md and AGENTS.md Crush would have loaded by default, so the packet does not replace them. A global crushrc next to that JSON is carried over by a generated crushrc in the temporary directory that sources it from its own directory, where Crush runs it. Delivery is acknowledged only after the child starts, so a spawn failure cannot lose the packet. The original config is not modified. ai-memory opens the project database read-only; the launched Crush process continues its normal native session writes.

The Linux/macOS Docker shell wrapper cannot inspect host projects or execute a host agent from inside its helper container. For run, show, continue, resume, and workstreams, it downloads the matching native release into ${XDG_DATA_HOME:-~/.local/share}/ai-memory/native-runner, verifies the published SHA-256 checksum, and executes that host client. The release's hooks/ bundle is kept beside it so auto-wire can stage hook scripts on a host where install-hooks never ran. The client lives with the host's ai-memory data rather than under ~/.cache because auto-wired hook configuration runs it directly: a cache flush must not break capture. A client downloaded by an older wrapper stays in ~/.cache/ai-memory/native-runner, and hooks auto-wired from it keep that path until a newer client version auto-wires again. To move them now, re-run ai-memory install-hooks --agent <agent> --apply, then delete the old directory. Set AI_MEMORY_NATIVE_BIN=/path/to/ai-memory to use a specific native build. Native package, release, and source installs need no shim. On native Windows, use the published ai-memory.exe or a source build.

The wrapper intercepts all five commands before Docker and preserves the host PATH, AI_MEMORY_SERVER_URL, and authentication environment. The native client's startup log shows server_url as well as its local config paths; data_dir and bind describe local defaults and do not override a configured remote server. If logs show data_dir=/data followed by starting managed ... No such file or directory, the installed wrapper is stale and sent the command into the helper container. Run ai-memory upgrade on the client machine. A remote/homelab server must be upgraded separately.

On a normal exit, ai-memory imports the transcript and closes the lease before returning. Handled setup, launch, or import failures cancel the lease immediately. A new launch retries an active-workstream conflict briefly so a previous launcher can finish; if another harness is genuinely still running, the conflict remains and concurrent writers are still rejected.

Lease recovery

A launcher that dies without releasing its lease — killed, its terminal closed, or a sandbox such as ai-jail torn down — leaves the workstream held until that lease lapses. An interactive relaunch (stdin and stderr are terminals) no longer fails on that: the conflict reports the lease's expiry, so ai-memory says who holds it and waits for it to lapse (at most one lease, ~90 seconds; Ctrl+C aborts), then starts normally. If the holder renews the lease while you wait, it is a launcher that is still running, and you get an error instead — stop it, or pass --new <name> for a separate workstream. The server's busy check stays the only arbiter: the waiting launcher never forces another run off. Non-interactive launches (scripts, hooks, CI) keep the short retry window and fail fast rather than hanging. Terminal interrupts continue to reach the child while the parent stays alive to finish or cancel the run.

When you know the prior launcher is gone and do not want to wait for the lease, force-expire it explicitly:

ai-memory run --force-unlock codex
# The exact wrapper flag is also accepted after the harness name.
ai-memory run codex --force-unlock

The replacement is atomic and limited to the same durable authenticated operator; in single-user or otherwise unattributed operation, both runs must be unattributed. A different operator's active run is still refused. The command expires the managed lease only — it does not signal or kill a native process. If the previous launcher is actually alive, its later heartbeats and finish are rejected, and its final transcript tail may not be imported. Use --force-unlock only after verifying that launcher has stopped. Older servers do not honor the request and return a refusal, so upgrade the server as well as the client before relying on this recovery path.

Before the child starts, Ctrl+C at the native-session chooser cancels the acquired run and exits without requiring Enter or adopting the selected session. The launcher waits for the server's cancellation response. A request error is reported and leaves the lease to expire within its normal 90-second window; a server that accepts the request but never responds can still keep the launcher waiting. Heartbeats have stopped, so the lease itself still expires.

While the harness or native-session selector is open, a temporary server outage produces one short notice instead of printing every failed heartbeat. The launcher keeps probing every 30 seconds with a 10-second request timeout so the 90-second lease stays safe across ordinary server restarts. Repeated failures are quiet; when the server responds again, one recovery notice confirms that heartbeats resumed. The native harness remains usable throughout the outage. If the outage exceeds one lease window, the original launcher may renew its run only while no newer launcher has claimed the workstream. A replacement prepare, cancel, finish, or destructive operation remains terminal for the old run.

If the client is terminated without cleanup, such as with kill -9, its lease expires within 90 seconds. A later managed run starts from the last committed adapter cursor, so already linked native sessions can import the missing tail without duplicating earlier events. A server or authentication failure before process launch is fatal; ai-memory does not silently start an unmanaged agent.

Privacy and storage boundaries

ai-memory's managed adapters do not write to Claude, Codex, OpenCode, Pi, Crush, Kimi Code, Command Code, Kiro, OMP, Grok, or Antigravity private stores. The launched harness retains normal ownership of its own session writes. Adapters read only documented or observed local session formats. Provider credentials, encrypted content, system/developer prompt records, and hidden reasoning are not copied. The server sanitizer runs before both the SQLite FTS ledger and immutable files under <data_dir>/raw/workstreams/<workstream-id>/segments/ are written.

The ledger is an operational continuity substrate, not a replacement for the markdown wiki. Durable decisions, rules, procedures, and project facts still belong in wiki pages through consolidation or explicit durable writes.

Project and directory renames

ai-memory rename-project --from OLD --to NEW changes only the server-side project name. Wiki paths are UUID-keyed, so it moves no server directory, source checkout, or native harness session. If the source checkout path itself is renamed, absolute-path session locators used by Claude Code, Codex, OpenCode, Pi, Kimi Code (state.json's cwd or legacy workDir), Command Code (v3 header cwd), Kiro v2 (<uuid>.json's cwd), Kiro v3 (session.json's workspacePaths), OMP, and Antigravity may still reference the old path; Crush's project-local .crush database moves with the checkout.

There is no portable, supported API that rewrites every harness's private project locator. ai-memory therefore does not mutate those stores or silently equate a renamed checkout with another clone of the same remote. Explicit native selectors still win and can recover a session when that harness supports cross-directory resume; OpenCode also provides its own export/import flow. For a renamed checkout, use an explicit harness and its documented session selector to seed the new managed workstream. Keep the old checkout path available until recovery is verified. Automatic discovery intentionally requires the recorded checkout to match exactly.

Manual acceptance

The opt-in acceptance runner exercises launcher edge cases and then orchestrates the locally installed Claude, Codex, OpenCode, Pi, Crush, OMP, Kimi, Command Code, Grok, and Antigravity CLIs through one real workstream:

scripts/managed-workstream-acceptance.sh

It is deliberately separate from CI because it uses local harness credentials and model calls. Hook configs, native session stores, the ai-memory server, and the Git fixture are isolated under a temporary directory. Claude, Codex, and OpenCode receive only copied authentication material; OMP receives a temporary agent directory with read-consistent credential/model database backups and copied settings. Crush uses its existing global provider configuration and an isolated project database. Kimi Code runs with an isolated $KIMI_CODE_HOME seeded with the operator's provider configuration. Command Code runs with an isolated HOME seeded only with auth.json and config.json. Antigravity runs with an isolated HOME seeded only with the operator's OAuth and settings files. The deterministic phase also covers first-run adoption, bare-mode selection and empty-directory failure, wrapper --yolo, lease exclusion, Crush context cleanup, fake-mode Kimi and Command Code store/resume/import round trips, an Antigravity hook/link/resume round trip, a fake-mode Kiro v2 store/resume/import round trip, the equivalent Kiro v3 nested-store round trip with transparent engine recovery, private-trajectory exclusion, and the established-workstream guard against obsolete sessions. The fake Kimi round trip also deletes the linked native session and verifies automatic fresh-session recovery and repointing. Native session creation, read-only extraction, cross-harness injection, and returning resume paths are all exercised. Docker wrapper host execution and remote URL preservation are covered separately by the ai-memory-cli packaging tests.

Kiro is intentionally skipped in the scripted real-model loop. Its --no-interactive mode writes a different v1 SQLite store, while both managed adapters read the interactive v2/v3 journals. Logged-in Kiro acceptance therefore remains interactive. For v2, run ai-memory run --new kiro-v2-accept kiro, enter a unique prompt, quit normally, then run ai-memory run --workstream kiro-v2-accept kiro-cli and verify the same UUID resumes. For v3, repeat with a fresh workstream and kiro --v3; the second plain kiro launch must transparently add --v3 --resume-id <sess_uuid>. Search both workstream ledgers for the unique visible assistant replies. Record the Kiro version and sanitize metadata/event files before changing either fixture schema.

The real-harness phase treats the model as the system under transport, not as the test oracle. For each leg it records the prior ledger sequence, then requires a newly imported assistant event from harnesses with readable native transcripts. For Antigravity it instead requires the exact native conversation link and a new correlated startup-hook observation, because its private trajectory protobuf is deliberately not decoded. When a context delta is expected, it first verifies that the prior ledger endpoint is newer than that harness's delivery cursor, then requires the latest managed run to report that exact endpoint as sync_through with context_delivered = 1. It does not require the model to quote a prior sentinel: Claude Code may externalize a large hook result to a file, and whether a model chooses to read that file is not a deterministic continuity signal. The deterministic fake Grok and Antigravity cross-harness fixtures exercise the same assertion helper without credentials or model calls.

Set AI_MEMORY_ACCEPTANCE_HARNESSES="command-code codex" to select a Command-Code-to-Codex-to-Command-Code round trip, or AI_MEMORY_ACCEPTANCE_HARNESSES="antigravity codex" to select an Antigravity-to-Codex-to-Antigravity round trip (agy and antigravity-cli are accepted aliases), AI_MEMORY_ACCEPTANCE_DETERMINISTIC_ONLY=1 to skip model calls, or AI_MEMORY_ACCEPTANCE_KEEP=1 to retain all temporary logs and data.

Running inside Herdr

Herdr is a terminal workspace manager that tracks which agent runs in each pane. It identifies the agent from the pane's foreground process, falling back to matching the agent's own screen output against per-agent manifests.

ai-memory run sits awkwardly between the two. The foreground process is the wrapper and the agent is its child — one process group whose leader is ai-memory — so process detection does not find the agent. The pane resolves only once the harness paints a title Herdr recognizes, which can be well after launch and may never happen for a harness whose output matches no manifest. Until then Herdr's agents pane shows nothing for that pane.

ai-memory does not try to fix this from the inside, deliberately. Herdr's hint for wrapper commands, HERDR_AGENT, is scoped to the pane's foreground process, and a process cannot amend its own environment after exec — so the wrapper has no way to describe itself to Herdr once it is already running. Setting the variable on the agent it spawns would put it somewhere Herdr does not look for it.

Two things work today.

Name the agent on the command, where Herdr does look:

HERDR_AGENT=codex ai-memory run codex

Or install Herdr's own agent integration, which is the better answer:

herdr integration install codex
herdr integration install claude

An installed integration reports agent identity and lifecycle state over Herdr's socket and is authoritative regardless of process detection — so the wrapper stops mattering entirely. It also upgrades what Herdr can show: real idle / working / blocked signals instead of inferring from the screen, which cannot reliably see blocked at all.

These are separate mechanisms writing to separate files: Herdr's integration installs its own hook script (~/.claude/hooks/herdr-agent-state.sh for Claude Code, ~/.codex/herdr-agent-state.sh for Codex), while ai-memory's lifecycle hooks live in the agent's own config. ai-memory's installer preserves foreign entries rather than replacing them, so re-running ai-memory install-hooks will not remove Herdr's. Back up the agent's config and diff it after installing either one if you want to be sure the other survived.