- frontend-api.md (#986): the list, search, and recent routes return bare
JSON arrays, not `{ "workspaces": … }`-style wrappers (the route tests
assert `as_array()`); a page read returns `body_markdown`, not `body`; a
search hit carries workspace/project/kind and no `id`.
- windows.md (#758): native `ai-memory upgrade` is done (#801/#802), not
in-progress.
- managed-workstreams.md + support matrix (#987): document the Codex shared
daemon handing hooks a stale AI_MEMORY_RUN_ID and the `--no-daemon`
workaround until the server-side fix lands.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
ai-jail integration (`ai-memory run --yolo`):
- The re-exec built `ai-jail <flags> <exe> run …` with no `--`. ai-jail
rejects one of its own flags after the command and `run` shares flag names
with it, so `run claude --yolo --env GH_TOKEN=…` aborted. The invocation now
emits `--` before the wrapped exe (forwarding a colliding flag additionally
needs ai-jail >= 2.4.2, whose guard honors the separator; the cross-tool
test gates on that version).
- The offer only checked for a file named ai-jail: Windows could show it, a
host without bwrap/sandbox-exec was offered a jail that cannot start, and a
~/.local/bin-only install was offered and then not found by the bare
`Command::new("ai-jail")` re-exec after the run was already cancelled.
usable_ai_jail(os, lookup) now returns the exact binary to exec only on
Linux/macOS with the backend present; otherwise no question is asked.
--true-yolo:
- It now implies --yolo (warning, ai-jail offer, harness dangerous mode):
alone it used to apply Claude's bypassPermissions with no warning. It is
interchangeable with --yolo for non-Claude harnesses, and recognized after
native arguments (`run claude --model opus --true-yolo`), where clap leaves
it in the native argv and it was forwarded to Claude as an unknown option.
- The claude_true_yolo config key only upgrades an explicit yolo launch, as
its doc comment stated, instead of bypassing permissions on every run.
- Removed what never worked: three CLAUDE_CODE_DISABLE_*RM* env vars Claude
Code does not read (absent from the 2.1.280 binary and its env reference),
and an empty permissions.ask array that cannot clear ask rules from other
scopes (Claude unions them). Docs now state that Claude honors explicit ask
rules and its command-safety checks in every permission mode.
Relaunch after an interrupted run:
- A launcher killed before releasing its lease (terminal closed, ai-jail
torn down) left the workstream held for up to 90s and the next launch failed
after a 5s retry. An interactive launch now parses the holder and expiry from
the 409, waits for that lease to lapse (bounded by one lease; Ctrl-C aborts),
then proceeds. A holder that renews meanwhile is reported as live and never
displaced; the server's busy check stays the only arbiter (security
inventory row 13b). Non-interactive launches keep the short window.
Tests: usable_ai_jail OS/backend/Windows/exact-path, `--` placement and the
colliding-flag regression (unit + real ai-jail --dry-run), the yolo_modes
table, --true-yolo in both argv positions via real clap parses, the reduced
true-yolo argv, and HTTP-level held-lease wait / renewed-owner / Ctrl-C cases.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
Command Code, Kiro CLI (v2 and v3) and Antigravity CLI have no native
session-end hook, so their sessions stayed open until a manual
`ai-memory finalize-session`. When `ai-memory run` launched one of them, it
now finalizes the run's own session after the harness exits: the one named
on the command line or chosen before the spawn, or the one its child linked
under AI_MEMORY_RUN_ID in this checkout, never a discovered one.
SessionId::from_native moves the hooks router's native-id mapping into core
so hook POSTs and the lookup share one key. finalize_session::run is split
into finalize() and report printing so the run reports on stderr. The
harness's exit code is kept; finalizing is bounded by a timeout, Ctrl-C
skips it, and a failure prints the exact finalize-session command.
The lookup matches the ended session too (as --reopen does), because these
harnesses keep capturing under the same id after a resume; a re-end with
nothing new is a no-op on the server.
Closes#941
A named profile (`--profile`, `OMP_PROFILE`, or the legacy `PI_PROFILE`) owns
`~/.omp/profiles/<name>/agent` and ignores `PI_CODING_AGENT_DIR`, as OMP does;
`install-hooks` wrote the extension into `PI_CODING_AGENT_DIR` instead. Profile
names are normalized and refused like OMP does, `PI_CONFIG_DIR` renames the
`~/.omp` root, and on Linux and macOS sessions move to `$XDG_DATA_HOME/omp`
when that directory exists. `install-mcp`, auto-wire, session import,
`backfill`, `doctor` and the `uninstall` sweep follow the same agent dir.
Refs #820
Forward-merge the 2.4.x fix batch (#877,#891,#893,#888,#820,#885,#886,#884,#890,#894,#895) so main stays a subset of release/2.5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
# Conflicts:
# CHANGELOG.md
# crates/ai-memory-cli/src/commands/run.rs
# crates/ai-memory-store/tests/suite/multi_session.rs
`ai-memory uninstall` left the `ai-memory run` auto-wire sentinels behind, so
the next managed launch skipped wiring and captured nothing. Removing hooks or
MCP now deletes every sentinel and lists them in the dry-run plan.
`--only mcp|instructions|skills` no longer deletes the stored hook bearer the
still-installed hooks read.
Refs #820
`ai-memory run crush`, `backfill` and `doctor` looked for sessions only in
`<cwd>/.crush/crush.db`; they now resolve the data directory as Crush's
`setDefaults` does. The managed context packet no longer drops the `CRUSH.md`
and `AGENTS.md` Crush loads by default, accepts every global `crush.json`
Crush accepts, and carries the global `crushrc` over.
Two fresh managed launches in one checkout could import each other's
transcript. A session a hook in the child linked under the run id now wins
when this checkout's store holds it, and Crush, which has no hooks, claims
only the one top-level session the run created, importing nothing with a
warning when another launch created one too.
Refs #820
A Kiro v3 resume that falls back to the default session store drops
`KIRO_HOME` from the child, but auto-wire still installed hooks and MCP under
`KIRO_HOME`, so the resumed session ran without either. Auto-wire now wires
the default home that resume reads.
Refs #820
`ai-memory run --env/--env-file` values reached the spawned harness and
native-session resolution but not first-launch auto-wire, so hooks and MCP
landed in the default config home, and a sentinel keyed only on agent and
version skipped a second account on the same version. Auto-wire now resolves
every install target from the launch environment, the Codex MCP entry follows
`CODEX_HOME`, and the sentinel also keys on the resolved hook and MCP paths.
Fixes found on the same paths: OMP profile and PI_CONFIG_DIR resolution,
blank relocation values, uninstall leaving sentinels behind, the Crush
context packet (default context files and the global crushrc), the Crush data
directory lookup, session discovery with concurrent launches in one checkout,
and Kiro v3 resume wiring. Spawned-server test fixtures now survive losing a
free port to another socket.
Refs #820
`managed_runs.native_session_id` starts as the workstream's current session
when a run is prepared, and a hook in the launched harness replaces it through
the link path. A link that repeated the prepared session left no trace, so
after exit `ai-memory run` fell back to the newest session in the checkout,
which a concurrent launch there may own.
Migration V67 adds `managed_runs.native_session_linked_at`, stamped by
`link_native_session` for its own run and dropped by `finish` when the session
changes. The run status reports it as `native_session_linked` (an older server
omits it and reads as false), and the launcher imports a linked session when
this checkout's store holds it, never falling back to one it set aside. A
process the child starts inherits the run id, so a session from another
checkout is refused; OpenCode is checked by the session's recorded directory.
Refs #820
`ai-memory run` auto-wires hooks whose command is the wrapper's native
client path. Keeping that client under ~/.cache meant a cache flush broke
every hook of Claude Code, Codex, Kimi Code, Command Code, Kiro CLI v3,
Grok and Antigravity CLI. Keep it in $XDG_DATA_HOME/ai-memory instead.
The wrapper also deleted the release's hooks/ bundle after extraction,
so auto-wire failed with "could not locate hooks directory" for
script-based harnesses on a host where install-hooks never ran. Keep
the bundle beside the client, where install-hooks looks for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PoLrSBy2WePNoHhD8BJHD
Users who launch harnesses via env-wrapping aliases (`env
CLAUDE_CONFIG_DIR=... claude`) had no first-class way to pass that
environment through `ai-memory run`. Add a repeatable `--env KEY=VALUE`
and an `--env-file <path>` (blank lines and `#` comments skipped),
wrapper-owned like `--yolo`/`--executable` so they must precede the
harness name. A later `--env` overrides a same-key `--env-file` entry;
values are taken literally.
The resolved environment reaches both the spawned harness `Command`
and `build_launch_plan`'s own native-session-store resolution (new
`build_launch_plan_with_env`, layered in front of the real process
environment), so a caller-scoped `CLAUDE_CONFIG_DIR` override no longer
requires exporting the variable into the invoking shell for
ai-memory's own hook/session resolution to agree with the harness.
This is the tractable first slice of #820; named launch profiles /
persisted configs remain out of scope.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
Document that ai-memory run resolves the native session dir and installs
hooks from its own environment, so a custom CLAUDE_CONFIG_DIR set only
inside a harness wrapper causes a split-brain and "native transcript
import failed" (#820). Export per-account config dirs before invoking
ai-memory run. The launch-configuration ergonomics half of #820 remains a
separate design-first feature.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
Managed launch is the recommended way to start a harness, so it should be the
path that makes capture "just work" — but it didn't: `ai-memory run kimi`
captured nothing if the Kimi hooks/MCP had never been manually installed.
`ai-memory run <harness>` now auto-installs that harness's ai-memory lifecycle
hooks and MCP server the first time it launches the harness, if they are not
already wired. It runs before the child spawns (so the harness picks up the
fresh hooks), is idempotent and one-time per harness + binary version (a
per-agent sentinel under <data_dir>/autowire-state/, keyed on the version so an
upgrade re-stages the fresh bundle), and is best-effort — an install failure
warns with the manual command and the harness still launches. It reuses the
existing install-hooks / install-mcp apply paths, which preserve unrelated user
config. Harnesses with no installer support (Crush) are skipped cleanly; Pi
wires hooks but has no MCP client to write.
On by default; opt out with `ai-memory run --no-autowire` (accepted before or
after the harness, so it is never forwarded to the child), AI_MEMORY_RUN_AUTOWIRE=false,
or run_autowire = false. Manual install-hooks/install-mcp remain for harnesses
never launched through run. Documented as the preferred launch path ("if in
doubt, run with ai-memory") in the README and managed-workstreams docs.
Tests: harness->AgentChoice mapping completeness (+ Crush skip, Pi no-MCP),
sentinel keying by agent+version, a real install-through-the-installers test
asserting hooks + MCP land AND unrelated user config is preserved AND a gated
re-launch is byte-identical (paths injected so it never touches real $HOME),
unsupported-harness-writes-nothing, present-sentinel-short-circuits, and
--no-autowire parsing in both positions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
Multi-account users (e.g. Corporate + Personal) name a PATH wrapper
script per account (claude-corp, claude-personal, ...) and select one
with --executable, since PATH resolution bypasses shell-only aliases.
Previously the harness positional only accepted the fixed claude/
claude-code names, so anything else was rejected as an unknown value
before --executable even got a chance to matter.
parse_run_harness_choice now falls back to a case-insensitive `claude`
prefix match after the normal ValueEnum lookup, so any claude*-prefixed
name (claude-corp, claude-personal, claudex, ...) resolves to
RunHarnessChoice::Claude. Every other unknown value is still rejected
with the existing "expected one of" error. One alias mechanism, not
two: no separate claude-any special case.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every user-facing doc was graded on four axes (what / why / when-to /
real example, including when-NOT-to). Fixes for the highest-impact
gaps:
- managed-workstreams: 'Do you need this?' - hooks + handoffs already
cover the quit-Claude-open-Codex case; managed runs are for native
resume surviving a harness switch. Skip guidance included.
- auto-improvement-loop: a 60-second user-facing top for a feature that
is on by default - what auto-approve means, the explicit
require_approval=true recommendation for shared/team servers, cost
shape, and what a staged proposal sidecar contains. Research prose
retained below the fold.
- okf: opens with the user payoff (hand a bundle to someone without
ai-memory; export-okf one-liner; what the receiving side sees)
before the conformance design.
- temporal: when to reach for as_of (audits/post-mortems; plain
memory_query is right 99% of the time) plus a worked
postgres-then-migrated example with both results.
- typed-edges: the gotcha->fixes->contradicts->lint loop as a concrete
scenario, and 'plain wikilinks remain the default' skip guidance.
- experience: a sample staged cross-session proposal.
- config template: consolidation and slots blocks say why/when, not
just what.
Accuracy bugs found by the same sweep: auto-scope's config snippet
still called mode="single" the default (per_actor since v1.39); ZCode
was listed twice with conflicting statuses in both support matrices
(it has MCP #529 AND hooks #532 - merged to one Supported row); the
README docs index listed ROADMAP-2.0 twice.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
* feat(cli): add interactive workstream resume picker
* chore(changelog): restore the blank line before [1.36.0]
The branch dropped one blank line separating the end of [1.37.0] from the
[1.36.0] heading. Whitespace only, but it sits inside an already-released
section, so `check-changelog-frozen.sh` rejects it - correctly: the guard
cannot tell a stray edit from a rewritten entry, and should not try.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
---------
Co-authored-by: AkitaOnRails <fabioakita@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Workstream names were fixed at `run --new` time and had no correction path,
so a typo outlived the work it labelled and the only escape was starting a
new workstream and abandoning the ledger attached to the old one.
The rename selects by current name or by the stable id `workstreams` prints,
resolving the same (workspace, project, repository, worktree) identity `run`
selects with. Both selectors repeat the checkout predicate: `workstreams.id`
is globally unique, so without it a caller holding an id from another
checkout could retitle a workstream their request never named. An id outside
the resolved scope reads as absent rather than renamable.
It is metadata only. `workstream_events`, `managed_runs`, and
`workstream_native_sessions` all key on `workstreams.id`, so the rename is a
single-row update with nothing to cascade, and `selected_at` and
`updated_at` are deliberately left untouched — relabelling is not activity.
Neither the discovery listing order nor the workstream a bare `ai-memory
run` resumes moves as a side effect of fixing a typo.
The destination is validated exactly like a `--new` name and refused with a
named conflict when another workstream in the same checkout already holds
it, turning the UNIQUE constraint into a typed error instead of a bare
SQLite failure. Renaming a workstream to the name it already has writes
nothing and is not an error.
The new `/workstream/rename` route requires NormalWrite rather than the
NormalRead its sibling discovery read uses, and resolves scope through
`lookup_existing_scope` so a rename never creates the workspace or project
it names. The Docker shell wrapper routes the command through its native
host client alongside `run`, `show`, `continue`, and `workstreams`, since
repository identity is a host resource the helper container cannot see.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: AkitaOnRails <fabioakita@gmail.com>
List the managed workstreams that `run --workstream` can select from the
current checkout, so branching between lines of work no longer requires
remembering names or reading the database by hand.
The list resolves the same (workspace, project, repository, worktree)
identity `run` selects with, puts the current selection first, then orders
by recent activity. Rows carry the linked harnesses and the stable
workstream id that `workstream-search --workstream-id` accepts.
Discovery is a read of workstream metadata only: checkout paths, repository
and worktree fingerprints, and native session ids never travel back to the
client. The new `/workstream/recent` route requires NormalRead, resolves
scope through `lookup_existing_scope` (no-create, fails closed), and bounds
the limit server-side.
The Docker shell wrapper routes the command through its native host client
alongside `run`, `show`, and `continue`, since repository identity is a host
resource the helper container cannot see.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: AkitaOnRails <fabioakita@gmail.com>
Herdr identifies a pane's agent from the foreground process, falling back
to matching the agent's own screen output. `ai-memory run` defeats the
first half: the foreground process is the wrapper and the agent is its
child, sharing one process group whose leader is `ai-memory`. So the pane
shows no agent until the harness paints a title Herdr recognizes, which
can be well after launch and may never happen for a harness matching no
manifest.
Deliberately not fixed in code. Herdr scopes its `HERDR_AGENT` hint to the
pane's foreground process, and a process cannot amend its own environment
after exec, so the wrapper cannot describe itself from the inside. Setting
the variable on the agent it spawns would put it somewhere Herdr does not
look — code that reads like a fix while doing nothing.
Documents the two things that do work: naming the agent on the command
(`HERDR_AGENT=codex ai-memory run codex`), and installing Herdr's own agent
integration, which reports identity and lifecycle state over Herdr's socket
and is authoritative regardless of process detection. Notes that the two
hook mechanisms write to separate files and that ai-memory's installer
preserves foreign entries.
No behavior change; docs only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add `ai-memory run kiro` (alias `kiro-cli`) for the AWS Kiro CLI (#356),
deliberately scoped to the binary's default v2 agent engine.
Cross-engine resume is prevented by construction, not detection: Kiro v3
sessions occupy a separate id space the v2 engine cannot resume (the
resume hint printed after a v3 session silently starts a fresh v2
session unless --v3 is added), and the v3 persisted-session format is
not publicly documented — so any `--v3`, `--mode`, or non-v2
`--agent-engine` selection turns the whole invocation into an unmanaged
passthrough with argv byte-identical. Headless `--no-interactive` runs
persist to Kiro's v1 SQLite store rather than the v2 session files this
adapter reads, so they pass through too, as do the one-shot
list/delete/model flags and every root utility subcommand except `chat`
(list verified against kiro-cli 2.16.0 --help-all).
Launch planning: session ids are server-assigned, so a fresh launch
injects nothing and the session is linked by the session-start hook or
discovered post-exit; a returning session appends `--resume-id <id>`
after user arguments (accepted at the root and on `chat`, verified on
the 2.16.0 binary). Explicit `-r/--resume`, `--resume-id`,
`--resume-picker`/`--list` selections always win. Kiro's `-v` is
verbose, not version, and stays managed.
Discovery/import: the interactive store is flat —
`$KIRO_HOME/sessions/cli/<uuid>.json` metadata + `<uuid>.jsonl` event
stream — and the adapter matches checkouts on the metadata `cwd`,
requiring the metadata session_id to agree with the file stem. The
parser imports the versioned v1 envelope (Prompt / AssistantMessage
with toolUse parts / ToolResults), ignores unknown record kinds for
forward compatibility, and annotates unknown envelope versions and
non-text parts as explicit losses. The stream is not verified
append-only, so it shares Kimi's rewrite tolerance: a prefix-hash
cursor that resets and replays on in-place rewrites, with stable
line-hash record ids deduplicating server-side.
`--yolo` maps to the official v2 flag `--trust-all-tools`, treats
`-a`/`--trust-tools` as already-satisfying (an explicit narrower trust
set is never widened), and maps nothing on non-v2 engines — v3 replaced
the flag with permissions.yaml — with a stderr notice.
Kiro joins the automatic bare-`run` pool (client and server side) and
the deterministic phase of the acceptance script, including a
byte-identical `--v3` passthrough check; the real-harness phase skips
kiro with an explanation because scripted headless turns cannot write
the store the adapter reads. Session-file shapes derive from public
kiro-cli 1.29.x references and are documented as pending revalidation
on a live logged-in install; the CLI argument contract was verified
against kiro-cli 2.16.0 locally.
Closes#356.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJvj7D7czyZzqk4pDXgLxY
Bare `ai-memory run` already continues the current checkout, but its
workstream lookup is keyed by (workspace, project, repo fingerprint,
worktree fingerprint), so it requires a `cd` into the project first.
Nothing resolved "the checkout I last worked in" from an arbitrary
directory.
`continue` fills that gap without new server state. Every successful
managed prepare already refreshes `ProjectLink::linked_at` in the
client-local registry, so the newest link is the last checkout used.
The command orders links by that stamp, revalidates the newest one, and
then delegates to the same bare-mode launch with the resolved directory,
including automatic harness selection.
Selection is revalidated on this host before anything launches. The
recorded path must still canonicalize to itself, which rejects a checkout
that moved or was replaced by a symlink, and it must still resolve to the
same (workspace, project), which prevents attaching a session to a
directory that now belongs to a different project. A link failing either
check is reported on stderr and skipped in favor of the next-newest, and
the chosen project and path are always printed, so a resume never
silently lands somewhere else. Stored strings are stripped through the
existing terminal filter before display.
Native harness arguments and `--executable` are rejected at parse time
because bare mode does not know which harness it will pick.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ai-memory run antigravity` (aliases `antigravity-cli`, `agy`) joins an
Antigravity session to the same workstream as the Claude Code or Codex
sessions on the same checkout.
`agy` accepts no caller-chosen id for a new conversation, so a fresh
launch injects nothing and the id is linked by the hooks or discovered
afterwards; a linked resume passes `--conversation <id>`. `--continue`
and `-c` are respected as explicit user choices and never overridden.
`--yolo` maps to `--dangerously-skip-permissions`, and the utility
subcommands pass through without a selector.
Conversation discovery reads the per-conversation SQLite databases under
`~/.gemini/antigravity-cli/conversations/`: the id is the file name, so
locating one needs no scan, and the workspace comes from two
self-describing protobuf fields in `trajectory_metadata_blob`. Only
conversations opened on the current directory are offered, and a
database whose metadata is shaped differently — an older or newer `agy`
— is skipped rather than failing the listing. The field walk is bounds
checked throughout and reads nothing else from the store.
Step payloads are undocumented, unversioned protobuf whose step-type
enum changes between releases, so conversation text is deliberately not
decoded: the visible-event ledger for this harness comes from
lifecycle-hook capture, and transcript export fails with a message
saying so. Antigravity is not part of the no-argument auto-detection
pool.
The server-side managed-run allowlist accepts the agent kind too;
without it the launch is rejected after the user has already chosen a
project and a harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>