Commit Graph
208 Commits
Author SHA1 Message Date
AkitaOnRails e804a31025 Merge main into release/2.4: bug batch + windows flake fix + auto-improve claim fix (V66)
# Conflicts:
#	crates/ai-memory-store/src/api_credentials.rs
#	crates/ai-memory-store/src/lib.rs
2026-09-21 18:58:26 -03:00
AkitaOnRailsandClaude Opus 4.8 5d5f01088d docs(llm): stop recommending gpt-5-mini for openai-oauth (#831)
The Codex/ChatGPT backend behind `openai-oauth` restricts the selectable
model set to a small server-defined list and rejects other ids
(including `gpt-5-mini`) with a deterministic 400. The TIP blocks in
`docs/llm-providers.md` and `docs/install.md` recommended setting
`AI_MEMORY_LLM_MODEL=gpt-5-mini`, which fails on that backend.

Advise leaving the provider default (`gpt-5.5`) for `openai-oauth`/`codex`,
keep `claude-haiku-4-5` for `anthropic-oauth`, and qualify `gpt-5-mini`
for `copilot` as unverified rather than asserting it works.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-21 15:52:25 -03:00
Thiago Macedo eadab8e4bd Add a macOS menu bar companion that bundles and governs the server.
The accessory app ships the ai-memory binary and hooks tree, starts the
existing LaunchAgent, and opens /web, status, config, and logs. Durable
data stays in Application Support so replacing the .app is an update.
2026-09-20 21:25:16 +02:00
AkitaOnRailsandClaude Opus 4.8 9c2e793503 fix(install-hooks)+docs: preserve capture-assistant on re-apply; 2.3 release-audit doc fixes
Post-audit of release/2.3 (v2.2.2..HEAD) before the 2.3.0 release.

Fix (S1, the one code finding): install-hooks re-applied with no
--capture-assistant flag silently stripped an existing assistant-capture
opt-in — most visibly through the new `ai-memory run` auto-wire, which always
re-applies without the flag. install-hooks now preserves an already-baked
--capture-assistant on a bare apply (Claude Code + Codex, via the same
existing-config read that prompt-capture already uses); an explicit flag still
forces it on. Regression tests: bare re-apply preserves the opt-in; a fresh
install without the flag stays off. (The underlying non-preservation is also
latent on main and can be backported to a 2.2.x patch if desired.)

Docs (release-readiness):
- README: add the agent-messaging.md row to the Docs table (new feature + 4 MCP
  tools shipped without a README entry); note run-as-preferred/auto-wire.
- ARCHITECTURE config reference: document capture_assistant, backfill_on_start,
  run_autowire + their AI_MEMORY_* env overrides.
- install.md: add an "Upgrading to 2.3.0" note (the two default-on behaviors +
  opt-outs) and a forward-only/backup-before-rollback note.
- design-boot-backfill.md: reconcile the now-answered "Open questions" with the
  as-built implementation (path B, on-by-default, capped).
- design-rules-promotion.md: drop the stale "(2.1)" target label (untargeted,
  still unimplemented).
- CHANGELOG: add the install-hooks preservation Fixed entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-16 12:28:07 -03:00
AkitaOnRailsandClaude Opus 4.8 5337c0254c feat(hooks): Codex assistant-final-turn capture on Stop (#743)
Extend the assistant-capture closed table with (Codex, Stop) → last_assistant_message
(verified on codex-cli 0.154.0), behind the existing double opt-in (client
--capture-assistant + server capture_assistant=true). The install gate
capture_assistant_allowed now admits Codex on a native hook platform (it
previously refused it), and the Codex render path threads --capture-assistant
onto the Stop command. The runtime read + sanitize/bound/backstop/store pipeline
is already agent-agnostic and keys off the closed table, so no new capture code.
Only (Codex, Stop) is added — Codex has no SubagentStop.

Tests: closed-table set updated to admit Codex Stop; a positive
client-transform capture test for Codex; the install-flag gate test admits
Codex; a render test asserting --capture-assistant bakes onto the Codex Stop
command (and only Stop) when opted in. Docs: support-matrix + install.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-15 14:35:29 -03:00
AkitaOnRailsandClaude Opus 4.8 1c18d3e6d5 feat(embeddings): add GitHub Copilot embedding provider (#739)
AI_MEMORY_EMBEDDING_PROVIDER=copilot reuses the existing Copilot OAuth token
infrastructure (no separate OpenAI/Gemini key). The GitHub->Copilot token
exchange/cache/refresh is factored out of CopilotProvider into a shared private
CopilotAuthState, which both the chat provider and the new CopilotEmbedder
compose (exchange/base-url/runtime-headers each called from one place). The
embedder POSTs {base_url}/embeddings with the exchanged Copilot bearer + the
required Copilot runtime headers, reusing the OpenAI-shape parse/normalise path;
provider() = "copilot", default model text-embedding-3-small / dim 1536. Auth is
resolved before construction only when the embedding provider is copilot
(invariant 14).

Caveat (documented in code + docs): Copilot's /embeddings wire contract is
inferred from its OpenAI-compatible chat contract plus openclaw#61717 prior art
and is unit-tested via wiremock, not verified against a live Copilot account —
it needs a real-Copilot smoke test before being relied on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-15 13:45:37 -03:00
AkitaOnRails 15f46bbdf3 Merge branch 'main' into release/2.3 (2.2.x fixes: rustls RUSTSEC, awk/spool hooks, Cursor/ZCode, schema)
# Conflicts:
#	CHANGELOG.md
2026-09-15 12:05:58 -03:00
Felipe Neves RicardoandCodex 0.154.0 032d8dfe65 fix(hooks): preserve ingest key across shell retry
Co-Authored-By: Codex 0.154.0 (GPT-5) <noreply@openai.com>
2026-09-14 13:51:29 -03:00
AkitaOnRails f81ecf97ca Merge PR #717: Codex account-aligned LLM provider (#716)
# Conflicts:
#	CHANGELOG.md
2026-09-12 16:01:41 -03:00
Felipe Neves RicardoandClaude Opus 5 5167c54e65 fix(hooks): spool an undelivered event in the POSIX script bundle
The shell bundle POSTed and forgot: `curl --max-time 0.5 ... || true`
discarded the event whenever the server was unreachable or answered 5xx,
with nothing written to disk. That is the failure #580 reported for the
generated TypeScript integrations, on the path the docker deploy installs
(`for_bash_script_runner`, and the wrapper forcing
`AI_MEMORY_HOOK_PLATFORM=posix` because the host has no local binary).

`ai_memory_post_hook` now reads the status code and, on an unreachable
server or 5xx, writes the event to `<data_dir>/hook-spool/` in the same
on-disk contract `ai-memory hook-drain` reads: same filename shape, same
`SpoolEntry` JSON, 0600 file inside a 0700 dir, tmp+rename. A 4xx is a
permanent rejection and is not retried. An `ingest_key` is minted once at
spool time and baked into the URL, so this bundle's drain and a concurrent
`hook-drain` cannot double-ingest — the same guarantee #580 chose.

A delivery that succeeds proves the server is reachable, so it flushes the
backlog behind itself, detached from the hook's own process and skipped
entirely when nothing is queued (every call on a healthy install). The
per-tool-call hot path gains nothing but a directory probe.

`ai_memory_json_field` reads a spooled field back, scanning left to right
and undoing the escapes `ai_memory_json_string` emits. An entry carrying a
`\uXXXX` escape came from a richer serializer (the native binary), so it is
declined rather than mangled and left for `ai-memory hook-drain`.

Verified both directions on a live pair: the native `hook-drain` binary
consumes a shell-written entry (parsing the URL with its shell-minted
ingest_key and the body back into a JSON object for `/hook/batch`) and
retires it on ack; the shell drain delivers a natively-written entry.

The PowerShell bundle has the same gap and is deliberately left out to keep
this reviewable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZ2LxU2ny3oo9NcCrEQSRw
2026-09-11 23:59:42 -03:00
AkitaOnRailsandClaude Opus 4.8 09216e3bab docs(install): lead LLM setup with the no-API-key subscription path (P5)
Promote 'use a subscription you already pay for' (anthropic-oauth / openai-oauth
/ copilot, no platform API key) and the zero-LLM default to a prominent callout
in the install guide's LLM section, per docs/design-hindsight-borrowings.md P5.
Docs only; the capability already existed in docs/llm-providers.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-11 16:03:24 -03:00
Gabriel Scharb cfb215094e test: complete Codex provider validation surfaces 2026-09-11 14:43:20 -03:00
Gabriel Scharb cd7bf7b9d2 feat: add Codex credential-reuse provider 2026-09-11 14:43:20 -03:00
Alan Hoffmeister 716dc4d702 fix(hooks): normalize native Codex tool observations 2026-09-09 22:51:17 -03:00
Felipe Neves RicardoandClaude Opus 5 a091c6c858 docs(routing): CLAUDE.md must import AGENTS.md to load it (#680)
The managed routing snippet directs agents to write durable project rules
into "the project's canonical agent instruction file", and both the CLI
hint and the docs steer that file toward AGENTS.md. Claude Code loads
CLAUDE.md and does not read AGENTS.md, so a project that follows the
recommendation without a bare `@AGENTS.md` import line in CLAUDE.md keeps
its canonical rules out of context at session start. They stay reachable,
since an agent acting on a prose "read AGENTS.md" pointer opens the file,
which makes adherence contingent on the agent choosing to read them.

State the precondition in SNIPPET_BODY, mirror it beside the
`--target AGENTS.md` guidance in docs/install.md and docs/usage.md, and
switch this repository's own CLAUDE.md from a prose pointer to the import.

The committed AGENTS.md managed block is regenerated to match SNIPPET_BODY,
as committed_agents_md_matches_snippet_body requires.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KqE9rQpsjqNK5mmpqmggL7
2026-09-08 19:31:17 -03:00
AkitaOnRailsandClaude Opus 4.8 614a8bdadb Merge PR #637: support Podman without Docker in the wrapper (#636) [2.1]
jpramos123's fix: bin/ai-memory auto-selects the container engine
(AI_MEMORY_DOCKER override -> docker -> podman), defaults to the
fully-qualified docker.io/akitaonrails/ai-memory image so Podman's
non-interactive short-name resolution does not fail, and preserves the
selected engine (%q-quoted) in the standalone-container recovery script
'ai-memory upgrade' emits instead of hard-coding docker. Reconciled onto
release/2.1 (new-capability -> 2.1); closes #636. Packaging tests (incl. the
new podman coverage) green; wrapper syntax OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-04 17:44:48 -03:00
Joao Paulo Ramos df03a6b4ee fix(wrapper): auto-detect podman container engine (#636) 2026-09-04 16:12:10 -03:00
AkitaOnRailsandClaude Opus 4.8 30ef4812f8 Merge PR #622: first-party OpenCode 2.0 beta support [2.1]
Reconciles enrell's #622 onto release/2.1. Additive new-harness feature
(opencode2): new CLI enum arms, wire aliases folded into the existing
AgentKind::OpenCode (no new variant, no migration), a read-only parameterized
SQLite transcript adapter for the beta session_v2/session_message tables, and
an ai-memory-opencode2.ts plugin derived from v1. Sequenced after #625: the
opencode2 plugin inherits #625's resolveToken() auth, so its test assertion is
updated from "Bearer ${TOKEN}" to "Bearer ${token}". CHANGELOG (Added),
mcp-install and support-matrix rows reconciled with the Codex-SessionEnd (#605)
and capture-assistant (#627) wording. Queued for 2.1.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-04 12:16:34 -03:00
enrell 33b5dd2c2d feat(opencode2): add first-party OpenCode 2.0 beta support
MCP (mcp.servers + oauth:false), dependency-free plugin shape
(ai-memory-opencode2.ts), managed run opencode2, v2 transcript
adapter (session_v2/session_message), acceptance case, docs,
changelog. Shares v1's config dir, session store, and agent kind;
no migration. Verified live against beta-18999.
2026-09-03 23:30:16 -03:00
AkitaOnRailsandClaude Opus 4.8 fc592a787e Merge PR #619: route gpt-5.6-luna through OpenCode Go Responses API [2.1]
Reconciles lucazz's #619 onto release/2.1 against #606's opencode operator
headers + Go/Zen base-URL work. #619 was based on main (pre-#606); merging
produced conflicts in opencode.rs and install.md, resolved by keeping BOTH:

- #619's transport enum (ChatCompletions | Responses) routes gpt-5.6-luna to
  /responses, where OpenCode Go actually publishes it (chat/completions 500s);
  other models keep Chat Completions.
- #606's operator headers + Go/Zen split re-applied on top: OPENCODE_GO_BASE_URL
  (ZEN deprecated alias), with_base_url / with_extra_headers now thread BOTH
  transports, and — the cross-PR fix — OpenCodeResponsesProvider::post layers
  headers via ExtraHeaders set_default+apply (mirroring openai.rs), so an
  AI_MEMORY_LLM_HEADERS entry overrides the Luna path's user-agent /
  x-opencode-session too, not just the Chat Completions path.

install.md row merged (Go/Zen + Luna). Gate on merged tree: clippy
--workspace -D warnings clean; full workspace tests 0 failed — #606's
header/base-url tests and #619's Luna Responses tests pass together.
Residual: OpenCode Go's live /responses format is covered by #619's mock
tests, not a live call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-03 19:50:38 -03:00
Lucas do Amaral Saboya 05dc99f0d1 fix(llm): route Luna through Responses API 2026-09-03 19:10:37 -03:00
Lucas Oliveira 00151a4e39 docs(llm): correct the session-header description after reconciling
Review pass over the merged state found three statements left over from the
per-process session id this branch used to carry: `docs/install.md` and
`docker/.env.production.example` still described the default as one id per
process, stable only if set explicitly. It is one id per logical operation,
stable across retries and the structured-output fallback, so the advice to
set it "to keep it stable across restarts" pointed at a problem that no
longer exists. Both now describe what the code does and why an operator
would still override it.

Also re-exports `OPENCODE_SESSION_HEADER` from the crate root, where its
sibling opencode constants already live.
2026-09-03 15:10:38 +00:00
Lucas Oliveira 25573e0077 Merge upstream/main: reconcile operator headers with the 2.0.2 client headers
PR #610 shipped caller identification for OpenCode Go in 2.0.2 — a
`ClientHeaders` opt-in carrying an agent string plus a `LlmOperationId`
under `x-opencode-session`. It overlaps this branch on `openai.rs`,
`openai_compat.rs`, `opencode.rs` and `lib.rs`. The two are layers, not
competitors, so both survive:

- 2.0.2's client headers stay the default and keep their semantics. Its
  per-operation id is better than the per-process one this branch carried:
  it stays stable across retries and the strict/tolerant fallback, which is
  what OpenCode's metrics key on. The per-process id and its helpers are
  gone.
- `AI_MEMORY_LLM_HEADERS` layers *over* those defaults. `OpenAiProvider::post`
  now builds one header map per request — operator entries, then
  `set_default` for the agent string and the operation-id header — and
  applies it once. Applying the two sources separately would append rather
  than replace, sending two `user-agent` values whenever an operator
  configures one, and a duplicate is worse than either value alone.
- The session header name and agent string come from
  `OPENCODE_SESSION_HEADER` and `DEFAULT_USER_AGENT` rather than literals
  repeated at the call site.

Unit tests that asserted the session default by reading the stored header
map are dropped: the header is applied per request now, so those pinned the
old mechanism. #610's wire-level tests already cover the defaults, including
retry stability; a new wire test covers what this branch adds, that an
operator entry beats the per-operation id and arrives exactly once.

The base-URL fix survives unchanged — 2.0.2 kept `OPENCODE_ZEN_BASE_URL`
holding Go's URL and its `new_with_base_url` private, so Zen is still
unreachable without it.
2026-09-03 14:46:15 +00:00
Lucas Oliveira ccdea09f95 fix(llm): let the opencode provider reach Zen, not just Go
`AI_MEMORY_LLM_BASE_URL` had no effect with
`AI_MEMORY_LLM_PROVIDER=opencode`. `OpenCodeProvider::new` hardcoded the
endpoint and `build_provider`'s `OpenCode` arm never read
`ProviderConfig::base_url` — the override was accepted at the configuration
boundary and then silently dropped, which is worse than rejecting it. Zen's
general catalogue at `https://opencode.ai/zen/v1` was unreachable through
the provider built for OpenCode; the only route was `openai-compat`, which
gives up the `x-opencode-session` default.

Zen and Go are separate products, not two spellings of one: Go serves a
smaller, cost-optimised model set under `zen/go/v1`, Zen the full catalogue
under `zen/v1`. The constant said one and held the other
(`OPENCODE_ZEN_BASE_URL = ".../zen/go/v1"`), and the docs repeated the
conflation as "Zen/Go" throughout, so the gap was invisible from the code.

- `OPENCODE_GO_BASE_URL` names the default endpoint for what it is.
  `OPENCODE_ZEN_BASE_URL` is deprecated in favour of it but keeps both its
  export and its value, so code compiled against v2.0 is unaffected.
- `OpenCodeProvider::with_base_url` repoints the provider, and the factory
  calls it with `ProviderConfig::base_url`, mirroring how the
  `anthropic-oauth` arm already handles its own override. Go stays the
  default, so existing setups do not move.
- An override keeps the session header and the user agent: both endpoints
  correlate requests the same way, so changing where a request goes must not
  change how it identifies itself.
- Docs distinguish the two products and state that model ids are per
  catalogue, so an override needs an explicit `AI_MEMORY_LLM_MODEL`. The
  `opencode-zen` alias selects Go like every other spelling — the endpoint
  comes from the base URL, not the alias — and now says so.

Unit tests pin the default, the alias's value, the override, and that the
session header survives it; a wiremock test drives `build_provider` and
asserts the request actually arrives at the overridden host carrying both
headers, since "the field changed" and "the request went elsewhere" are
different claims.
2026-09-03 14:26:36 +00:00
Lucas do Amaral Saboya dc0584c958 fix(llm): identify OpenCode Go requests (#608)
OpenCode Go requires a user agent and x-opencode-session, but the shared OpenAI-compatible sender had no opt-in client-header path. Generate one logical operation ID per LLM call, preserve it across fallback attempts, and reuse captured session IDs where available.
2026-09-03 11:03:05 -03:00
Lucas Oliveira 53180c8d51 feat(llm): send operator headers and identify ai-memory on every request
OpenCode Zen/Go notified operators that requests missing an
`x-opencode-session` header may start erroring, and reported ai-memory's
traffic as "Unknown client — your requests carry no user agent, so we
can't tell what sends them". Both halves are real: `reqwest` sends no
`User-Agent` unless one is configured, and nothing in the crate could
attach a caller-supplied header.

Add `AI_MEMORY_LLM_HEADERS` (`llm_headers` in config.toml): comma-separated
`Name=Value` / `Name: Value` entries, parsed and validated once at the
configuration boundary into a typed `ExtraHeaders`, then sent on every chat
request. This follows the rule provider auth already obeys — providers
consume typed material and never re-parse operator strings — and means a
malformed entry fails at startup rather than on the first consolidation
pass. Headers ai-memory sets itself are refused rather than duplicated:
`RequestBuilder::header` appends, so a second `authorization` would break
the request instead of overriding it. Values are marked sensitive and never
logged; `Debug` prints names only.

Two defaults ride on the same mechanism, layered *under* the operator's so
an explicit entry always wins:

- `User-Agent: ai-memory/<version>` on every provider. Not opencode-specific:
  an unattributable request is the one a rate limiter throttles first, and
  any gateway benefits from knowing what called it. Copilot is excluded — it
  keeps `GitHubCopilotChat/<version>`, the agent GitHub's API expects.
- `x-opencode-session` on the `opencode` provider, one id per process, since
  that header is Zen/Go's own request-correlation field.

Zen/Go permits this. Its documentation ("Where can I use it?", docs/go.mdx)
says Go "is designed to be used with OpenCode and other popular coding
agents that produce a similar types of requests", documents the
`https://opencode.ai/zen/go/v1/...` endpoints for direct use, and asks that
the calling tool "does not generate abusive traffic" and "properly
identifies itself (no broad user agents)". Hence naming ai-memory in the
agent string rather than copying OpenCode's own — identifying the caller is
the requirement, and impersonation would defeat it.

Integration tests drive `build_provider` against wiremock: asserting the
header is on the struct is not the same claim as asserting the right single
value is on the wire.
2026-09-03 12:02:07 +00:00
AkitaOnRailsandClaude Fable 5 6498ac5703 fix(install-hooks): generated TS integrations spool failed deliveries
The generated TypeScript integrations (OpenCode, OMP, Pi, OpenClaw)
fired-and-forgot every hook POST, so a session run while the server was
unreachable was silently lost — unlike the shell hooks, which have
spooled since day one. Failed (network or 5xx) deliveries are now
written to <data_dir>/hook-spool/ in the CLI spool's exact on-disk
contract (filenames, SpoolEntry JSON, permissions, tmp+rename), so
`ai-memory hook-drain` and the shell hooks' piggyback drain deliver
them too, and each integration drains its own spool once the server is
reachable. Entries carry an ingest_key minted at spool time, making
concurrent drains idempotent server-side.

Implemented as one shared TS runtime (render_shared::ts_spool_runtime)
applied to the queue templates by a fail-loud source transformation and
embedded in the OpenClaw template, so all integrations share a single
audited spool implementation.

Verified end to end under node: offline spool -> offline drain keeps
entries -> online drain delivers with bearer and deletes; and a
TS-written entry drained by the real `ai-memory hook-drain` binary.

Closes #580

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-02 08:48:30 -03:00
Matheus CardosoandCursor bafa7c964e Merge remote-tracking branch 'upstream/main' into feat/llm-reasoning-effort
Keep both Unreleased changelog entries and combine config.rs
auth-secret validation with the reasoning-effort tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 11:22:59 -03:00
15178b885e feat(admin): add password sessions and separated API credentials (#533)
* feat(auth): add browser sessions and API credentials

* fix(auth): preserve 1.x compatibility paths

* fix(changelog): preserve released sections

* docs(auth): tie mirror triggers to 2.0 cutover

* docs(store): correct mirror migration version

* docs(changelog): link admin console PR

* fix(migrations): renumber human_auth/api_credentials to V54/V55

Main gained V52__purged_sessions_tombstones and V53__page_embed_failures
after this branch was opened, so the merge produced four migration files
across two version numbers. Git saw no conflict - the filenames differ -
and the collision only surfaces at runtime:

    UNIQUE constraint failed: refinery_schema_history.version

on a fresh database, so the merged tree could not open a store at all.

Renumbered V52__human_auth -> V54 and V53__api_credentials -> V55, and
shifted the version numbers the tests pin: `run_to`/`open_to` targets, the
`schema_version` assertions, the rollback assertion, and the test names.
The pre-migration fixture point moves 51 -> 53 because main's V52/V53
create unrelated tables (purged_sessions, page_embed_failures) and touch
neither `users` nor any table these migrations rewrite.

Migration content is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm

---------

Co-authored-by: AkitaOnRails <fabioakita@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 23:26:41 -03:00
676e55f43b feat(cli): add interactive workstream resume picker (#534)
* feat(cli): add interactive workstream resume picker

* chore(changelog): restore the blank line before [1.36.0]

The branch dropped one blank line separating the end of [1.37.0] from the
[1.36.0] heading. Whitespace only, but it sits inside an already-released
section, so `check-changelog-frozen.sh` rejects it - correctly: the guard
cannot tell a stray edit from a rewritten entry, and should not try.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm

---------

Co-authored-by: AkitaOnRails <fabioakita@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 22:58:59 -03:00
AkitaOnRails 95edbe034e Merge remote-tracking branch 'origin/main' into test-557
# Conflicts:
#	CHANGELOG.md
#	Cargo.lock
2026-08-31 22:47:34 -03:00
ff4f260267 feat(cli): add ai-memory rename-workstream for checkout-local renames (#538)
Workstream names were fixed at `run --new` time and had no correction path,
so a typo outlived the work it labelled and the only escape was starting a
new workstream and abandoning the ledger attached to the old one.

The rename selects by current name or by the stable id `workstreams` prints,
resolving the same (workspace, project, repository, worktree) identity `run`
selects with. Both selectors repeat the checkout predicate: `workstreams.id`
is globally unique, so without it a caller holding an id from another
checkout could retitle a workstream their request never named. An id outside
the resolved scope reads as absent rather than renamable.

It is metadata only. `workstream_events`, `managed_runs`, and
`workstream_native_sessions` all key on `workstreams.id`, so the rename is a
single-row update with nothing to cascade, and `selected_at` and
`updated_at` are deliberately left untouched — relabelling is not activity.
Neither the discovery listing order nor the workstream a bare `ai-memory
run` resumes moves as a side effect of fixing a typo.

The destination is validated exactly like a `--new` name and refused with a
named conflict when another workstream in the same checkout already holds
it, turning the UNIQUE constraint into a typed error instead of a bare
SQLite failure. Renaming a workstream to the name it already has writes
nothing and is not an error.

The new `/workstream/rename` route requires NormalWrite rather than the
NormalRead its sibling discovery read uses, and resolves scope through
`lookup_existing_scope` so a rename never creates the workspace or project
it names. The Docker shell wrapper routes the command through its native
host client alongside `run`, `show`, `continue`, and `workstreams`, since
repository identity is a host resource the helper container cannot see.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: AkitaOnRails <fabioakita@gmail.com>
2026-08-31 22:23:50 -03:00
Matheus CardosoandCursor 8f664f78ab feat(llm): map typed reasoning effort onto native provider fields
Config typos were becoming provider 400s on the first consolidation
call. Fail closed at load, then clamp each host to its published
enum so OpenAI/Anthropic/Grok/OpenRouter/Codex get a valid wire value.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 20:02:03 -03:00
Bruno CostaandAkitaOnRails 3f0af7d9ac feat(hooks): add ZCode (z.ai) lifecycle-hook integration (#512) (#532)
Follow the Zero model: exec-form native commands (type: "process", no
shell) merged into the root hooks block of ~/.zcode/cli/config.json
around third-party hooks, idempotent via the statusMessage ownership
marker. Wire the six documented triggers (SessionStart, UserPromptSubmit,
PreToolUse, PostToolUse, PostToolUseFailure, Stop) — PostToolUseFailure
fires instead of PostToolUse when a tool throws and maps the payload
error to the observation outcome. PermissionRequest is deliberately not
installed: its hook chain races the interactive permission client, so
passive capture is unreliable by design.

ZCode injects SessionStart stdout as model context
(hookSpecificOutput.additionalContext), verified live against engine
v0.16.5, so session-start delivers the prior session's handoff like
Claude Code. There is no true SessionEnd (Stop is per-turn), so
finalize-session --agent zcode closes sessions.

- AgentKind::Zcode + V51 sessions CHECK migration, alias zai
- dual camelCase/snake_case capture, closed-tool gating with
  metadata-only degradation for unknown payload shapes
- apply raises maxOutputBytes to 64 KiB only when the user has not
  chosen their own value, so a fetched handoff is not truncated
- flagged integration covers the existing guard arrays and parity tests

Co-authored-by: AkitaOnRails <fabioakita@gmail.com>
2026-08-30 18:55:33 -03:00
Bruno CostaandAkitaOnRails 022e9d8690 feat(mcp): add ZCode as an install-mcp client (#511) (#529)
ZCode (z.ai) keeps its user-scope MCP config at ~/.zcode/cli/config.json
with servers under the nested `mcp.servers` map — the same shape Zero
and OpenClaw already use. Its entry schema is strict: any key outside
type/url/headers/enabled/timeoutMs makes ZCode drop the server silently,
so the generated registration carries exactly type, url, and an
Authorization bearer header when a token is configured.

Adds the `McpClient::Zcode` variant (name `zcode`), the default config
path, the NestedMcpServers location, the strict entry builder, the
render_zcode snippet, the uninstall sweep with the ["mcp","servers"]
JSON pointer, and the infer_installed_mcp_config arm. --apply merges in
place preserving sibling servers and is idempotent. MCP-only for now:
ZCode lifecycle hooks are tracked separately in #512.

Tests pin the destination path, the strict entry shape, the CLI parse,
sibling preservation with idempotent apply, and the full
install/uninstall round-trip. Docs: docs/mcp-install.md section +
one-shot list, README support matrix, docs/install.md, CHANGELOG.

Co-authored-by: AkitaOnRails <fabioakita@gmail.com>
2026-08-30 13:04:34 -03:00
2a9f6ead93 feat(config): add EMBEDDING_API_KEY for embedding-only credentials (#514)
Embeddings are already independently configurable — provider, model,
dimension and base URL each have their own setting — but there was no key
to go with them. `openai_embedding_api_key` returned `OPENAI_API_KEY`
before the base-URL check ran, so pointing `AI_MEMORY_EMBEDDING_BASE_URL`
at a second provider sent it the chat model's credential. The existing
`LLM_API_KEY` fallback only fires when `OPENAI_API_KEY` is absent, which
also takes the `openai` chat provider down, since it reads that same
variable.

`voyage` and `google`/`gemini` already name their own key and are
untouched. Scoped to the two embedders that borrowed another role's:
`openai` resolves EMBEDDING_API_KEY -> OPENAI_API_KEY -> LLM_API_KEY (the
last still only with a custom base URL), and `openai-compat` resolves
EMBEDDING_API_KEY -> LLM_API_KEY, staying keyless when neither is set.

With the variable absent, resolution is byte-identical to before.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: AkitaOnRails <fabioakita@gmail.com>
2026-08-28 15:48:08 -03:00
7e02f09f3b feat(cli): add ai-memory workstreams checkout-local discovery (#499)
List the managed workstreams that `run --workstream` can select from the
current checkout, so branching between lines of work no longer requires
remembering names or reading the database by hand.

The list resolves the same (workspace, project, repository, worktree)
identity `run` selects with, puts the current selection first, then orders
by recent activity. Rows carry the linked harnesses and the stable
workstream id that `workstream-search --workstream-id` accepts.

Discovery is a read of workstream metadata only: checkout paths, repository
and worktree fingerprints, and native session ids never travel back to the
client. The new `/workstream/recent` route requires NormalRead, resolves
scope through `lookup_existing_scope` (no-create, fails closed), and bounds
the limit server-side.

The Docker shell wrapper routes the command through its native host client
alongside `run`, `show`, and `continue`, since repository identity is a host
resource the helper container cannot see.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: AkitaOnRails <fabioakita@gmail.com>
2026-08-28 15:07:38 -03:00
lhzapata bc31f180ec fix(routing): pin managed skill payloads to LF (#502) 2026-08-27 18:15:15 -03:00
35aa307c24 feat(agents): add Pool (Poolside Agent CLI) hook capture and finalize-session (#476)
Pool joins as a first-class AgentKind for lifecycle-hook capture:

- AgentKind::Pool (wire `pool`, alias `poolside`) in ai-memory-core, added
  to ALL, serde, and from_wire. SessionStart handoff injection stays off:
  Pool tolerates hook stdout, but model-visible context injection is not
  demonstrated, and accepting a handoff is destructive — it remains
  available via the MCP memory_handoff_accept tool.
- V50 forward migration rebuilds the sessions CHECK constraint and the
  scope-pairing triggers with the `pool` value without losing rows,
  following the V47 (command-code) pattern.
- Hook ingestion recognizes Pool's documented snake_case session_id / cwd /
  tool_name / tool_input payload for concrete session attribution and
  closed tool-family titles, and enforces [capture] ignore_paths for
  Pool's read/edit/write/remove file tools; unknown payload shapes degrade
  to metadata-only capture.
- hooks/pool/ bundle: five events (SessionStart, UserPromptSubmit,
  PreToolUse, PostToolUse, Stop) as .sh scripts with .ps1 peers, covered
  by the bundle parity test.
- install-hooks --agent pool and setup-agent --agent pool stage the
  scripts and print a ready-to-paste repo-root .poolside/settings.yaml
  hooks: snippet — Pool's hook config is project-scoped YAML, so
  ai-memory deliberately never writes project-local files.
  finalize-session --agent pool closes sessions: Pool has no true
  session-end event (Stop is a turn boundary), the same class as Codex
  and Antigravity CLI.
- README support matrix and docs/install.md record the hooks-only tier.
  No first-party install-mcp client and no managed workstream
  (ai-memory run pool) are claimed: Pool's native session-store contract
  is not demonstrated, per docs/managed-harness-contributions.md.

Verified against Poolside CLI v1.0.16 (macOS).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: AkitaOnRails <fabioakita@gmail.com>
2026-08-24 16:16:43 -03:00
Fabio Akita d6a151dd73 fix(capture): correct allowlist enforcement boundary and warn when unenforced (#480)
#475 shipped documentation saying the allowlist mode is "not per-agent: the
mode is stored once in the data directory and every agent's hook honours
it". The second half is false, and I wrote it.

The gate runs inside the native `ai-memory hook` binary, immediately before
the spool write. Script-based installs POST to the server directly and
never execute that binary, so nothing reads the mode: the
`AI_MEMORY_HOOK_PLATFORM` override, the Docker host wrapper, and
`setup-agent` snippets are all unenforced. This is the same boundary
`[capture] ignore_paths` has always documented, and #475 should have said
so instead of claiming something broader.

A false privacy guarantee is worse than a documented limitation, and worse
here than elsewhere: the whole point of the mode is that an operator with
a legal confidentiality duty can rely on it. Someone reading the old
sentence would trust a protection their install may not have.

Found while auditing #476, which adds an agent shipping script hook
bundles. That PR is not the cause: `install-hooks --agent pool` renders the
native command like every other agent, and its `.sh`/`.ps1` files are the
documented script-fallback artifact for setup-agent/docker snippets. The
defect is entirely in #475's wording.

`install-hooks --apply` now warns when it stores a mode the install it is
writing cannot enforce, keyed on the same native-platform predicate
`--capture-assistant` already uses. Verified in both directions: silent on
the native default, warning under `AI_MEMORY_HOOK_PLATFORM=posix`. The
predicate reads an environment variable and `std::env::set_var` is
unavailable under `unsafe_code = "forbid"`, so that run is the evidence
rather than a unit test.

The mode is still stored when unenforceable, so a later native reinstall
picks it up rather than silently losing the operator's choice.

Refs #446
2026-08-24 16:02:00 -03:00
Fabio Akita f99a60f0d3 feat(capture): add allowlist mode so unmarked repos capture nothing (#475)
By default a repository with no `.ai-memory.toml` marker is still captured,
so the marker can only ever narrow what an already-captured repository
sends. Forgetting one therefore captures *more*. For an operator working
across many checkouts where some carry material under a legal
confidentiality duty, that is the wrong direction to fail in (#446).

`install-hooks --apply --capture-mode allowlist` inverts it: a repository
without a marker emits no lifecycle event at all, and forgetting a marker
now costs recall rather than confidentiality.

Two things shaped the implementation.

The gate is independent of the per-event capture policy. `CapturePolicy::
inspect` runs only for tool events, so expressing this as a
`CaptureDisposition` would have dropped tool calls while leaving
UserPromptSubmit, SessionStart/End and Stop bodies spooling — from a
repository the CLI reported as opted out. Prompt text is precisely what
#446 is about, so that would have shipped a false guarantee. The gate sits
before the spool write and covers every event.

The mode is stored once per install rather than baked into each agent's
hook command. There are ~15 render paths building hook commands; one
missed path would mean silent capture for that agent. A single file in the
data dir covers them all, and makes the reporter's binding requirement —
that the protection survive `install-hooks --apply`, including the refresh
inside `upgrade` — true by construction rather than by preservation logic
a future render path could forget.

An unreadable or unrecognised mode file reads as the historical default,
not as allowlist: tightening is the operator's explicit choice, and
failing the other way would let a corrupt file silently stop all capture.

`--check-capture` now reports `capture_mode`, `marker_present` and
`admits_capture`, so an opt-out can be verified without sending anything.
Its test now pins the exact key set rather than a field count, since that
output is how an operator confirms the protection.

Default behaviour is unchanged; `--capture-mode denylist` restores it.

Refs #446
2026-08-24 14:33:37 -03:00
YANGYUandlihuiyang1024 2b4a0238d3 docs(install): document hook latency expectations (#472)
Co-authored-by: lihuiyang1024 <249777924+lihuiyang1024@users.noreply.github.com>
2026-08-24 12:24:56 -03:00
Vitor Vilas Boas 9f11139f27 fix(upgrade): verify compose container ownership 2026-08-23 19:25:20 -03:00
AkitaOnRailsandClaude Opus 5 90ad26529b docs(install): state what mise actually verifies
The mise section claimed the GitHub backend "verifies its checksum, GitHub
artifact attestation, and SLSA provenance". Only the first is true here.

`release.yml` runs no `attest-build-provenance` step, and the attestations
API returns 404 for this repository — the two `provenance: false` lines in
that workflow are buildx image settings, not artifact attestation. The
`.sha256` sidecars are real and published, so checksum verification does
happen.

Claiming a supply-chain guarantee that is not there is worse than claiming
none: someone choosing this install path for a tool that holds their
private notes would be relying on a check nobody performs. Reworded to say
what mise does today, and to note the capability exists if the workflow
later emits attestations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 17:40:18 -03:00
Murillofilho86andClaude Sonnet 5 20a762a46f docs(install): document mise github backend, unblock cargo install status
`mise use -g github:akitaonrails/ai-memory` already works today against
the existing tagged GitHub Releases — verified live: it matches the
`ai-memory-<os>-<arch>.tar.gz` asset naming, checks checksum, artifact
attestation, and SLSA provenance, then extracts and runs. No plugin,
registry entry, or workflow change needed, so document it as the
recommended no-toolchain path instead of only pointing at build-from-source.

`cargo install ai-memory` can't ship as the v0.3 roadmap scoped it: both
`ai-memory` and `ai-memory-core` (the workspace's foundational internal
crate) are already taken on crates.io by unrelated projects. Record the
blocker in the roadmap rather than silently stalling on it — publishing
needs a registry-naming decision first.

Also correct the roadmap's stale `Handoff` field grouping entry, which
still described the struct as ungrouped and "not urgent" after #457
already shipped the sub-struct grouping.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 13:43:33 -03:00
AkitaOnRails 1559a730b3 Merge remote-tracking branch 'origin/main' into merge-448
# Conflicts:
#	CHANGELOG.md
2026-08-21 13:29:33 -03:00
Lucas Oliveira d6964f08e8 feat(mcp): serve Gemini/Vertex-safe tool schemas behind an opt-in
A client that forwards ai-memory's MCP tool schemas to a Gemini/Vertex
model failed the whole session at tools/list:

    Unable to submit request because `ai-memory_memory_auto_improve`
    functionDeclaration `parameters.max_proposals` schema specified other
    fields alongside any_of. When using any_of, it must be the only field
    set.

rmcp generates schemas with schemars' draft-2020-12 settings and applies
no post-processing, so schemars renders every optional tool argument as a
nullable union: `{"description": …, "type": ["integer","null"], …}`.
Google's Schema (functionDeclaration.parameters) takes a single `type` and
treats any_of as exclusive with every sibling key, so a converter that
forwards ours verbatim emits any_of with `description` still beside it.
The field named in the error is arbitrary — every Option argument on all
18 tools has that shape.

Gemini CLI never hits this because it performs the same collapse
client-side before building the functionDeclaration, which is also the
evidence that $ref, $defs, title, const, and format: "uint" reach Vertex
untouched and are not part of the problem. Pass-through clients such as
OpenCode on a Vertex model have no such pass.

Generalize the shipped root-combinator patch (#412) into an ordered
SchemaDialect so the operator's configured floor and a request's ?flavor=
marker combine with a plain max, and add the GeminiSafe dialect on top: it
strips root combinators as before, collapses `type: [T, "null"]` to
`type: T` plus `nullable: true`, and merges an Option-shaped combinator
that carries siblings into its lone real branch, parent keys winning so the
doc comment survives. Nothing else is rewritten. A union of several real
branches and a $defs entry like FeedbackKind's oneOf-of-consts are left
alone — neither has a single-subschema equivalent, and narrowing them would
change the advertised contract for clients that work today.

Opt in per client with `?flavor=gemini` (alias `vertex`), or server-wide
with `gemini_safe_schemas` / AI_MEMORY_GEMINI_SAFE_SCHEMAS for clients that
cannot carry a query marker. Default output is byte-identical, and runtime
argument validation is unchanged in every dialect.

install-mcp deliberately appends no marker: OpenCode is provider-agnostic,
and Gemini CLI and Antigravity CLI need none.

A guard walks every registered tool under the new dialect and rejects any
array `type` or combinator-with-siblings, so a future optional argument
cannot silently reintroduce the bug.
2026-08-21 12:58:09 +00:00
lihuiyang1024 ba28482f39 feat(install-hooks): add prompt capture opt-out 2026-08-21 16:35:05 +08:00
AkitaOnRailsandClaude Opus 5 109918cbba docs: correct the Pi/OMP extension filenames and document profiles
Post-audit of the 1.29.0 range found eight references to
`extensions/ai-memory.ts` still in README.md, docs/install.md and
docs/mcp-install.md — including the top-level support table — after the
rename to `ai-memory-pi.ts` / `ai-memory-omp.ts`.

Stale docs here are worse than merely wrong. Each agent loads every direct
`*.ts` in its extensions directory, so an operator following the old
instructions by hand would create a second ai-memory extension alongside
the installed one and capture every event twice — the exact duplicate-load
the rename and the new collision warning exist to prevent.

Also documents `--profile` and `OMP_PROFILE`, which shipped with no
coverage outside the changelog, and states the `PI_CODING_AGENT_DIR`
precedence in the same place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:36:59 -03:00
AkitaOnRails 1e058a1402 Merge remote-tracking branch 'origin/main' into merge-435
# Conflicts:
#	CHANGELOG.md
2026-08-19 15:10:53 -03:00