8000 Tags · alvinhui/ai-memory · GitHub
[go: up one dir, main page]

Skip to content

Tags

Tags

v1.29.0

Toggle v1.29.0's commit message
Release v1.29.0

- Corrected the Pi/OMP extension filenames in `README.md`, `docs/install.md`,
  and `docs/mcp-install.md`, which still described the pre-rename
  `ai-memory.ts` after the agent-distinct `ai-memory-pi.ts` /
  `ai-memory-omp.ts` split. Following those instructions by hand created the
  very duplicate-load the split exists to prevent, since each agent loads
  every `*.ts` in its extensions directory. Documented `--profile` /
  `OMP_PROFILE` and the `PI_CODING_AGENT_DIR` precedence alongside them.
- The lint `stale` threshold is now derived from the operator's `[decay]
  lambda` instead of a hard-coded 30 days (akitaonrails#426). The rule only fires on
  episodic pages with zero accesses, and for those the decay score reduces
  exactly to `salience * exp(-lambda * age)` — the reinforcement term carries
  `ln(1 + access_count)`, which is zero — so the lint was already measuring
  decay, just against a constant rather than against `lambda`. Slowing decay
  made the two diverge: at `lambda = 0.008` real eviction lands near day 201
  while the lint still called a page stale on day 31, and because a report
  page is written whenever any finding exists, one page nobody intended to
  read produced a new `_lint/<date>.md` every day, forever. The threshold is
  now `0.6 / lambda`, which is **exactly 30 days at the default
  `lambda = 0.02`** — unchanged for anyone who never tuned decay — and 75
  days at `0.008`. An invalid or non-positive lambda falls back to the
  historical constant rather than silencing the rule.
- `bin/deploy` now refuses to push a single-architecture build over a tag that
  already resolves to a multi-architecture manifest (akitaonrails#427). It builds for the
  architecture of the machine it runs on, so deploying a homelab from an x86
  workstation to `IMAGE=akitaonrails/ai-memory:latest` replaced the
  amd64+arm64 manifest CI had published with an amd64-only image, and every
  arm64 host pulling `:latest` failed with `exec format error`. The shipped
  `bin/deploy.env.example` also defaulted `IMAGE` to `:latest`, so following
  the documented setup led straight into it; it now defaults to a private
  `:homelab` tag and explains that release tags are published by CI only.
- `install-hooks` now writes agent-distinct extension filenames
  (`ai-memory-pi.ts`, `ai-memory-omp.ts`) instead of both agents sharing
  `ai-memory.ts`, so a Pi install and an OMP install no longer overwrite each
  other when `PI_CODING_AGENT_DIR` points them at one home. A superseded
  `ai-memory.ts` is removed only when it carries this tool's generated marker
  *for that same agent* — a file you wrote yourself, or the other agent's, is
  left alone and reported. OMP profiles are supported via `--profile` or
  `OMP_PROFILE`; `PI_CODING_AGENT_DIR` takes precedence over a profile, since
  it names the agent directory outright.

  Note distinct filenames stop the overwrite but do **not** make a shared
  directory safe: each agent loads every `*.ts` it finds there, so installing
  both into one home captures every event twice, once per agent identity.
  That collision is upstream — both agents read the same environment
  variable — so `install-hooks` now warns when it detects one and points at
  the two ways out. (akitaonrails#421)
- `install-hooks --agent pi`, `--agent omp`, and `uninstall` now honor
  `PI_CODING_AGENT_DIR`, instead of always writing to `~/.pi/agent/extensions/`
  and `~/.omp/agent/extensions/`. Both agents relocate their whole agent
  config home (`~/.pi/agent` / `~/.omp/agent`) through that variable, so on an
  install with it set the extensions landed where the agent never loads them:
  the install reported success and capture silently did nothing. ai-memory
  already honored the variable when resolving Pi/OMP transcripts, so the two
  halves of one install disagreed about where that home was. Installs without
  `PI_CODING_AGENT_DIR` set are unaffected. The manual (non-`--apply`)
  instructions now name the resolved path too, instead of always printing the
  `~/.pi/agent` / `~/.omp/agent` default. (akitaonrails#411)

- CI now rejects a CHANGELOG version section that repeats a `### ` heading
  (`scripts/check-changelog-sections.sh`). `bin/release` copies
  `[Unreleased]` into the new version verbatim, so a duplicated heading ships
  release notes with the entries split across two identical headings — valid
  Markdown, invisible in review. The same defect was found in four already
  released sections (1.19.0, 1.13.0, 1.4.0, 1.1.1) and folded in the same
  commit; no entry text was changed.
