Repository navigation
Tags
Tags
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)
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`.
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).
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).
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).
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).
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).
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).
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)
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).
PreviousNext