- New `llm_timeout_secs` config key (env `AI_MEMORY_LLM_TIMEOUT_SECS`, or
  `llm_timeout_secs = 900` in config.toml) overrides the per-request timeout
  every chat provider applies to its HTTP calls — previously hardcoded at
  300s per provider. Slow hosted gateways (observed with free aggregator
  tiers whose long completions exceed five minutes) failed every request with
  `http: error sending request` once generation crossed the ceiling, so LLM
  consolidation exhausted its retries and left heuristic pages behind; now
  operators raise the bound at startup instead. The Copilot token exchange
  is bounded by the same value, and the 300s default is unchanged ([akitaonrails#435]).
- Added a `flake.nix` so NixOS and Nix users can build and run ai-memory
  without the Rust toolchain or Docker: `nix build`, `nix run . --
  --version`, or `nix develop` for a dev shell. The build is self-contained
  (SQLite bundled, libgit2 vendored, rustls with webpki-roots — no OpenSSL,
  no system-library hunting). The flake skips the packaging test suite
  because those tests exercise the Docker-wrapper shell script and need
  `docker`/`podman` on PATH; the rest of the workspace tests can be run via
  `nix develop -c cargo test --workspace`. Flake inputs are pinned to
  explicit revisions rather than floating branches, so the build is
  reproducible, and a `nix` CI job builds the flake and runs the resulting
  binary whenever a Nix build input changes plus weekly, so the packaging
  cannot rot unnoticed. ([akitaonrails#405])
- New `strip_root_combinators` config flag (env `AI_MEMORY_STRIP_ROOT_COMBINATORS`,
  or `strip_root_combinators = true` in config.toml) strips root-level
  `anyOf`/`oneOf`/`allOf` from MCP tool input schemas on every `tools/list`.
  Generic MCP clients such as OpenCode and Cursor never send the `?flavor=`
  marker, yet forward schemas verbatim to strict upstreams (Moonshot, Bedrock)
  that reject root combinators with a 400 — this gives operators behind such an
  upstream a mode-independent opt-in. Runtime "exactly one of" validation is
  unchanged ([akitaonrails#412]). The key is documented in the generated
  `config.default.toml` so it is discoverable without reading the source.

- Changed the default model for the `gemini` provider from `gemini-2.5-flash`
  to `gemini-3.5-flash`. Google has scheduled the 2.5 Flash family for
  retirement — the Gemini API deprecation table lists `gemini-2.5-flash-lite`
  for **October 16, 2026** (Vertex AI and the Agent Platform list October 20;
  ai-memory talks to the Gemini API, so the earlier date is the one that
  applies). The thinking-budget workaround still covers the legacy models.
  Note it is deliberately **not** applied to `gemini-3.5-flash-lite`, which
  rejects `thinkingConfig` outright with HTTP 400 — unlike
  `gemini-2.5-flash-lite`, which accepts it — so that model omits the field
  and the e2e smoke test keeps `gemini-2.5-flash-lite` as its second
  variant. (akitaonrails#423)
- Documented OrcaRouter through the existing `openai-compat` provider instead
  of adding a redundant provider type, including the endpoint, model, and API
  key mapping needed for deployment. (akitaonrails#410)

- The `open_questions` field in automatic SessionEnd handoffs now uses
  multi-signal heuristics instead of blindly copying the last user prompt.
  Trailing acknowledgments ("ok", "thanks", "好的") are filtered; a
  question-mark-terminated final prompt is tagged as an unresolved question;
  file-tool activity without a subsequent Stop produces an advisory to check
  the working tree; and a mid-task exit (Stop without SessionEnd) is flagged
  so the receiver knows the previous session did not finish cleanly. (akitaonrails#425)

v1.28.1

Toggle v1.28.1's commit message
Release v1.28.1

- Updated `h2` 0.4.14 → 0.4.16 for [RUSTSEC-2026-0258](https://rustsec.org/advisories/RUSTSEC-2026-0258),
  an unbounded-empty-DATA-frame denial of service. `h2` is a transitive
  dependency of the HTTP stack ai-memory's own server runs on (axum/hyper), so
  a `--transport http` deployment was reachable by this, not only outbound
  client use. Lockfile-only change.

- The built-in sanitizer now redacts seven further credential shapes, completing
  three vendor families it already covered only in part. **GitHub:** every
  token prefix — `gho_` (OAuth, the form `gh auth login` writes to disk),
  `ghu_`, `ghs_`, `ghr_` — where previously only `ghp_` and `github_pat_`
  were caught. **AWS:** `ASIA…` STS temporary key ids alongside `AKIA…`.
  **Stripe:** `rk_live_` restricted keys alongside `sk_live_`. Plus three
  providers with no prior coverage: **Google OAuth refresh tokens** (`1//…`,
  longer-lived than the `AIza…` keys already handled — they mint access
  tokens until revoked), **Meta / Facebook Graph access tokens** (`EAA…`,
  ad-account, page and business-management scope), **Telegram bot
  tokens** (`<bot-id>:…`, covering both the `AA…` form every issued token uses
  and the shape Telegram's own docs publish), and **GoHighLevel Private
  Integration Tokens**
  (`pit-…`, which do not expire until manually revoked). These run in
  `BUILTIN_PATTERN_STRS`, so unlike
  operator `[sanitize].extra_patterns` they also scrub client-side, before an
  excerpt reaches the local spool or the wire.
  The AWS rule is anchored to the published twenty-character format rather
  than an open tail, because `ASIA` is also an English word and an open
  tail redacted ordinary uppercase text such as `ASIAPACIFICREGION`.

  Behaviour change: text matching these shapes is now replaced with
  `[REDACTED]` where it previously persisted verbatim. An operator who
  *wants* one of them kept visible (for example a public bot id) can add it
  to `[sanitize].allowlist`.

v1.28.0

Toggle v1.28.0's commit message
Release v1.28.0

- `install-hooks --agent codex` and `uninstall` now honor `CODEX_HOME`, instead
  of always writing to `~/.codex/hooks.json`. Codex loads hooks from its
  configured home, so on an install with `CODEX_HOME` set the hooks landed
  where Codex never reads them: the install reported success and capture
  silently did nothing. ai-memory already honored the variable when resolving
  Codex transcripts, so the two halves of one install disagreed about where
  that home was. Installs without `CODEX_HOME` set are unaffected.

- Added `ai-memory move-session <session-id> --to <project> [--to-workspace]
  [--pages move|regenerate] [--confirm] [--force] [--create]`, its batch form
  `--from-project <project> [--from-workspace]`, and `POST /admin/move-session`
  to move one session (or every session touching a project) to another
  project, in the same or another workspace. One transaction per session
  re-stamps the `sessions` row, its `observations` (wherever they landed), the
  `handoffs` it produced, its consolidation jobs, auto-improve runs and
  scheduler claim, and its `sessions/<id>.md` page: `--pages move` (default)
  carries every version and
  the file along (409 when the destination already has that page), `--pages
  regenerate` retires the source page (clearing the session's
  `summary_page_id`) and removes its file so the next consolidation rewrites
  it in the destination. Without `--confirm` the server
  runs the same transaction and rolls it back, so the summary is an exact dry
  run and the CLI prints the command to apply (with `--create` the dry run
  reports `would_create_project` and creates nothing). An open session, a pending or
  running consolidation job, or (batch) the active source project refuse with
  409 unless `--force`. `sessions.cwd` stays as recorded (the response warns
  when its basename is not the destination), and `auto_improve_proposals`,
  `entities` and `page_feedback` are not re-stamped. A session already rooted
  in the destination is re-homed instead of refused: its row stays
  (`session_moved: false`, open-session guard not applied) and only its rows
  still lying in other scopes are gathered (`page: already in destination`
  when its page needs nothing), and the batch form enumerates sessions with a `sessions` row in
  the source OR observations stamped into it, so a phantom project holding
  only observations of sessions rooted elsewhere can be emptied. New
  admission op `move_session`. The dry run and the `/admin/move-session`
  response name every scope the move would drain (`source_scopes`), because
  gathering by session id can empty a project the caller never named and
  moving back does not restore the original split. (akitaonrails#402)
- Added a read path for one session's raw hook observations, before any
  consolidation: the MCP tool `memory_read_session_observations` and the
  `/api/v1` routes `GET .../projects/{project}/sessions` and
  `GET .../sessions/{session_id}/observations`. Both return only the rows that
  landed in the resolved `(workspace, project)`, report `elided_other_scope`
  for a session that crossed repositories, and apply the same owner filter as
  handoffs; a session id from another project or operator reads as not found.
  Pagination is `limit`/`offset` with `total` (default 50, max 200; the
  session list defaults to 20, max 100), `order` is `asc` or `desc`, `kinds`
  and a full-text query narrow the rows, and `body_max_chars` (default 4000,
  `200..=16384`) caps each body with a visible truncation marker. The MCP tool
  reads the latest completed visible session when `session_id` is omitted
  (akitaonrails#401).

v1.27.0

Toggle v1.27.0's commit message
Release v1.27.0

- Added `[routing] mid_session` to choose how a mid-session event is attributed
  once the agent's cwd has moved. `follow-cwd` (default) keeps the historical
  per-event resolution; `sticky` keeps the session's project wherever the agent
  wanders, closing the cross-repo `cd` case that split one session's raw record
  across two projects. Lifecycle hooks now tag each `project` override with its
  provenance (`project_src=marker` or `project_src=repo-root`), so `sticky`
  overrules a host-derived repo name while a `.ai-memory.toml` marker still
  wins in both modes. Clients older than this release send no provenance and
  keep their overrides authoritative (akitaonrails#394).
- Added `[auth].secure_cookie` for HTTPS reverse-proxy deployments to mark
  `/web` browser authentication cookies `Secure` while preserving plain-HTTP
  loopback compatibility by default (akitaonrails#396).

- Unauthenticated HTTP binds beyond loopback now fail closed before accepting
  requests. Intentional insecure LAN use requires `--allow-insecure-no-auth`;
  authenticated non-loopback HTTP remains available with its TLS warning
  (akitaonrails#396).
- `/api/v1` internal failures now return the fixed body
  `{"error":"internal server error"}` instead of the source error chain, which
  could expose data-directory paths and configuration to a browser. The
  detailed cause is logged server-side (akitaonrails#396).
- The `/web` browser authentication cookie is now `SameSite=Strict` rather than
  `SameSite=Lax`, so it no longer rides top-level cross-site navigations into
  the UI. Existing sessions stay valid (akitaonrails#396).

- Mid-session hook events that resolve to a different project than their
  session — the ordinary result of an agent `cd`-ing into another checkout —
  are recorded again instead of being dropped as a session-UUID collision.
  Owner and agent still identify a session and a mismatch there remains
  terminal; a `SessionEnd` naming a foreign scope is still dropped without
  ending the session (akitaonrails#396).
- Hook session UUIDs now reject cross-owner reuse atomically before ingest-key,
  observation, summary, handoff, or end-state mutation; explicit root
  `finalize-session --all-owners` recovery remains available (akitaonrails#396).
- Newly created data directories, configuration files, SQLite databases,
  managed-workstream segments, and downloaded backups now receive owner-only
  Unix permissions before sensitive content is written, independent of the
  ambient umask. Existing installations are left unchanged; Windows relies on
  filesystem ACLs (akitaonrails#396).
- Under the `repo-root` project strategy, mid-session events whose cwd sits
  outside any git repository and any `.ai-memory.toml` marker (agent scratch
  directories, `/tmp`, data folders) now inherit the session's project instead
  of minting phantom basename projects (`scratchpad`, `data`, `tmp`, ...). The
  host-side hook resolves the repository root itself and sends
  `project=<root name>`, so a missing override already proves the cwd is
  unresolvable; session-sticky attribution now accepts these events even
  outside the session's cwd subtree, with the broad-anchor guards unchanged.
  Deliberate rescopes keep working: git checkouts and markers arrive as
  explicit overrides and never reach stickiness, and the default `basename`
  strategy is untouched (akitaonrails#394).

v1.26.1

Toggle v1.26.1's commit message
Release v1.26.1

- Forget-sweep decay now removes the authoritative Markdown file while
  conditionally tombstoning the selected page, preventing reconciliation from
  resurrecting evicted content. Aged cleanup recognizes rewritten heads,
  deletes their complete `supersedes` ancestry, ignores lifetime access counts,
  and preserves a newer page recreated at the same path. The corrected scoped
  tombstone index is added by a new migration rather than rewriting history
  (akitaonrails#391).
- The `pages_fts_rows` and `observations_fts_rows` status counters now count
  indexed documents instead of content-table rows. Both FTS tables use external
  content, so `SELECT COUNT(*)` against them was answered from `pages` /
  `observations` and the `fts: N/M` health pair could never diverge, no matter
  how far the index had drifted (akitaonrails#392).

v1.26.0

Toggle v1.26.0's commit message
Release v1.26.0

- Added MCP-only Swival CLI support. `install-mcp --client swival --apply`
  merges ai-memory's native HTTP entry into the project-root
  `.swival/mcp.json`, and `uninstall` removes only the matching ai-memory
  entry while preserving sibling servers. Lifecycle capture and managed
  workstreams remain unsupported because Swival's callback contract does not
  expose a stable session identifier (akitaonrails#385).

- Lifecycle-only sessions containing only `SessionStart` / `SessionEnd` now
  close without generating an empty session page, fallback handoff, or LLM
  consolidation job. Startup handoff claims are bound to the native receiver
  session when clients expose one; if that receiver exits without substantive
  work, the same transaction that ends it returns the accepted handoff to the
  open pool. Tool-bearing zero-prompt sessions remain substantive. Shipped
  Claude Code, Codex, OpenCode, Command Code, Kiro, Antigravity, Devin, Cursor,
  Gemini CLI, and Kimi Code delivery paths forward their available session ids
  so an empty receiver cannot permanently consume real work (akitaonrails#386).
- The `bin/ai-memory` wrapper now detects rootless mode and SELinux under
  podman, so host-file commands stop failing with `Permission denied (os error
  13)` on podman-based distros. Both the `-u 0:0` remap and `--security-opt
  label=disable` were decided from `docker info --format
  '{{.SecurityOptions}}'`, a Docker-only field that podman cannot evaluate;
  the swallowed error left both gates off exactly where they were needed.
  Podman's `.Host.Security.*` keys are consulted when that probe comes back
  empty. `bootstrap` is now covered as well: it only reads host files, but an
  unmapped UID blocks reads just as hard, and it degraded silently to "no
  `.git` found" before dying. Restore archives, explicit config paths, and
  commands using a host-backed `AI_MEMORY_DATA_DIR` now receive the same
  host-file treatment (akitaonrails#388).

v1.25.0

Toggle v1.25.0's commit message
Release v1.25.0

- Added version-aware Kiro CLI v3 managed workstreams. `ai-memory run kiro
  --v3` reads the authenticated 2.16.2 nested `session.json` / `messages.jsonl`
  store through a visible-event allowlist, persists the incompatible engine
  flavor in its cursor, resumes with exact `--v3 --resume-id`, and joins bare
  automatic selection alongside v2 without cross-resuming either store. Plain
  returning Kiro launches recover the linked engine transparently; v3 wrapper
  `--yolo` adds no flag because Kiro replaced `--trust-all-tools` with
  `permissions.yaml`. A targeted compatibility path also handles Kiro 2.16.2
  writing v3 sessions to default `~/.kiro` despite custom `KIRO_HOME`: only a
  resume proven to live in that fallback drops the override for its child
  process. The optional deterministic acceptance runner now covers fresh,
  resume, and import round trips for both engines. The server automatic-harness
  validator now accepts the Kiro candidate pool advertised by the client,
  preventing bare `ai-memory run` from rejecting a discovered Kiro session
  before launch (akitaonrails#356).
- Added first-party Command Code MCP and stable lifecycle-hook support.
  `install-mcp --client command-code` merges the documented user-scope HTTP
  entry; `install-hooks --agent command-code` preserves existing settings and
  registers only `SessionStart`, `PreToolUse`, `PostToolUse`, and `Stop` with
  native session attribution, capture exclusions, and startup handoff
  injection. `ai-memory run command-code` now adds v3 checkout-scoped
  transcript discovery, exact `--session <uuid>` resume, automatic harness
  selection, native `--yolo` translation, and a visible-event allowlist that
  retains branch parent ids and summaries while excluding hidden reasoning,
  images, custom/Mod records, and provider metadata. Deterministic acceptance
  covers fresh, resume, and incremental-import round trips. Experimental unsandboxed Mods remain
  excluded, and the turn-only Stop boundary is documented with the manual
  finalizer workflow (akitaonrails#373).
- `ai-memory finalize-session --session-id <uuid>` targets exactly one open
  session instead of "the latest open one for this agent+scope". Agents with
  no true SessionEnd hook (Kiro CLI, Codex, Antigravity CLI) rely on
  `finalize-session` to synthesize the summary+handoff, and the
  newest-first default breaks down with several concurrent sessions for the
  same agent in one project — e.g. multiple terminal tabs each running Kiro
  CLI against the same repo — where it can close out a still-active session
  instead of the one that actually finished. Backed by a new optional
  `session_id` filter on `GET /admin/open-sessions`, composed with (not
  bypassing) the existing owner filter: a session id belonging to another
  operator is still unreachable without `--all-owners`, same as the default
  query ([akitaonrails#374]).

- Kimi Code 0.34.0 managed runs now discover checkout-local sessions from the
  current `state.json` `cwd` field as well as the legacy `workDir` alias. The
  parser rejects conflicting aliases and persisted ids that disagree with the
  session directory, and deterministic acceptance now exercises the current
  state schema (akitaonrails#382).
- Prevented delayed post-tool and shutdown hook tails from redirecting the
  shared active-project fallback after work moved to another project. Only
  session starts, user prompts, and pre-tool events now advance shared or
  identity-only fallbacks; other events refresh exact session mappings, and
  dropped-subagent preflight no longer publishes scope (akitaonrails#372).
- The Anthropic provider stopped sending `temperature` to Claude 4.7 and later
  models, including the Claude 5 families, and to Claude Mythos Preview. Those
  models reject non-default sampling parameters, which made `bootstrap`,
  consolidation, `lint`, and auto-improvement fail with an upstream 400. Both
  `anthropic` and `anthropic-oauth` now omit the field for affected models and
  preserve it elsewhere. `llm-test` also sends a representative 0.2 value
  before provider normalization, so it exercises the same compatibility path
  as the real pipeline (akitaonrails#377).
- Both wrappers (`bin/ai-memory` and `bin/ai-memory.ps1`) now forward
  `ANTHROPIC_OAUTH_TOKEN` and `CLAUDE_CODE_OAUTH_TOKEN` to the helper
  container. Their API-key counterparts were already in the passthrough list,
  so subscription-token setups (`claude setup-token`,
  `AI_MEMORY_LLM_PROVIDER=anthropic-oauth`) silently lost their credential at
  the container boundary. The visible symptom is that the retry `status`
  itself recommends — `llm-test --provider anthropic-oauth` — fails with a
  missing-token error, because `llm-test` runs client-side in the helper
  rather than on the server. The PowerShell wrapper also trims trailing path
  separators as individual characters, so Windows invocations reach Docker
  instead of failing during path normalization (akitaonrails#379).

v1.24.0

Toggle v1.24.0's commit message
Release v1.24.0

- MCP-only clients are now visible in the operator's traffic picture.
  Every MCP tool call is counted against its caller — the sanitized
  `clientInfo.name` from the initialize handshake when the HTTP
  transport runs stateful (`--http-stateful`) or over stdio, else the
  `X-Memory-Actor-Agent` overlay an ingress proxy asserts, else
  `unknown` — split into reads and writes and bucketed per UTC day in a
  new `client_activity` table (V46). A new multi-user root-gated
  `GET /admin/activity/by-client?since_days=N` reports the aggregate,
  volume-descending with a name tiebreak (`0`/absent = whole history).
  Counts buffer in memory and flush from one background task on a one-minute
  interval, even when the server becomes quiet; failed batches remain bounded
  in memory and retry once per interval. Process exit can still lose the
  current interval. Each UTC day retains at most 128 distinct names and folds
  additional names into `other`, preventing untrusted client names from
  causing traffic-proportional row growth. Unknown future tools count as
  writes, so an unclassified tool surfaces as suspicious growth instead of
  hiding among reads. This complements
  `/admin/sessions/by-agent`: hook-driven agents open sessions,
  MCP-only clients (VS Code Copilot, Claude Desktop, scripts) never do,
  and until now left no trace at all. (akitaonrails#366)
- Added explicit Kiro CLI v2 managed-workstream launches through `ai-memory run
  kiro` / `kiro-cli`, including native `--resume-id` reuse, `$KIRO_HOME`
  discovery, v2 `--yolo` translation, read-only visible-event import, and
  checkout-scoped UUID/metadata validation. Non-v2 engines pass through
  without session injection. Kiro remains outside bare automatic selection
  until a logged-in current-format acceptance run is available, and v3 managed
  sessions remain unsupported (akitaonrails#356).
- Added verified Kiro CLI v2 lifecycle hooks. CamelCase hooks merge into
  existing agent configs, honor `$KIRO_HOME`, preserve unrelated hooks and
  per-agent project strategies, fail before changing any target when one is
  invalid, infer remote connectivity from the managed Kiro MCP entry, inject
  handoffs through `agentSpawn` stdout, enforce capture exclusions for known
  v2 tool payloads, and uninstall only exact ai-memory entries. Kiro v3 hook
  capture remains unsupported pending sanitized live lifecycle and built-in
  tool payload fixtures, despite its now-documented standalone schema (akitaonrails#355).
- Added `[consolidation] max_input_tokens` and `max_output_tokens`
  (`AI_MEMORY_CONSOLIDATION__MAX_INPUT_TOKENS` /
  `AI_MEMORY_CONSOLIDATION__MAX_OUTPUT_TOKENS`) for provider-specific context
  limits. Input sizing now accounts for the rendered system/user messages,
  bounded current-page or slot context, structured-output schema, and provider
  envelope reserve instead of budgeting only the observation dump. Unsupported
  minimums are rejected at startup (akitaonrails#369).

- Mixed-case and trailing-period usernames now use deterministic hashed
  per-operator slot namespaces, preventing distinct identities from sharing one
  physical directory on case-insensitive filesystems (akitaonrails#364).
- Consolidation prompts no longer ignore system, schema, instructions, and
  dynamic slot/current-page overhead when allocating observations. The
  provider-neutral estimate is deliberately conservative and documents the
  remaining tokenizer variance instead of claiming an exact token ceiling
  (akitaonrails#369).
- A failing LLM no longer costs a session its PreCompact/PostCompaction
  checkpoint. Provider failures (context overflow, rate limit, outage) and
  unmappable structured output now degrade to the rule-based checkpoint a
  zero-LLM install writes, so configuring a provider can no longer be worse
  than leaving it unset. The fallback keeps the `consolidate` admission event;
  admission rejections, wiki/store failures, and unresolvable sessions still
  fail closed (akitaonrails#369).
- Native-session checkout matching now fails closed when either path cannot be
  canonicalized, instead of treating two missing or inaccessible paths as the
  same checkout during managed resume or adoption (akitaonrails#356).

v1.23.0

Toggle v1.23.0's commit message
Release v1.23.0

- Documented provider-neutral coexistence with structural code-intelligence
  to
C57C
ols: use ai-memory for historical intent and continuity, use the current
  checkout or structural provider for live symbols and impact analysis, verify
  historical structural claims before acting, and keep source, builds, tests,
  and observed runtime behavior authoritative. No provider coupling, automatic
  querying, or persisted structural-evidence schema was added (akitaonrails#353).

- Antigravity CLI's generated native `PreToolUse` hook now returns the required
  `{"decision":"allow"}` response on normal capture, malformed input, and
  capture-policy drops. Local installs default to the native spool-based hook
  command, whose generic `{}` response caused Antigravity to deny every tool
  call even though the staged shell and PowerShell hooks already returned the
  correct decision (akitaonrails#352).

- Kiro CLI is now supported as an MCP-only client through `install-mcp
  --client kiro-cli` (alias `kiro`). The installer preserves existing
  `$KIRO_HOME/settings/mcp.json` content, adds bearer headers when configured,
  honors `$KIRO_HOME`, and appends a Bedrock schema flavor that removes only
  unsupported root-level JSON Schema combinators. Non-loopback Kiro endpoints
  must use HTTPS and are rejected before `--apply` writes an unusable config.
  Kiro lifecycle hooks and managed workstreams remain deferred because its v2
  and early-access v3 engines use incompatible hook and session formats
  (akitaonrails#351).
- Added `ai-memory continue`, which resumes the most recently launched managed
  checkout from any directory. `run`'s bare mode already continues the current
  checkout, but its workstream lookup is keyed by repository and worktree
  fingerprints, so it requires a `cd` first. `continue` orders the client-local
  checkout links by their `linked_at` stamp, revalidates the newest one's path
  and resolved scope, and then delegates to the same bare-mode launch. Stale,
  retargeted, scope-mismatched, or corrupt-ordering links are announced on
  stderr and skipped rather than silently resuming a different project. It
  accepts `--workspace`,
  `--yolo`, and `--fresh`; native harness arguments and `--executable` remain
  unavailable because bare mode does not know which harness it will pick.
  Docker-wrapper installs route the command through the checksum-verified host
  client so it can inspect local checkouts, session stores, and harnesses
  (akitaonrails#350).
- New `GET /admin/sessions/by-agent` endpoint reporting how many sessions
  each agent CLI opened in one scope (`claude-code`, `cursor`, `codex`, …),
  so a dashboard can answer "where is this project's memory coming from".
  `sessions.agent_kind` already carried the answer but no read surface
  exposed it — the existing `/admin/open-sessions` takes the agent as a
  *filter* and returns neither the kind nor a total. Counts cover open and
  ended sessions alike, take an optional `since_days` window (`0` or absent
  = whole history), and order count-descending with an agent-name tiebreak
  so equal counts do not reorder between calls. Like the other scoped admin
  reads it reports the caller's own sessions plus unowned ones, with
  `all_owners=true` as the recovery switch. Unknown scopes 404 rather than
  being auto-created. No migration. (akitaonrails#349)

v1.22.0

Toggle v1.22.0's commit message
Release v1.22.0

- Added `ai-memory show`, a project-first managed launcher that joins private
  client-local checkout links with public server project metadata and a bounded
  local scan, then launches an installed harness without changing the parent
  process directory. It supports discovery-only `--json`, stages new projects
  atomically with routing and Agent Skills, keeps remote-server filesystem paths
  private, refreshes links after successful managed prepares and CLI
  rename/move operations, and runs host-side through the Docker wrapper (akitaonrails#342).
- Hook ingestion now recognizes Hermes Agent as a concrete session kind and
  understands its documented shell-hook `tool_name` / `tool_input` envelope
  for bounded tool-family titles and capture exclusions. Migration V44 expands
  the session allowlist without losing session ownership, observation
  watermarks, indexes, or scope-pairing triggers. This remains protocol support,
  not a first-party Hermes installer, and session-start handoff acceptance stays
  disabled because Hermes ignores that hook's stdout (akitaonrails#337).
- Trusted-proxy identity now has a dedicated
  `[auth].actor_proxy_bearer_token`, distinct from the root bearer. Proxy
  requests must assert either a username or the complete OIDC issuer/subject
  pair; missing, partial, duplicated, or comma-folded identity headers fail
  closed. Proxied root access requires the configured OIDC issuer/subject pair;
  a display username never grants root. Ordinary root and DB-user requests
  continue to ignore raw actor headers, and leaving the proxy token unset
  preserves existing behavior (akitaonrails#333).
- Qualified identity keys now keep usernames separate from OIDC
  `(issuer, subject)` pairs and drive active-project routing consistently for
  hook and MCP requests. The stable OIDC pair outranks display usernames, so
  same-subject users from different issuers cannot alias each other (akitaonrails#333).
- The `/admin/*` route layer and the MCP `memory_forget_sweep` tool now ask
  "does this deployment distinguish operators" instead of "do `users` rows
  exist", so a trusted-proxy deployment — which never writes a `users` row —
  gets root-only admin gating instead of waving every proxied caller through
  the single-operator escape hatch (akitaonrails#333).
- Optional `[slots] per_user` namespaced engine-written memory slots by the
  authenticated operator. Session briefs and consolidation prompts now receive
  shared slots plus that operator's own bounded namespace, while foreign slot
  writes are refused. The feature defaults off, preserves existing shared
  slots, and intentionally leaves exact wiki reads and searches project-wide
  because it is an agent-context injection boundary rather than RBAC (akitaonrails#335).
- Handoffs now belong to the operator that created them (migration V39). On a
  server shared by several people the open-handoff lookup was scoped by
  `(workspace, project, state)` alone, so the next session to start — whoever it
  belonged to — consumed the pending baton, and delivery is destructive, so the
  author simply lost it. `cwd` did not help: a handoff created through
  `memory_handoff_begin` is always manual (`from_session_id = NULL`), and manual
  handoffs bypass the cwd check and outrank automatic ones, so the deliberate
  artefact was exactly the one that crossed operators. Ownership is now checked
  before those rules, and the owner column holds the qualified
  `IdentityKey::storage_key()` TEXT. `memory_handoff_begin` gains `shared: true`
  to publish a baton to the whole project on purpose; `memory_handoff_accept`
  gains `any_owner: true` for recovery. A NULL owner still means "shared", so
  every stored row and every caller without an authenticated actor behaves
  exactly as before (akitaonrails#334).
- Sessions record their operator (migration V40), and the open-session lookup
  behind `GET /admin/open-sessions` is scoped to the caller unless
  `all_owners=true`. `finalize-session` drives off that lookup and acts
  destructively on the result — ending the session, synthesising a page from its
  observations and minting a handoff carrying its raw prompts — so picking "the
  newest open session in the scope" could do all of that to a colleague's live
  session. The new `--all-owners` flag exposes the server switch (akitaonrails#334).
- New `GET /api/v1/workspaces/{workspace}/projects/{project}/handoffs` lists a
  project's handoffs, filtered by `state` and scoped by owner, backed by new
  non-partial indexes (migration V41) since every pre-existing handoffs index is
  partial on `state = 'open'`. There was no handoff listing anywhere in the
  system: readers only ever fetched the single pending one and consumed it, so a
  mis-delivered baton could not be inspected or recovered. On a server that
  authenticates, the prompt-derived fields (`summary`, `open_questions`,
  `next_steps`) are served to a caller the server can name and to the root
  operator, and are omitted with `redacted: true` for a caller it can place as
  neither — unowned rows are shared, so such a caller matches every one of them,
  and unlike the overview's single newest card this returns the project's whole
  history. Cross-owner reads require the explicit root-only
  `all_owners=true` recovery switch. The metadata is served either way,
  which is what makes the listing useful; a server with no auth configured
  serves the bodies too, since it already serves every page body
  unauthenticated (akitaonrails#334).
- New admission-chain operations `handoff_begin`, `handoff_accept` and
  `handoff_cancel`. Handoffs live in their own table, so their lifecycle never
  passed through `Wiki::write_page` and was invisible to admission webhooks —
  leaving the operations that move prompt-derived text between operators
  unauthorizable. Every path raising one of them asks and announces in the same
  order: only the webhooks that can refuse (blocking + reject policy) are
  awaited before the operation, and observers are dispatched fire-and-forget
  after it, only if it happened — so `memory_handoff_accept`, whose routine
  answer is `{"handoff": null}`, never announces an accept that found nothing,
  and a mirror is never told about a baton the engine then abandoned. The
  automatic SessionEnd handoff and the session-start claim run through the same
  chain as their operator-triggered counterparts, forwarding the caller's
  webhook skip-list under the same root-only rule as every other transport.
  Neither can cost an operator anything beyond the operation the webhook
  declined: SessionEnd still writes the summary page, runs the opt-in
  consolidation and commits (a refusal skips the baton and is logged), and a
  refused, timed-out or unreachable claim leaves the handoff open for the next
  session (akitaonrails#334).
- Pending auto-improve proposals record who staged them (V42
  `staged_by_actor_user`, the qualified identity key, surfaced on the proposal
  detail), and the one-pending-per-target rule is scoped per operator through
  a NULL-collapsing unique index, so one operator's pending suggestion stops
  blocking everybody else's for the same page while every unattributed caller
  keeps the original one-per-page rule unchanged (akitaonrails#336).
- Page reinforcement records each distinct authenticated operator (V43
  `page_access`) beside the existing shared access counter. The opt-in
  `[decay] breadth_weight` term (default `0.0`) lets the forget sweep retain
  pages reinforced by several operators without changing existing scores at
  the default or for pages with zero or one identified reader (akitaonrails#336).

- `run` accepts `antigravity` (aliases `antigravity-cli`, `agy`) as a
  managed harness, so an Antigravity session joins 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 no
  selector and the id is linked by the hooks or discovered afterwards; a
  linked resume passes `--conversation <id>`, and `--continue` / `-c` is
  respected as an explicit user choice. `--yolo` maps to
  `--dangerously-skip-permissions`, and the utility
  subcommands (`models`, `plugin`, `update`, …) pass through without a selector.
  Conversation discovery reads the per-conversation SQLite databases under
  `~/.gemini/antigravity-cli/conversations/`: the id is the file name and the
  workspace comes from two observed protobuf fields, so only conversations
  opened on the current directory are offered and a database from another `agy`
  version is skipped instead of failing the listing. Step payloads are
  undocumented, unversioned protobuf, 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 native contract was
  verified against Antigravity CLI v1.1.7 (akitaonrails#345).

- `run` on Windows now resolves npm-style harness installs through `PATHEXT`
  and starts the resolved wrapper. This avoids accepting an extensionless Unix
  shell shim such as `opencode` during availability checks and then failing to
  launch it when the adjacent `opencode.cmd` is the Windows entry point. Unix
  resolution remains unchanged (akitaonrails#343).
- `memory_auto_improve` without a `session_id` now selects the newest
  completed session that has no persisted auto-improvement run. Preflight-
  skipped sessions therefore advance the implicit manual-review queue instead
  of permanently starving older sessions; passing an explicit session ID still
  permits a targeted rerun (akitaonrails#338).
- Handoff and session ownership is stamped only where the deployment actually
  distinguishes operators. A server with `[auth].bearer_token` +
  `[auth].root_username`, no `users` rows and no proxy has one operator and two
  transports: stamping that one name on every HTTP write while the stdio /
  in-process transport carries no actor would make one person's handoffs and
  sessions invisible to their own other transport on the same data directory.
  With nobody to separate, the stamp is the pre-ownership `NULL` and both
  transports agree. Reads are deliberately not gated the same way, so rows
  stamped while a deployment did distinguish operators stay readable by that
  operator afterwards (akitaonrails#334).
- The automatic SessionEnd handoff, the session page and both consolidation
  paths attribute to the operator recorded on the **session**, not to whoever
  delivered the event — a spool drain, a shared hook token or an operator
  finalizing a stuck session all carry a different identity. The atomic store
  operation also rejects a handoff whose owner differs from its source session
  (akitaonrails#334).
- Briefings and both read-only overviews — workspace and project — scope
  handoffs to the requesting actor instead of showing only unowned ones, and
  `pending_handoff_count` applies the same filter as the fetch — otherwise a
  briefing advertises a pending baton the same caller can never retrieve, and on
  any server that stamps owners the overview's handoff card would go permanently
  empty while the count beside it kept reporting the row. The read-only
  `/api/v1` overview no longer surfaces handoffs that belong to a specific
  operator, including the raw prompt text an automatic handoff is synthesised
  from, to a browser the server cannot attribute (akitaonrails#334).
- Retiring superseded automatic handoffs no longer crosses an operator
  boundary. Both sweeps — the same-cwd expiry on a new SessionEnd handoff and
  the post-claim cleanup after an accept — match on the acting handoff's
  `owner_user`, so one person starting or ending a session in a directory
  cannot expire another person's pending baton. A shared handoff (no owner) is
  visible to everyone, so it is only ever superseded by another shared one; on
  a single-operator or unauthenticated server every row is unowned and the
  sweeps behave exactly as they did (akitaonrails#334).
- `ops::accept_handoff` propagates whether the claim actually succeeded, so
  `memory_handoff_accept` no longer returns the handoff body when the atomic
  claim was lost — previously two agents could be handed the same baton. The
  cross-operator escape hatches require admin authority: `any_owner` on
  `memory_handoff_accept` and on `memory_handoff_cancel` (which previously had
  no recovery path at all, so a handoff whose owner no longer matched any
  reachable identity could not be discarded), and `--all-owners` on
  `ai-memory finalize-session` (akitaonrails#334).
- The `[auto_scope] per_actor` active-project map is keyed by the qualified
  identity on both sides — the hook ingress that publishes and the MCP tools
  that read — so an OIDC-proxied operator's writes and reads land on the same
  slot instead of silently missing on every read (akitaonrails#334).
- Owner predicates for pending and exact-id handoff reads now execute in SQL
  before prompt-derived fields are loaded. Exact-id cancellation returns the
  same result for absent, wrong-scope and foreign-owner ids, so it no longer
  discloses another operator's handoff state before authorization. Accept and
  cancel also recheck the expected workspace and project in the atomic state
  update, closing a lifecycle race between the scoped read and destructive
  write (akitaonrails#334).
- Ownership writes reject malformed identity keys, and malformed non-null
  session-owner keys already present in the database fail closed during hook
  processing instead of being converted to the shared `NULL` bucket.
  Named-user handoff history uses two indexed shared/owned ranges, so a large
  volume of another operator's rows cannot turn a bounded listing into a
  project-wide scan (akitaonrails#334).
- Actor-scoped briefing, overview and handoff-history responses now use
  `Cache-Control: private, no-store`, preventing a browser from reusing one
  operator's prompt-derived response after credentials at the same URL change
  to another operator (akitaonrails#334).
- Automatic session-start handoff admission is capped at 750 ms, below the
  shortest shipped client's one-second fetch timeout. A slow deciding webhook
  leaves the baton open instead of approving and consuming it after the caller
  has disconnected (akitaonrails#334).
- A staged auto-improve proposal colliding with one already pending no longer
  aborts its whole staging run (losing the run row, its sibling proposals and
  the paid LLM review): the colliding proposal alone is skipped, and every
  staging surface — `memory_auto_improve`, `/admin/auto-improve`, the
  telemetry report, the curator, the CLI and the scheduler's log — names the
  skipped target and the reason instead of silently returning N-1 proposals
  (akitaonrails#336).
0