All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
This file holds the current major line. Earlier lines are archived per major
version in docs/changelog/:
2.x, 1.x and 0.x.
- Routed OpenCode's
claude-haiku-5-5through the Anthropic Messages API for both Go and Zen instead of the unsupported Chat Completions protocol. (#1470) - Ignored a UTF-8 byte-order mark at the start of
run --env-fileinput so the first environment variable keeps its intended name. (#1465) - Invoked GitHub Copilot CLI's native Windows lifecycle commands with
PowerShell's call operator. Quoted
ai-memory.exepaths now execute instead of producing a parser error that made fail-closed pre-tool hooks deny every tool call. (#1462) - Repaired the exact migration checksum recorded by v2.2.1 for the original V62 page-window migration before running strict migration validation. This removes the false divergence error on every upgraded-server start while keeping unknown migration edits fatal. CI now rejects changes to migration files already present on a PR's base branch. (#1443)
- Sent
response_format=json_schemafor structured OpenCode Chat Completions calls, with the existing tolerant fallback for models that reject it, somemory_query answer=trueno longer receives unstructured prose by default. (#1441) - Preserved third-party commands that share an event matcher group with an
ai-memory hook when
install-hooksreapplies or removes its own entry. (#1453) - Accepted a UTF-8 byte-order mark before imported page frontmatter in the wiki sync companion. (#1452)
- Skipped H1-like comment lines inside fenced code when the importer derives page titles, including AgentMemory vault imports. (#1451)
- Recognized importer frontmatter whose closing fence is the final line of a file without a trailing newline. (#1450)
- Encoded spaces as
%20instead of+in importer URL path segments, preserving workspace, project, and page names that contain spaces. (#1449) - Recognized exclamation-mark punctuation in short automatic-handoff
acknowledgments such as
thanks!andperfect!. (#1448) - Treated a blank
session_idas omitted when starting an automatic improvement review, matching the other session-scoped MCP tools. (#1447) - Returned MCP
invalid_paramsfor caller-supplied invalid page paths, tiers, and scopes instead of misreporting them as internal server errors. (#1445) - Parsed an empty Markdown frontmatter block (
---followed immediately by its closing fence) as frontmatter instead of folding it and later thematic breaks into the page body. (#1444) - Kept hook events whose JSON payload contains half of a UTF-16 surrogate pair
as a
\uescape, such as tool output cut in the middle of an emoji. Native, shell, PowerShell and direct hook clients now share the bounded server-side repair, replacing the unpaired escape with U+FFFD instead of dropping the whole event. (#1432) - Model-produced page paths are now cleaned of the remaining shapes
ensure_portablerefuses, a component ending in a dot or space and a DOS device name likeaux.md, so one such path no longer aborts every page in a consolidation or bootstrap batch. (#1430) - Preserved FTS5 column filters inside parenthesized groups, so queries such
as
(title:handoff)no longer broaden into a search for the literal wordtitleacross every indexed field. (#1442) - Revalidated session-purge tombstones inside the reindex write transaction, preventing a queued watcher update from recreating a deleted summary after file cleanup failed. Kept surviving manual replacements at the same path editable through the watcher (#1439).
- Added exact hover timestamps to
/webpage metadata, made the OKF upgrade dialog keyboard accessible, and added a labelled main-navigation landmark with current-page state and narrow-screen wrapping. (#1391, #1392, #1393) - Made guarded admin routes treat an omitted
confirmasfalse, returning the documented 400 response without performing the operation. (#1394) - Retried OpenAI-style embedding requests on HTTP 503 as well as 429, honored
bounded whole-second
Retry-Aftervalues, and madeai-memory embedfail after printing a report that contains failed pages. (#1397) - Made pending-write diffs return an internal error when the existing page cannot be read instead of presenting an update as a new page. (#1399)
- Refused page-path components over 255 UTF-8 bytes and additional Windows DOS device spellings, while keeping existing non-portable pages readable. (#1400, #1407)
- Recognized frontmatter whose closing fence is at end of file, ignored H1-like comments inside fenced code when deriving titles, and gave empty exported wikilink labels their target as visible text. (#1402, #1403, #1419)
- Preserved FTS5 prefix operators and
title:/body:filters on punctuated terms, and stripped managed-workstream column prefixes only at word starts. (#1404, #1405, #1406) - Stripped reasoning that ends in a bare closing tag from OpenAI-compatible structured responses and found Cursor Agent JSON inside bracketed prose. (#1408, #1409)
- Matched automatic-handoff acknowledgments as whole phrases, treated Windows drive roots as broad session anchors, preserved Antigravity output around invalid UTF-8 bytes, and stopped repeating the last prompt in automatic handoffs. (#1410, #1411, #1412, #1418)
- Returned
invalid paramsfor malformed handoff and message ids and accepted surrounding whitespace consistently on handoff cancellation. (#1413) - Fixed A2 compaction summaries when a heading directly precedes the first paragraph or the page uses CRLF line endings. (#1414)
- Excluded TTL-expired pages from cross-project degree metrics, memory-health counts and details, and project/workspace page-count and timestamp summaries. (#1422, #1423, #1426)
- Stopped LLM provider and embedder requests from following a redirect to
another origin. reqwest drops
Authorizationon a cross-host hop but keeps every other header, sox-api-key,x-goog-api-keyand operator headers fromllm_headersreached whatever host a configured endpoint redirected to. Redirects inside the same origin still work; one that changes scheme, host or port now fails the request and names the origin it pointed at. (#1396) - Required Write access before a session start records a project's
[profile]profile_contribute/profile_consumeflags. A Read-only viewer's explicit values now only narrow its own session instead of changing the flags for every operator in the project. (#1421) - Stopped the server from opening files named in a hook payload. Antigravity
step-log enrichment now runs only in the client-side
ai-memory hook; the/hookand/hook/batchingest paths store thetool_responsethe client sent and never readartifactDirectoryPathortranscriptPathlocations on the server's own disk. (#1420) - Fixed
memory_lintreporting findings for TTL-expired pages. The lint pass now omits expired pages before evaluating episodic staleness, rule suggestions, duplicate titles, and vector or LLM contradiction candidates, preventing findings for expired pages that are pending forget-sweep deletion. (#1390) - Fixed curator reporting TTL-expired pages as cold episodic candidates,
stale slots, or title collisions. The curator now filters out expired pages
before evaluating episodic retention scores, and uses
list_unexpired_pageswhen evaluating slot staleness and title uniqueness. (#1389) - Resolved percent-encoded ordinary Markdown link paths before indexing and web-route encoding, preserving spaces and Unicode filenames without double decoding or changing project scope. (#1382)
- Encoded a literal
%in an exported wikilink target as%25, so an OKF bundle's Markdown link still resolves to the same literal page path. (#1456) - Preserved indexed Markdown links with escaped or nested brackets in their labels, including links exported in OKF bundles and reindexed after import. (#1380)
- Fixed
open_feedback_findingsreporting feedback attached to TTL-expired pages. The store query now filters out expired page targets against the current timestamp, preventingmemory_lintfrom emittingfeedback_flaggedfindings for expired pages that are pending forget-sweep deletion. (#1388) - Fixed
existing_latest_pathsignoring page TTL expiration. The query now filters out expired pages against the current timestamp, preventing the consolidation link guard from keeping wikilinks or typed relations targeting expired pages that are pending forget-sweep deletion. (#1387) - Fixed
page_belief_inputscounting contradictions against TTL-expired target pages as active live contradictions. When a page declares acontradictsrelation to a target whoseexpires_atTTL has passed, the declaration is stale and must not weaken belief strength or reduce confidence in retrieval ranking.contradiction_sqlnow applies thenot_expiredguard on the target page, aligning belief input aggregation with the lint's resolved/stale split. (#1386) - Stopped the LLM contradiction pass from auditing its previous
_lint/report.mdas a knowledge page, which could recycle stale findings or displace a real page from the bounded prompt. (#1455) - Kept the previous backup or OKF export intact when an archive download is interrupted, and publish completed downloads atomically. Successful downloads replace destination symlinks with private files rather than changing their targets or inheriting their permissions. (#1381)
- Read a first-line
.ai-memory.tomlscope key, flag, oraliasesdeclaration when the marker starts with a UTF-8 BOM, in both native and legacy POSIX hook routing. (#1457) - Counted a latest page whose only stored vector belongs to another provider,
model, or dimension as missing an embedding in
ai-memory statusand the web/healthpage, so the count no longer reads zero after an embedding-model switch. (#1431) - Bounded free-text search before the FTS5 parser sees it. A query of 100k
distinct terms spent ~17.5 s in query preparation alone (and a ~500 KB
document ~59 s end to end), and a NUL byte reached the phrase parser as
end-of-input, surfacing
sqlite: unterminated stringas an internal error instead of a result. Query text is now folded (NUL becomes a space) and cut to the first 4 KiB on a UTF-8 boundary; ordinary queries are unchanged. (#1434) - Fixed generated OKF directory indexes so names containing spaces, Markdown punctuation, literal URL delimiters, or Unicode remain readable, clickable links in exports and newly generated migration indexes. (#1438)
- Made the SQLite writer wait out the 5-second busy timeout when another
process holds the database, such as an external
wal_checkpoint, instead of failing hook ingestion at once with "database is locked". (#1460) - Retried the Google, Copilot and Voyage embedders on HTTP 503 as well as 429,
with the bounded whole-second
Retry-Afterpolicy the OpenAI-style embedders already use. A brief provider overload no longer fails backfill pages, inline page embeds or query vectors. Voyage previously did not retry at all. (#1466) - Returned MCP
invalid_paramsfrommemory_queryfor conflictingglobal,scopes,workspace/projectandas_ofarguments and for anas_ofthat is not an ISO-8601 instant, instead of misreporting them as internal server errors. (#1468)
3.1.1 - 2026-10-17
-
Prevented the Windows executable from overflowing its small default main thread stack as the async CLI command tree grows. The top-level dispatch now runs on an explicitly sized stack on Windows; the native Windows suite had reached the point where every integration test that launched
ai-memoryaborted before handling its command. -
Fixed native hook events getting stranded forever when a harness killed the process after the spool file was synced but before its atomic rename. A later drain adopts complete, valid
*.json.tmpentries after a five-minute grace period and removes malformed old remnants. Claude Code settings installs now giveSessionEnda 15-second timeout instead of its 1.5-second default budget; plugin hooks rely on recovery because Claude does not let plugin timeouts raise that budget. (#1377) -
Hardened OIDC credential transport after CodeQL alert #176: the shared refresh-grant sink now requires a typed TLS-or-loopback endpoint, and device authorization and token polling revalidate their endpoints immediately before each request. A caller-mutated discovery object can no longer bypass the login-time HTTPS check.
-
On Windows,
setupnow starts the logon host with a plain executable path, and the host passes plain drive/UNC data-directory and config arguments to the supervisor and server it launches, instead of the\\?\verbatim prefix a canonical path carries. TheRunvalue was already plain (3.1.0); this covers the command lines built fromcurrent_exe()and the canonical data dir. This path cleanup does not by itself resolve Bitdefender's behavioral detection. (#1280) -
Fixed an explicit
llm_base_url(orAI_MEMORY_LLM_BASE_URL) being dropped for theanthropicandopenaiproviders. Config resolved the override for every provider, as documented, but the provider factory never applied it to those two, so requests and the API key went straight to api.anthropic.com or api.openai.com with nothing in the log. A gateway or reverse proxy set in front of either vendor now receives the traffic. The docs now name the backends that take no override: the ChatGPT backend behindopenai-oauthandcodex, andcursor's CLI. (#1347) -
Corrected the
ExtractionState::NotApplicabledocumentation and aligned the generated OpenCode capture policy with the native evaluator: only an active policy dropping a search/list tool reportsnot-applicable; an invalid policy reportsextracted.ai-memory hook --check-capturealso usesnot-applicablewhen no policy was evaluated. (#1352) -
ai-memory auth login openai-oauthandauth login copilotno longer print a failed endpoint's whole response body. Error bodies are bounded, scrubbed of secrets and terminal control sequences, then capped for display; provider errors, including OIDC login and refresh, use the same ordering so a secret at the display boundary is still redacted. (#1359) -
Fixed OIDC device login and refresh sending credential-bearing requests to non-TLS endpoints. Issuers and advertised or persisted device/token endpoints must now use
httpsoutside loopback, and authentication requests do not follow redirects. Plainhttpremains available for local test issuers onlocalhost,127.0.0.1and::1. (#1354) -
Fixed
install-instructionsprinting a false "Claude Code reads instead of AGENTS.md" note when$HOMEis reached through a symlink. The home.claude/CLAUDE.mdskip now compares with the same canonicalisingpaths_matchhelper the import check uses, so a symlinked home matches its resolved path. (#1345) -
Corrected stale claims in
docs/comparison.mdanddocs/competitive-parity.mdthat said Windows has no service step and the Windows install is experimental. The 3.1install.ps1+setupWindows service step is shipped production, not experimental; the "3.0 (unreleased)" observation-stream note is corrected to reflect the released 3.0 line. (#1271) -
Made the executable auto-improvement eval policy server-owned. Manual CLI and HTTP reviews now use
[auto_improve.eval]from the server process, and legacy requests that try to supply anevalobject fail closed with an upgrade message. -
Made managed cross-harness continuation survive quota exhaustion without a voluntary handoff. The startup packet now places its server-authored resume directive outside the untrusted transcript fence, and refreshed
CLAUDE.md/AGENTS.mdrouting tells an incoming agent that "continue", "resume", and "pick up where we left off" must consult the privateAI_MEMORY_PREVIOUS_SESSION_HISTORYdossier when the managed-run variables are present. The dossier remains untrusted evidence and stays local to the launcher machine. -
Fixed unbounded memory use in consolidation and auto-improve for long sessions: both paths now read a bounded projection of the session's observations instead of loading every row, keeping the LLM input byte-identical. Sessions with no observations in the consolidator's target scope (purged or foreign) are refused before any work, exactly as empty sessions were before. (#1363)
-
Fixed the session page's uncapped
## Promptslist. Above 500 prompts the page now lists the first 250 titles and the last 250, numbered by their real position, with one line between them saying how many were left out. The store reads at most 500 titles for it, and the page's prompt count and the digest stats still report the true total. (#1349) -
Fixed auto-improve approvals that failed because a
reject-policy admission webhook was unreachable, timed out, rate-limited or answered 5xx being stored as rejections. The proposal is still markedfailed, but the outage no longer enters the rejection buffer, so later reviews are not told to avoid a proposal nobody refused and rejection telemetry counts only real refusals. (#1358) -
Fixed consolidation persisting non-page
relations:targets in YAML frontmatter on disk. Raw titles containing dots, directory paths, or stemless.mdstrings inrelations:were previously written into frontmatter and retained byprune_relationswhenextract_relation_linksreturned an empty list, causing the wiki watcher to emit repeated warnings on every reconciliation pass. Targets are now validated throughextract_relation_linksbefore frontmatter assembly and during relation pruning. (#1353, #1366) -
Consolidation now also drops well-shaped same-project
relations:targets that name no existing page. Relations among pages in the same atomic batch remain valid, and ordinary same-project body wikilinks may still point to a page that will be written later. (#1353) -
Verified target latest status and expiration in
ReaderPool::contradiction_edges, so a contradiction pointing at a tombstoned or expired page is reported as unresolved instead of active, and expired source pages do not emit contradiction findings. (#1367) -
Verified target latest status and expiration in
ReaderPool::dangling_cross_project_links, so cross-project links to tombstoned or expired pages are reported as dangling instead of resolved, and expired source pages do not emit dangling link findings. (#1368) -
Fixed the
/webproject tree and its recent-activity list, the namespace listing, and the/api/v1pageslist and legacyrecentarray showing pages whoseexpires_atTTL had passed, which search,memory_recent, the incrementalrecentform and page history already hid. A project move and the curator still see every current page, and an exact-path read of an expired page still answers. (#1369) -
Fixed
memory_read_pagereturning a page whoseexpires_atTTL had passed with nothing to say so. The TTL design keeps an exact-path read answering for such a page whilememory_query,memory_recentand the briefing hide it, and promised the response would carryexpired: true; the flag was never written. The response now carriesexpired: truefor an expired page, on both the markdown and the DB-fallback branch, and is unchanged for a live one. (#1371) -
Fixed the
/webpage view showing a page whoseexpires_atTTL had passed exactly like a live one, although search and the project listings leave such a page out. The metadata row now carries anexpiredbadge beside the tier and kind; a live page renders as before. (#1372) -
Fixed
GET /admin/read-pageandai-memory read-pagereturning a page whoseexpires_atTTL had passed with nothing to say so, althoughsearchno longer lists such a page. The response now carriesexpired: truefor an expired page, on both the markdown and the DB-fallback branch, andread-pageprints anexpired: trueline (the field also appears in--json); a live page's output is unchanged. (#1373) -
Fixed web wikilink rendering generating dead links for directory and stemless targets. In markdown pages, wikilinks pointing to a directory (
[[notes/]]) or a stemless target ([[notes/.md]],[[.md]]) were rewritten into invalid page links that yielded 404 responses in the web browser. They now remain as literal text, aligning web wikilink preprocessing with the wiki engine's link extractor. (#1364) -
install-mcpnow writes the absolute path of theai-memorybinary as thecommandof every stdio bridge entry (Claude Code's session-aware entry, Muse Code and the session-less bridge of the other clients), as hook commands already did. A client started without~/.local/binon itsPATHno longer fails to spawn the bridge. The Docker wrapper keeps the bareai-memory, and existing entries with either form are still recognized. (#1362) -
Fixed the session-start brief hiding a page's body behind a longer frontmatter excerpt. When a pinned,
_rules/or_slots/page had asummary:orabstract:longer than its body, the brief printed that excerpt instead of the body, however much of[briefing] max_charswas left, so a small wiki whose pages fit did not get every body whole. Such a body now shows whole: it is priced at its own size when the brief decides which pages fit, and the bytes it saves over the excerpt go to other pages. A page with an empty body keeps its excerpt. (#1360) -
Fixed
ai-memory setupwiring OpenCode 2 with OpenCode 1 artifacts. A named--agent opencode(oropen-code), or an OpenCode found by the session store both majors share, always got the V1 function pluginplugins/ai-memory.tsand a directmcp.ai-memoryentry, which OpenCode 2 does not load.setupnow runsopencode --versiononce, asinstall-mcpandinstall-hooksalready did for the generic spelling, and wires the V2{ id, setup }plugin and nestedmcp.serversentry when it reports major 2. A failed probe writes no OpenCode wiring and is reported as a setup problem;--agent opencode2still forces V2 without probing. (#1356) -
Fixed
ai-memory mcp-bridgeexiting when a client sentserver/discoverbeforeinitialize. A client that supports both the 2026-07-28 MCP revision and the olderinitializehandshake, such as Antigravity CLI 1.4 and the Antigravity 2.24 app, sends that probe first and falls back toinitializewhen the server answers with an error. The bridge ended the session instead, so the fallback found a closed pipe (calling initialize: client is closing: EOF) and the client never connected. Untilinitializearrives, the bridge now answers every other request with a JSON-RPC Method not found error, ignores notifications such as the client cancelling its probe, and still answersping. A client that starts withinitializesees no change. (#1355)
3.1.0 - 2026-10-16
- Added first-party IBM Bob support.
install-mcp --client ibm-bob --apply(aliasbob) merges a native Streamable HTTP server into~/.bob/settings/mcp.json, using Bob's documentedlegacyprotocol negotiation so it skips the non-standardserver/discoverprobe.install-hooks --agent ibm-bob --applymerges the five events common to Bob IDE and Bob Shell into~/.bob/settings/settings.json, preserves other settings and hooks, enforces capture exclusions over Claude-shaped tool payloads, and delivers the pending handoff as plainSessionStartcontext. Bob has no trueSessionEnd, so finished sessions usefinalize-session --agent ibm-bob. Managed transcript resume is not claimed without a documented stable native transcript store. (#1331) - Added host-local native history continuation for managed harness switches.
When
ai-memory runmoves a workstream from one harness to another, it pins the previous native session, validates it against the exact checkout, and exposes a private temporary visible-history dossier to the receiving child throughAI_MEMORY_PREVIOUS_SESSION_HISTORY. The startup packet tells the agent to consult it for details omitted by the bounded portable ledger. The remote server receives no local path or transcript body; same-harness runs create nothing, foreign/missing stores fail closed to the portable fallback, and the dossier is deleted when the child exits. (#1327) - Added opt-in
[consolidation] verbatim_residue = true, which recovers a bounded set of literal error lines, paths, URLs, status codes and versions from observations omitted or truncated by the consolidation projection. The residue is marked as untrusted, grouped and indented so captured text cannot forge prompt structure, and never changes stored page content except through the existing structured LLM response. The default remains off and preserves existing prompt bytes. The eval harness can compare both states withab --residueandprojection --residue. (#1215) - Grouped interactive
statusanddoctorreports into sections with semantic colors, consistent margins and terminal-width wrapping, retaining every diagnostic field and remedy. Redirected,NO_COLOR,TERM=dumband JSON reports kept their existing stdout format. (#1313) - Added an opt-in server-rendered management console for users, sessions, and
personal MCP API keys, using existing authentication and SQLite storage.
Required the
management-uibuild feature and runtime switch. (#1231) - Added opt-in session inspection and owned-session purging for signed-in
browser users, enabled at runtime with
serve --enable-session-management(AI_MEMORY_ENABLE_SESSION_MANAGEMENT). A user can purge only a session they own in a project they may write; an Admin can purge any. Requested in #1231. (#1267) - Added opt-in self-service
aim_API keys for signed-in browser users, enabled at runtime withserve --enable-self-service-keys(AI_MEMORY_ENABLE_SELF_SERVICE_KEYS). Labels must be 1 to 128 characters and a user may hold at most 20 active keys. A self-issued key survives a password reset or account disable and must be revoked separately. Requested in #1231. (#1268) - Added
ai-memory search --global, which searches the wiki pages of every project from the terminal instead of only the current one. Each hit shows itsworkspace/projectin the text output and carriesworkspaceandprojectfields in--json. The flag cannot be combined with--workspaceor--project, and a search without it still resolves its scope from the current directory. The server side isGET /admin/searchwith noworkspace/project: its hits now includeworkspaceandprojectnext to the fields they already had, and the route stays root-only on a multi-user server. Global search covers wiki pages only; raw observations and a search limited to one workspace are not part of this change. (#1253) - Added kind and tier filters to the web search. The search page in
/webhas "Kind" and "Tier" selects, and/web/searchacceptskindandtierfor both a project search and a cross-project one. Every retrieval stream applies the filter in its own query, so other kinds cannot crowd out a rare one, a filtered search asks the streams for no more candidates than an unfiltered one, and the reranker scores only pages the filter keeps. Kind and tier both ignore case, a nonstandard kind is flagged on an empty result, and "Search all projects" keeps the filters. An unknowntieris a 400. (#1248) - Added
ai-memory setup --plan, which prints the plan and changes nothing, and--plan --json, which prints it as a JSON document with aformatversion for a script or front end to render. The plan now says what each detected agent was found by (its session store, its MCP config, or Muse's directory), numbers the agents, and lists the other agents--agentaccepts. At the prompt you can type the numbers of the agents to wire instead ofy; a number outside the plan changes nothing. (#1270) - Added a native Windows path to the two-command install.
install.ps1, published with each release next to its own.sha256, checks the release zip against that checksum, refuses archive entries that are absolute or hold.., unpacks it under%LOCALAPPDATA%\Programs\ai-memoryand adds that directory to the userPATH(AI_MEMORY_NO_MODIFY_PATH=1skips it), with no administrator rights.ai-memory setupnow has a service step on Windows: it writes anai-memoryvalue under the per-userRunkey that starts a hiddenai-memory service-hostat logon, and starts it right away. The value is written and read through the registry API, not by runningreg.exe: an unsigned binary runningreg.exe addon theRunkey is a common persistence signal, and Bitdefender's behavioral engine quarantinedai-memory.exefor it (#1280). Its paths are written plainly, without the\\?\prefix a canonical path carries. Stopping the host does not runtaskkilleither:setupanduninstallleave a stop request in the data dir, and the host kills the server it started and exits.taskkillis only the fallback for a host that does not answer within ten seconds (one started by an older binary, or a hung one) or a server whose host already died. The host runs the server with no console window and restarts it after a failure, giving up after five failures within ten seconds of starting.ai-memory uninstall --applystops the host and removes the value when it still has the shapesetupwrites and names this install's data dir, so an uninstall against another data dir (a second install, a test's temporary one) leaves the entry alone.ai-memory doctorandai-memory statuswarn (logon_binary_missing, also in their--jsonoutput and whenstatuscannot reach the server) when that entry names anai-memory.exethat no longer exists, which is what an antivirus quarantine leaves behind, and say how to restore the binary or remove the entry. (#1271, #1280, #1300, #1311) - Added
ai-memory setup --web, which shows the setup plan as a page in your browser and applies the agents you tick there by runningai-memory setup --yes --agent ...and streaming its output to the page. The page is served on127.0.0.1behind a one-time address printed in the terminal (opened in the browser unless--no-openor there is no desktop), refuses requests with anotherHost, without the address's secret, or, for a change, without the page's ownOriginand CSRF token, and holds no token. It stops on Finish, after 15 minutes without a request, or on Ctrl-C. (#1272) - Added a desktop installer preview,
companions/ai-memory-desktop: a Tauri app that uses an existingai-memoryinstall or copies its bundled one into the same per-user location asinstall.shandinstall.ps1, then shows theai-memory setup --webpage in its own window. CI builds unsigned Linux, macOS and Windows installers on manual dispatch; they are not attached to releases until the project signs them.setup --webgained a hidden--stop-on-stdin-eofflag so the page stops when the app does. (#1273) - A prompt that starts with
reload context(for examplereload context after last compaction, then …) now puts the stretch of the session that its last compaction dropped back in front of the agent: the prompts typed between the latest two compactions, verbatim; the last captured reply (with the assistant-capture opt-in); and the project's open handoffs, listed without claiming them. The new read-onlyGET /recaproute builds it from captured observations. Claude Code and Codex receive it with that prompt throughUserPromptSubmitadditionalContext, OpenCode V1/V2 through the plugin's system/context hook, and Grok Build on the nextPostToolUse. Serving the recap writes nothing, so a reload never consumes a baton (#1230). - Added first-party CodeBuddy Code (Tencent) support.
codebuddyis anAgentKind(migration V81), so hook events are attributed to it and a Claude Code-shaped tool payload (tool_name,tool_input,tool_response) is stored as a closed tool observation instead of an emptyothercapture.install-hooks --agent codebuddy(aliascode-buddy) merges nativeai-memory hook --event … --agent codebuddycommands for seven events (SessionStart,UserPromptSubmit,PreToolUse,PostToolUse,PreCompact,Stop,SessionEnd) into thehookskey of~/.codebuddy/settings.json, keeps sibling settings and your own hooks, and changes nothing on a re-run. There is no script bundle. TheSessionStarthook delivers the pending handoff throughhookSpecificOutput.additionalContext, and a capture-onlySessionStartorUserPromptSubmitprints nothing, since CodeBuddy appends that output to the session. ASessionStartwithoutcwdfalls back toCODEBUDDY_PROJECT_DIR.setupwires CodeBuddy when~/.codebuddyexists,setup-agent --agent codebuddyprints the hook block, anduninstallremoves the entries. Transcript backfill and headlesscodebuddy -psession close are not part of this change. Requested in #1299. (#1312) - Added the
memory_restore_pageMCP tool: withoutrevit lists the git checkpoints that changed a page, and withrevit writes that version back through the wiki write path (sanitizer, admission, slots and supersession apply), keeping the checkpoint's frontmatter. Restoring can bring back text a root deleted or purged, so both listing and restoring need the same admin authority as/admin/restore-page. The MCP surface is now 24 tools. (#1307) - Added an opt-in sweep of old hook event ledgers by age. With
[decay] ledger_retention_months = N(envAI_MEMORY_DECAY__LEDGER_RETENTION_MONTHS;0, the default, disables it; a negative value is rejected at startup), the forget sweep deletes a project'slog-YYYY-MM.mdonceNwhole UTC calendar months have passed after its month ended. A file qualifies only when its name is a rotated ledger and its content opens like a ledger, so a page that merely shares the name is never deleted; that content gate is repeated after the blocking admission round-trip.log.md, the current month and the previous month are never deleted whateverNis. The deletion goes through the wiki (Wiki::delete_event_ledger, under the mutation guard) and never writes the database; a dry run reportsledgers_prunableandledger_bytes_prunableand deletes nothing, and a real run reportsledgers_pruned,ledger_bytes_prunedandledger_page_rows_left(legacy page rows it left alone) in theforget-sweep/memory_forget_sweepreport. Each removal consults the blocking admission chain on the newdelete_event_ledgerop, the same check a page delete runs: areject-policy webhook keeps the file, which the sweep counts inledgers_undeleted, keeps going and retries on the next run, and non-blocking observers are told only after the unlink. The deletion is permanent on disk; the wiki's git history keeps the blobs a checkpoint already captured. (#1322)
ai-memory setupnow ends by naming the agents to restart and the question to ask one of them, "What do you remember about this project?", instead of a generic restart line.install.shnames the link's full path in itsnext:line when~/.local/binis not onPATH, so the suggested command no longer fails with "command not found". The AUR install and upgrade messages, and the README Fedora, Arch and macOS app steps, point toai-memory setupinstead of the manualinit,systemctlandinstall-*commands. (#1270)- Changed how the opt-in session-start brief (
[briefing] inject_on_session_start) spendsmax_chars. Every pinned,_rules/and_slots/page now gets a short excerpt first, taken from its frontmatterabstract:orsummary:, or else from the prose it opens with (read on into its first list items). The budget left deepens pages to a longer excerpt and then the whole body: pinned and_slots/pages first, then pages a few bytes short of whole, then the cheapest bodies. Before, the brief printed whole bodies in order, so one long_rules/page was cut with a[truncated by ...]note and every page after it became an "omitted" path. Bodies are no longer cut: a body that does not fit whole shows its excerpt. Four or more unpinned excerpts from one folder share a heading, and the omitted list and the closing note point atmemory_read_page. Small wikis whose pages fit still get every body whole. A project with many standing pages now sees all of them, fewer in full: pin a page, or give it anabstract:, to control what it shows. The change adds no marker key and no migration. (#1264)
- Restored fresh source installs after crates.io yanked every
ziprelease compatible with Candle 0.11. Cargo now patcheszip8.6.0 to its immutable upstream release commit, andcargo-denypermits only that repository with an exact revision. Locked builds keep the same reviewed code, whilecargo install --path crates/ai-memory-clican resolve from a clean graph again. The patch can be removed when Candle publishes support forzip9. - Fixed the forget sweep's raw-observation prune deleting the raw capture of
a session that still has a queued consolidation. A session resumed after its
first summary gets a new
session_consolidation_jobsrow (pendingorrunning) while its old summary page is still live, so the prune could delete the session's raw observations before the queued job read them, and the job would consolidate a truncated session. The prune and its dry-run count now share one exclusion: a session with apendingorrunningconsolidation job, or resumed capture newer than its last SessionEnd generation, keeps every observation, however old;completed,failedandsupersededjobs do not block after the session is fully ended. (#1324) - Fixed SessionEnd loading every observation of the session into memory, up to
three times, on the hook path. One production claude-code session held
948,136 observations, so ending it deserialized close to a million rows
(bodies included) per read. The lifecycle-only decision is now one
EXISTSquery, and the summary page and the auto-handoff are rendered from bounded reads taken in one read transaction: SQL aggregates for the counts and the "last of a kind" markers, the 250 first and 250 last rows the raw list prints, and early-exit scans for the first or last prompt that passes the usual filters; subagent-preamble prompt bodies are read one at a time and only until one names the page. The page, the handoff and the lifecycle-only decision are unchanged byte for byte (an equivalence test holds them to the original row-list code on sessions of every shape). What still grows with the session is what the page itself lists: the title of every user prompt. The PreCompact and PostCompaction checkpoint page uses the same bounded reads. The writer's lifecycle-only re-check and the reader now build their query from one shared predicate. (#1323) - Fixed
ai-memory run --yolofailing with401 Unauthorizedafter it re-ran itself under ai-jail on an install whose bearer token lives in[auth].bearer_token_file. ai-jail's private home hides the data dir, so the jailed run had no token. The outer run now passes the token it resolved, and aserver_urlset inconfig.tomlor derived frombind, into the jail through the environment, never the command line. (#1284) - Fixed every structured LLM call (consolidation, lint, auto-improve and the
rest) failing with a 400 on Claude Opus 5.5, Sonnet 5.5, Fable 5.1 and
Mythos 5.1. Those models reject a forced
tool_choice, which the Anthropic provider sent to make the model call itsresulttool. They now get the defaultauto, with a tool description that asks for the call, and a reply that carries the JSON as text is read as the result. Other Claude models keep the forced call. (#1328) - Fixed
AI_MEMORY_LLM_REASONING_EFFORT=nonefailing every Anthropic request to Claude Opus 5.5 and Sonnet 5.5 with a 400. Both rejectthinking: {"type": "disabled"}. Opus 5.5 now gets nothinkingfield, as Fable and Mythos already did, and Sonnet 5.5 getsthinking: {"type": "between_tools"}, its way to turn thinking off. (#1326) - Fixed the Anthropic provider rejecting any response that carried a
thinkingorredacted_thinkingblock as an unknown variant. Claude Opus 5, Sonnet 5, Haiku 5.5 and the later Opus, Sonnet, Fable and Mythos models think when a request leavesthinkingout, and Claude models from 4.6 on are sent adaptive thinking onceAI_MEMORY_LLM_REASONING_EFFORTis set, somemory_exploreandai-memory llm-testfailed as soon as the model thought. The provider now skips the blocks it does not read. (#1325)
3.0.1 - 2026-10-15
- Fixed the WinSW v2.12.0 service-account example using the v3
<username>element. WinSW v2.12.0 expects separate<domain>and<user>elements; it ignored<username>and installed the service asLocalSystem, which cannot safely own a wiki under another user's profile. The Windows guide now uses the version-correct XML, grants service logon, explains reinstalling an existing service, and verifies its recordedStartName. (#1321) - Fixed
/web/healthwarning that hook events wait in the local spool during every active session. Hooks spool each event first and deliver it at the turn, compaction or session boundary, so a few fresh events in the spool are normal, andai-memory doctoralready reports them as information. The page now warns only when an event has failed a delivery attempt, when the oldest event has waited 15 minutes or more, or when its age is unknown; a full spool is still an error. (#1320) - Fixed a hook claiming a handoff from the wrong project when an event named
no directory. A
SessionStart(or Kimi Code'sUserPromptSubmit, or Grok's firstPostToolUse) whose payload carried nocwdsentGET /handoffwith no scope, so the server resolved its default project or the caller's last active one, and the hook consumed that project's handoff. The native hook, the shell and PowerShell hook bundles, and the generated OpenCode, OpenCode 2, OMP and Pi integrations now fetch no handoff without a directory, for every agent; the native hook says so on stderr at session start. The handoff stays open formemory_handoff_accept. Devin still falls back toDEVIN_PROJECT_DIR, then the hook's own directory. (#1312) - Fixed
ai-memory setupreporting "ai-memory is set up" after it installed Codex hooks that Codex had not trusted yet. Codex skips an untrusted hook, so Codex sessions went uncaptured. When Codex was wired,setupnow asks Codex for the hooks' trust state, the waydoctordoes, and when any are untrusted or modified it names them and says to open Codex, run/hooks, and trust them. It still exits 0. (#1313) - Fixed
install-instructionstreating a line that is exactly@AGENTS.mdas proof that Claude Code loads theAGENTS.mdbeing written. Claude Code resolves@pathfrom the file that contains the import, including in the middle of a sentence, expands~/from the home directory, and ignores imports inside code spans and fenced blocks.@AGENTS.mdin.claude/CLAUDE.mdloads.claude/AGENTS.md, a fenced@AGENTS.mdloads nothing, and@AGENTS.mdin a parent directory loads that directory's file. The note now compares the resolved path with the file being written and names the import that reaches it. Recursive detection reads at most 256 KiB per file and 1 MiB per import graph, and skips oversized or non-UTF-8 files. (#1298) - Fixed Antigravity CLI posting a session start and claiming a handoff on every
user turn:
invocationNumrestarts at 0 each turn, so only the first turn of a conversation (keyed byconversationId) now maps to SessionStart, in the native, shell and PowerShell hooks. Payloads without aconversationIdkeep the previous per-turn behavior. A conversation resumed in a new process does not start a new session, so a handoff left meanwhile is read withmemory_handoff_listrather than injected. (#1306) - Fixed
llm-test --provider anthropic-oauthreporting Anthropic's refusal of aclaude setup-tokentoken as a plain rate limit: that 429 now carries a hint that it is not a real rate limit and names the alternatives, whichdocs/llm-providers.mdalready describes. (#1170) - Fixed the curator report treating a per-user focus slot
(
_slots/<operator>/current-focus.md) as an ordinary slot. It only recognised the shared_slots/current-focus.md, so with per-user slots the focus slot got the 30-day stale threshold instead of the 7-day one and was reported three weeks late. (#1314) - Fixed multi-page consolidation losing every page of a batch when the
model gave one page a path that cannot be repaired: a leading slash, a
..or.segment, or an empty segment. Path validation failed the whole session's consolidation, so the valid pages in the same answer were never written. That page is now skipped with a warning and the others are written, as already happened for paths that fail the portability check. (#1315) - Fixed auto-improve patches losing track of a page's sections after a code
block that contains a different fence. A line of three backticks inside a
~~~block (or a shorter run inside a longer fence) ended the block early, so the headings after it were missed, and areplace_sectionon the section before them replaced those sections too. Profile harvesting had the same flaw: the code after such a line was read as prose and could be harvested as a preference. A fence now closes only on a run of the same character that is at least as long, with no more than the three leading ASCII spaces CommonMark permits. Four-space, tab and Unicode-whitespace pseudo-fences stay code in profile harvesting, section patches and wiki link extraction or rewriting. (#1316) - Fixed the thin-client commands ignoring a custom
bind, which could write into the wrong store.server_urldefaulted tohttp://127.0.0.1:49374and was never derived frombind, soai-memory --data-dir X status(andwrite-page,search,purge-project, ...) called the default port whateverbindsaid. With nothing listening there the command failed; with another server listening there it operated on that server's data, and one reporter wrote test pages into their real store this way. When neitherconfig.tomlnorAI_MEMORY_SERVER_URLsetsserver_url, it is now derived frombind: an unspecified address maps to loopback (0.0.0.0to127.0.0.1,[::]to[::1]) and any other host is used as given, hostname included. An explicit value in the file or the environment still wins, even when it names the default URL; abindthat is not ahost:port, or one on port 0, leaves the default URL alone; and the defaultbindresolves to the same URL as before, so an install that never changed either key is unaffected.install-hooksandinstall-mcpstill prefer an existing MCP entry's URL, and otherwise write the derived URL instead of the default port. The loader now retains that explicit provenance when the file deliberately names the default URL. (#1269) - Fixed a burst of sessions in a directory with no backfill marker launching
one
backfill --autoper session. The SessionStart hook now takes an atomic in-progress marker before it spawns the backfill, so concurrent sessions in that directory start one; the marker is dropped when the worker ends and a leftover one stops blocking after 30 minutes. The backfill's emptiness check against the server also gives up after 30 seconds instead of waiting forever on a saturated server. Keying the trigger on the project rather than the directory is not part of this change. Each marker now has an unpredictable ownership nonce under an advisory lock, so a delayed worker cannot refresh or remove a newer stale takeover. (#1287) - Fixed
/web/pendingoffering Approve and Reject buttons that always failed with401: unknown erroron a server with a bearer token and no password sign-in. The browser reaches/webthere with the root token over HTTP Basic, which only admits reads. The page now says so, points to password sign-in, and shows each proposal'sai-memory pending-writes approve/rejectcommand instead, separately quoted for POSIX shells and PowerShell; stored workspace or project names cannot inject shell syntax. A decision that fails for another reason now shows the server's message even when it is plain text. (#1288) - Fixed the managed routing snippet's claim that Claude Code never reads
AGENTS.md. Claude Code 2.1.277 and later reads it when noCLAUDE.md,.claude/CLAUDE.md, orCLAUDE.local.mdis present, and reads that file instead when one exists. The@AGENTS.mdimport is how a canonicalAGENTS.mdreaches sessions that read onlyCLAUDE.md. (#1276) - Fixed bundled hook discovery for a binary installed in a standard Unix
prefix such as Nix:
install-hooksandsetup-agentnow probe<prefix>/share/ai-memory/hooks/from<prefix>/bin/ai-memory, andsetup-agentchecks it before the fixed/usr/local/shareand/usr/sharelocations. (#1275) - Fixed
ai-memory install-hooksandsetup-agentfailing on a second run when the staged hook scripts were read-only copies from a read-only package store (the Nix store ships them0444). Staging now replaces a read-only copy instead of erroring. (#1275) - Fixed generated hook commands baking an absolute
/nix/store/...binary path that Nix garbage collection later deletes. A binary running from the Nix store now renders the bareai-memorycommand, resolved throughPATHwhen the hook runs. (#1275) - Fixed the NixOS service lacking
gitonPATH: the module now addspkgs.gitto the unit'spath. The package binary stays unwrapped so the live-process guard can still see a running server. (#1275) - Fixed Windows capture dropping the body of file and shell tool events
the client had kept. With
[capture] ignore_pathsactive, server re-inspection compiled a POSIX never-match sentinel and treated the policy as invalid, so a kept Read or Bash became metadata-only. The sentinel now follows the event cwd's path flavor. (#1250) - Fixed
GET /admin/sessions/by-agent, and soai-memory doctorandai-memory backfill, taking minutes on a large project (178 s for 780 sessions over 185k observations). Without planner statistics SQLite answered each session's capture probe from the project-wide index and rescanned the whole project once per session; the probe now always uses the session index (0.3 s on the same store). The session list and session detail reads behind/web,/api/v1andmemory_read_session_observationsran the same per-session probes and are pinned the same way. (#1257) - Fixed pages written by an auto-improvement approval, a batch write or a
checkpoint restore getting no embedding. An approval (through
pending-writes approve, thememory_auto_improvetool,POST /admin/auto-improveor the scheduler), the batch writes behind consolidation, the forget sweep, dream and bootstrap, andai-memory restore-pagewrote and indexed the page but never embedded it, so it stayed out of the vector stream of hybrid search until an embedding backfill ran, which by default is only at the next server start. These paths now embed each written page the same waymemory_write_pagedoes. A provider or embedding-store failure after the durable page commit is recorded for backfill but no longer reports the whole mutation as failed or prevents later post-commit webhook dispatch. (#1258) - Fixed
ai-memory runreportingserver unavailablefor a heartbeat the server answered and rejected. A heartbeat is rejected once its managed run stops being active, which on a workstation usually means a laterrunprepared a workstream and expired every active run whose lease had already passed. The launcher printed the outage notice for a failure of any kind and sent the real reason to adebugline nobody sees at the default level, so a server that answered409 managed run lease is not activein about a millisecond read as an outage. The notice now carries the server's own reason for a rejection, and keeps the outage wording only for a server the launcher could not reach. A peer that accepts the connection but closes before an HTTP response is also classified as unreachable, and the tests no longer race to reuse a released ephemeral port. (#1277) - Fixed
ai-memory run --jaillosing the configured server route inside ai-jail's private home. The outer run forwards an explicit or bind-derivedserver_urlandbase_path. It does not promote a root bearer read fromconfig.tomlorbearer_token_fileinto the harness environment: that case now stops with an actionable error and requires a least-privilegeAI_MEMORY_AUTH_TOKEN, while a token the operator already supplied in the environment continues across the existing child-process boundary. (#1284) - Updated rustls to 0.23.46 for RUSTSEC-2026-0346, -0347, -0348 and -0349, in the server and in the importer, relay and wikisync companions, and the importer's anyhow to 1.0.104 for RUSTSEC-2026-0190. (#1286)
- Fixed the hook spool delivering nothing after
ai-memory auth enablewhile it still held entries captured before the switch. Those entries carry no credential and sort first, so their batch was refused with401and the drain stopped there, never posting the entries behind them that carry the accepted token. A refused credential now only holds back the entries that carry it: they stay queued, the first of them charged once per pass, and the drain carries on with the rest. Entries captured with no credential are still dropped once their attempt budget runs out; they are never resent with the current token, which would attribute them to the operator. (#1283) - Fixed
install-instructionswriting the block intoAGENTS.mdalone, with no warning, when Claude Code would never read it. Claude Code readsAGENTS.mdonly when noCLAUDE.md,.claude/CLAUDE.mdorCLAUDE.local.mdexists in the directory or above it, so a developer'sCLAUDE.local.mdhid the block. The command now names the file Claude Code reads instead and how to import@AGENTS.mdfrom it. (#1297) - Fixed
move-sessionleaving prompt profile candidates and ingest keys in the source project. When a session is moved or re-homed into another project, its promptprofile_candidates(source_ref = 'session:<id>') andingest_keyswere not re-stamped into the target project, which distorted cross-project profile convergence and caused a subsequentpurge-projectof the source project to destroy them via cascade. They now re-stamp in the same transaction. (#1265) - Fixed the SQLite
-walfile staying as large as the database after aVACUUM.compact,reclaim-ledger-versions --compactand the session, project and workspace purges with compaction now runPRAGMA wal_checkpoint(TRUNCATE)after the rewrite, and the store's writer connection setsjournal_size_limitto 64 MiB. A checkpoint that a reader holds back is logged atwarnand the operation still succeeds. A 29.5 GiB database that shrank to 6.3 GiB kept a 17.8 GB WAL, and the next restart spent 9 minutes replaying it.servenow also drops its read connections and checkpoints the WAL on SIGINT, SIGTERM, Ctrl-C or the end of stdio input, waiting at most 5 seconds; a read that arrives after that point is refused. A hard kill before the checkpoint still leaves the WAL to replay. (#1317) - Fixed the hook event ledgers (
log.md,log-YYYY-MM.md) being committed to the wiki's git repository. Every checkpoint after a hook append wrote a new whole-file blob of the ledger, and nothing packed them: one install'swiki/.gitreached 3.49 GB, 229 MB per version of a single ledger. Checkpoints now leave ledgers out, recognized by name and by content, so an ordinary page named like a ledger stays versioned. A ledger an earlier version committed is dropped from the git index, never from disk, by the next checkpoint. Existing history keeps its old blobs;docs/lifecycle-ops.mdexplains how an operator can rewrite it. (#1318) - Fixed source-built Docker images failing after the plugin renderer began
embedding the project license. The builder now copies
LICENSEbefore compiling, and the packaging test guards that compile-time input.
3.0.0 - 2026-10-13
- Added backlinks, outgoing links and an outline to the web page view. A page
in
/webnow has a side column (below the text on narrow screens) with "On this page", the h2 and h3 headings linked to their anchors (shown from two headings up); "Cited by", the pages that link to it, with an empty state; and "Cites", the pages it links to. Each group lists ten pages and then "and N more", and a page in another project carries itsworkspace/project. A link from a project the viewer cannot read is left out, as in the/api/v1page response. (#1246) - Added ai-memory as a marketplace plugin for Claude Code and Codex. The
repository is now a plugin marketplace:
claude plugin marketplace add akitaonrails/ai-memorythenclaude plugin install ai-memory@ai-memory, orcodex plugin marketplace add akitaonrails/ai-memorythencodex plugin add ai-memory@ai-memory(Codex runs the hooks only after you trust them in/hooks; trust survives plugin upgrades). Each plugin carries the lifecycle hooks, the MCP tools throughai-memory mcp-bridge, the routing skills and astatusskill that runsai-memory doctor. The hooks call one short POSIX launcher that hands every event to the installed binary's nativeai-memory hook, soignore_paths, allowlist mode, marker scope and server profiles apply exactly as forinstall-hooks; without the binary the plugin captures nothing, says so once at session start and never blocks the agent. The binary is still installed separately. Catalogs pin each plugin to the release tag, andplugins/is generated by the hiddenai-memory render-plugins, with a test that fails when the committed files drift from it. When Claude settings-file hooks and the plugin are both installed, the settings hooks win and the plugin skips the events they deliver, but only a settings hook with a catch-all matcher (empty,*or absent) counts; sessions with no project folder are not captured through the plugin. Droid running the Claude Code plugin gets the same treatment: the plugin skips folderless sessions and the eventsinstall-hooks --agent droidalready delivers. The Claude Code hooks start the launcher withsh, a relativeai_memory_binoption is ignored, and the plugin trees and every*.share pinned to LF line endings. With no server URL set, the launcher omits--server-url; a binary older than 3.0 rejects that, so the launcher retries it once againsthttp://127.0.0.1:49374and prints an upgrade hint. The plugins need ai-memory 3.0 or later. On Windows, Codex callsai-memory hookdirectly without the launcher, so folderless sessions are captured there and a missing binary goes unreported;docs/install.mdrecords this.setupandai-memory runno longer write settings hooks, MCP entries or skills for an agent whose plugin is enabled;doctorreports each plugin, version skew, double wiring and Codex plugin hooks Codex skips;upgradealso updates the Claude Code plugin;uninstallremoves the plugins.ai-memory hook --server-urlandai-memory mcp-bridge --server-urlnow treat an empty value as unset and use the configured server, andhook --server-urlmay be omitted. Each release attachesai-memory-claude-plugin.zipfor the Claude desktop app's plugin upload. Seedocs/install.md#marketplace-plugins. (#1166) - Added a recall trace to web search. A project page has its own search box,
and a search with
workspaceandproject(/web/search?q=…&workspace=…&project=…) now ranks with the same hybrid ranker asmemory_queryfor that project: FTS5, entity, vector, graph and observation streams, the authority adjustment, and the reranker when one is configured. The server now hands its configured embedder and reranker to the web UI, and the reranker's four-call concurrency limit is shared withmemory_query. With "Explain ranking" (explain=1) each hit shows its rank, raw score and RRF contribution in every stream, the fused score, the authority multiplier, and the intent boost and rerank score when present; the page also lists the streams that ran, the embedder and reranker in use, and each stream's time in milliseconds. A web search without a scope is still the cross-project FTS5 search, asmemory_query(global=true)is. Project search follows the per-project read rule: a viewer without a grant on a restricted project gets a 403 and no trace, and an unknown project is a 404. - Added a root-only server health page at
/web/health, linked from the web UI header. It shows the hook counters since the server started (accepted, shed with 429 by saturation or rate limit, stored, failed, dropped), the writer queue depth, the hook spool and recovery journal in the server's data directory, the consolidation queue (last completion, pending, running, completed and failed jobs), the configured embedder, both providers' status and the stored vector identities. Failing checks are listed with the command that fixes them, the same linesai-memory statusprints:ai-memory recoverfor a backed-up spool or journal,ai-memory compact --confirmfor reclaimable space,ai-memory embedfor pages without vectors,ai-memory embed --forcefor vectors made under another model, and the LLM provider's retry command. The page is read-only and is gated like/web/pending: on a multi-user server anonymous requests get a 401 and database users a 403. It never shows provider error text, page content or project names. - Added a two-command native install for Linux and macOS:
curl -fsSL https://github.com/akitaonrails/ai-memory/releases/latest/download/install.sh | sh, thenai-memory setup.install.shis a new release asset with its owninstall.sh.sha256. It downloads the release tarball for the machine, checks it against the published.sha256file and refuses to install when that file is missing, names another asset or does not match, or when the archive holds an absolute or..path or a symlink or hard-link member. Itswgetfallback for the default GitHub URL runs only with--https-onlyand refuses awgetthat lacks the flag. It unpacks under~/.local/lib/ai-memorywithout restoring file ownership, links~/.local/bin/ai-memory, needs no root, leaves an existing non-linkai-memory(such as the Docker wrapper) alone, and runs nothing when the download is cut short. The newai-memory setup [--yes] [--agent <name>]... [--no-service]runsinit(a fresh data dir gets the root bearer token), installs and starts a per-user service rendered from the packaged systemd user unit or launchd LaunchAgent with this install's binary, data dir and config, wires MCP, hooks and global Agent Skills for each named or detected agent, and ends with a read-only check of/healthz,memory_statusanddoctor. The unit names a stable path to the binary (the invoked path, anai-memoryonPATH, or Homebrew'sopt/binlink) when the resolved binary sits in a versioned Homebrew Cellar or/nix/storepath, so the service survives an upgrade or garbage collection. It shows the plan and asks first; without a terminal it changes nothing unless given--yes. A second run changes nothing. Windows has no service step.ai-memory uninstall --applystops and removes that service, but only a unitsetupwrote and nobody edited since; any other unit at that path is left alone and reported. With--purge-data, uninstall refuses a live ai-memory process before it stops the service.INSTALL_FOR_AGENTS.mdtells an AI agent how to install ai-memory for its user, andscripts/onboarding-acceptance.shmeasures the path from a clean home to the first returned memory. - Added page history and session replay to the read-only web UI. Each page
links to its history (
/web/w/<ws>/<proj>/h/<path>): every stored version, newest first, superseded ones included, so a version a concurrent write replaced stays visible, with timestamps and the author of attributed writes, plus a server-side unified diff between any two versions (by default the latest against the version it replaced). Expired versions are hidden, and a page whose TTL has passed shows no history. The same data is available as JSON atGET /api/v1/workspaces/{ws}/projects/{proj}/history/{*path}. The version list is capped at 200 per request, and the diff refuses inputs over 512 KiB, 10,000 lines or 1,000 changed lines and stops at 2,000 output lines. The project page lists recent sessions, and each opens a timeline (/web/w/<ws>/<proj>/s/<session-id>) that pages through its observations in capture order with first/prev/next/last links and a jump field; with JavaScript, a slider andj/kkeys step one observation at a time. It uses the same readers, owner filter and caps as the/api/v1session routes, and observations render as escaped text. Both views follow the per-project read rule: a non-grantee of a restricted project gets a 403, and a version id or session id from another project or workspace reads as not found. Theai-memory-webcrate now has aclippy.tomlthat forbids theWikiwrite and destructive methods, so a write path in the web UI or/api/v1failscargo clippy -D warnings. - Added an observation stream to
memory_queryranking, on by default because it raised hit@5 on the retrieval eval for both zero-LLM and local-embedding search while staying inside its latency and context-token budgets. To keep the previous ranking, set[retrieval] observation_stream = false(orAI_MEMORY_RETRIEVAL__OBSERVATION_STREAM=false); ranking is then identical to before. A zero-LLM session page keeps only a short excerpt of each turn, so the detail a question asks about often lives only in the session's observations. With the flag on, project andscopessearches also run full-text search over observations in the same project, and each matching session votes once, through its best-matching observation, for its own latestsessions/<id>.mdpage as one more RRF stream. A page the stream voted for takes its lexical evidence from the stream alone: page FTS over its title-only body still reports a rank but adds nothing, so a session that only shares topic words in its titles cannot outvote the session holding the answer. When page search did not match the page itself, the matching excerpt becomes the hit's snippet, andexplain=truelistsobservationinstreams_activewhenever the stream runs and reports the matched observation ids. The excerpt and the ids are shown only when the caller may read that session's timeline; another operator's observations still vote for the shared session page. The stream finds the project's observation range with an index seek. Hits are still pages and nothing is written; sessions without a page keep theraw_hitsfallback, andglobal=truesearch is unchanged. - Added a root-only
GET /admin/metricsendpoint in Prometheus text format. It exposes the hook admission and delivery counters, MCP tool calls split into reads and writes, and fixed-bucket latency histograms forPOST /hookandmemory_query. Every label comes from a fixed set: no query text, page body, prompt, client or project name, scope or token appears in the output. On a multi-user server only root can read it, like every/admin/*route. - Added a
timingsobject tomemory_queryresponses withexplain=true: each retrieval stream's wall time in milliseconds (fts_ms,entity_ms,vector_ms,graph_ms,observation_ms,rerank_ms), summed overscopes. Streams that did not run are left out, and a query withoutexplainreads no clock for them. - Added a Grok Build CLI first-turn handoff rule.
install-hooks --agent grok --applyandinstall-skills --scope global --agent grokwrite$GROK_HOME/rules/ai-memory-handoff.md(into the Grok home the hook file was written to), which has the model callmemory_handoff_listas its first tool, throughuse_toolwith the qualified keyai-memory__memory_handoff_list, before reading any file. Grok discards SessionStart stdout, so that call is what lets the first PostToolUse claim the baton through GET/handoff. The rule correctsworkspaceandprojectfrom the marker or git remote when the first list is empty or answers with a scope error, and treats the handoff PostToolUse claimed as authoritative. A same-name file without the managed marker is kept unless--force;uninstall --only hooksor--only skillsremoves the managed rule. (#1228) - Added the end reason to session-end observations. When a harness's
SessionEndpayload carries areason(Claude Code sendsclear,logout,prompt_input_exitorother), its first line is stored, sanitized, as the observation body, so consolidation can tell a normal quit from anything else, and the zero-LLM session page closes its summary withended (<reason>). Payloads without a reason are stored as before. (#1225) - Added pending cross-project mail to the web UI. Project cards carry an amber
N inboxbadge when messages are waiting for an agent in that project, the home page summarises the total and links the projects holding mail, and the project page gains a read-only Mailbox section listing up to five of them (subject, sender, a snippet, age; "5 of N" when more are waiting), with an empty state when there are none. A sender the viewer may not read is shown as "a project you cannot read", and the page never pops or cancels a message. (#1202) - Added
install-mcp --client prime-agent, which registers ai-memory with Prime Agent (PrimeIntellect-ai/prime-agent), merging an HTTP entry into themcpServersmap of$PRIME_AGENT_CODING_AGENT_DIR/settings.json(default~/.prime/agent/settings.json) and preserving other settings and servers. With a token the entry names"bearerTokenEnvVar": "AI_MEMORY_AUTH_TOKEN"and the token is not written; Prime Agent 0.10 counts a user HTTP server as connected, and lists it to the model, only with that variable set (or an OAuth grant), so export it where you launchprime-agent. Without a token the server is still callable throughmcp.call_tool("ai-memory", ...)but is not listed. MCP-only: Prime Agent 0.10 has no lifecycle hooks. (#1163) - Added
expected_page_id(write only if that is still the page's latest version) andcreate_only(write only if the page does not exist) tomemory_write_page, andexpected_page_idtomemory_delete_page. A precondition that no longer holds fails withreason: "precondition_failed"and the current version, and changes nothing.memory_read_pagereturns the version aspage_idand/api/v1pages asid. This is the compare-and-write the team-wiki sync needed (#986). - The
ai-memory-wikisynccompanion gained CI and post-merge integration (#986, slice 5):sync --checkwrites nothing and exits 0 in sync, 3 on drift and 4 on conflicts or refusals (a clone without sync state compares the repository with the server directly), andinstall-hook/uninstall-hookmanage a marked gitpost-mergeblock that runssyncafter each merge: a dry-run report unless--on-merge apply, never with--prefer, and never with a stored token. The cookbook has a GitHub Actions recipe. (#986) - Added delete propagation to the
ai-memory-wikisynccompanion withsync --propagate-deletes(#986, slice 4): a file deleted in the repository deletes its server page throughmemory_delete_page, and a page deleted on the server deletes its file, when the other side is unchanged since the last sync. A delete against an edit is a conflict that--preferresolves, more than--max-deletes(default 10) refuses the run, and a pinned page is deleted only with--prefer repo. Every sync write and delete now carries the page version it was planned against, so a page changed during the run is reported and left alone while the rest apply, andsync --applyrefuses a server older than 3.0. Without the flag deletes are still only reported. (#986) - Changed
ai-memory doctorto report three silent capture failures: an unreachable server (reported instead of aborting the check), a hook spool that is full and evicting undelivered events, and installed ai-memory Codex hooks that Codex skips because they are untrusted or changed since they were trusted (asked of Codex itself throughcodex app-server).ai-memory statusno longer says queued events will be delivered once the spool has started evicting them. - Added two-way sync to the
ai-memory-wikisynccompanion (#986, slice 3):syncimports repository edits and new pages through the publicmemory_write_pageMCP tool and exports server edits, comparing each page with the last sync. A page changed on both sides is a conflict until--prefer repoor--prefer server; deletes are reported, not synced. It is a dry-run without--apply, refuses files without frontmatter, refuses to overwrite server pages whose metadata a write would clear, and re-reads each page right before writing it. An export, or an import's rewrite of the repository file, is skipped and reported when that file changed during the run, and the run exits non-zero. (#1164) - Changed
finalize-session --agent antigravity-clito replay the session's typed prompts fromagy'shistory.jsonlbefore its synthetic session-end, so live Antigravity sessions, whose hooks carry no prompt event, get a summary page titled by the first prompt, a handoff with "Started/Last", and prompts thatmemory_queryandmemory_recentcan find. Only lines whose conversation and workspace match the session are read; each prompt carries the keybackfillmints for it, so a repeated--reopenstores it once and a prompt typed after the first finalize arrives with the next one. A checkout the capture allowlist does not admit and--all-ownersruns replay nothing, and a missinghistory.jsonlonly warns. (#1160) - Added
copilot(aliascopilot-cli) toai-memory run, which manages GitHub Copilot CLI as a workstream harness: a fresh session gets a generated--session-id, a returning one is resumed with--resume=<id>, user selectors (--resume,--continue,--session-id,--connect) win, utility subcommands pass through, and--yolomaps to Copilot's native--yolo. Visible messages, tool calls and results, and compaction summaries are imported read-only from$COPILOT_HOME/session-state/<uuid>/events.jsonl(system prompts, hidden reasoning, hook output, and telemetry are excluded), and the existing SessionStart hook delivers the workstream context. The first managed launch auto-wires Copilot's hooks and MCP, anddoctornow counts its local sessions. Verified against Copilot CLI 1.0.92. (#1040) - Made GitHub Copilot CLI a first-class routing target:
install-skills --agent copilot-cli(andinstall-instructions --skills-agent copilot-cli) writes the managed Agent Skills to the repository's.github/skillsor, globally, to$COPILOT_HOME/skills(default~/.copilot/skills);memory_install_self_routingreturnscopilot_clifilename and skill-root hints; the routing snippet, the routing-install skill, and the MCP instructions name Copilot CLI withAGENTS.md; anduninstallsweeps both Copilot roots, removing only marker-bearing ai-memory skills. (#1040) - Added
ai-memory recoverand a bounded local recovery journal for server outages. Offline managed launches now record only uniquely correlated native session locators; recovery quarantines overlapping spool entries before exact/hook/batchtranscript replay, journals harnesses without transcript export as spool-only without calling their exporter, retains bounded recovery dedup keys with their sessions, fails closed on terminal drops, and lets only the original owner finish an expired unsuperseded managed run through exact idempotent replay. Backfill and recovery replay now use reservedrecovery_*idempotency keys, so re-runningbackfill --forceover sessions imported by ai-memory ≤2.6.0 (or plain-backfilling a recovery-replayed session) can duplicate those older observations. Every spool drain path deletes an event the server answereddropped_*orresumedinstead of re-sending it each pass, and a hook that finds the spool's capacity lock busy waits up to 50 ms and then spools the event anyway. (#1125) - Added
AI_MEMORY_LLM_COMPAT_DISABLE_THINKINGfor theopenai-compatprovider (opt-in, off by default, ignored by every other provider): when set, every chat request carrieschat_template_kwargs: {"enable_thinking": false}, so thinking-capable local engines (vLLM / SGLang serving Qwen3-class models) spend the output budget on the structured payload instead of a reasoning pass. (#1130) - Added the
cursorLLM provider. Consolidation calls the logged-in Cursor Agent CLI (agent --print --mode ask) so a Cursor subscription can drive summaries without an API key. The default model iscursor-grok-4.6-high("Grok 4.6").AI_MEMORY_CURSOR_AGENToverrides the binary. The provider never passes--yoloor--force, because the prompt is captured session text, and runsagentwith an allowlisted environment, so it never inheritsAI_MEMORY_AUTH_TOKENor provider keys. (#1127) - Added
applies_toandenforced_byarguments tomemory_write_page, and--applies-to/--enforced-bytoai-memory write-page, for profile entries. Both fields were documented as profile-entry frontmatter, but no tool could set them, so scoping an entry to a stack or keeping it out of the digest meant editing the wiki file by hand, and the next tool rewrite of the entry dropped the value. A tag the profile cannot detect in any project (typscript) is refused instead of silently hiding the entry, and both arguments are refused on any page the digest does not read: one outsideprofile/, or aprofile/path in an ordinary project. (#1178) - Added the server version to the footer of every signed-in
/webpage (the public login and change-password pages leave it out), on the right side opposite "ai-memory · read-only", so you can tell which build a browser tab is talking to without runningai-memory status. (#1171) - Added
install-mcp --client dshfor the DeepSeek Harness (aliasesdeepseek-harnessanddeepseek_harness). DSH keeps its MCP client in a Cordis profile patch rather than a fixed config file, so the command prints a ready-to-merge- insert:fragment for@deepseek-ai/dsh-mcp-clientand refuses--apply. (#1193) - Added agentmemory import to the
ai-memory-importercompanion.agentmemory-vaultreads its Obsidian vault export;agentmemory-exportreads itsExportDataJSON file, from disk only, and needs a mapping file from agentmemory's project names (or exact session working directories) to ai-memory workspaces and projects.--applyrefuses to run while any record is unmapped. Pages land underimported/agentmemory/(--dest-prefix), never undersessions/or another reserved family, and links to pages that were not imported become plain text. Every write goes throughmemory_write_pagewithcreate_onlyorexpected_page_id, and the importer refuses servers older than 3.0. A memory's versions are written oldest first so ai-memory keeps the supersession chain, andforgetAfterbecomesexpires_at. Each page carries a stable UUIDv5 ingest key, so an interrupted import resumes without duplicates and a rerun writes nothing. Text is redacted before anything is planned or sent. Observations are dropped unless--replay-observationsreplays them asexternal-importsessions through/hook/batch, and records with no ai-memory counterpart are counted in the manifest. The cookbook has a "Coming from agentmemory" section. - Added an agentmemory residue check to
ai-memory doctor. It reports agentmemory configuration left behind after a migration: an MCP server entry in any harness configinstall-mcpknows (or in a project's.mcp.json,.cursor/mcp.json,.gemini/settings.jsonor.vscode/mcp.json), and agentmemory guideline blocks in rules files such asAGENTS.md,GEMINI.md,.cursor/rules/and.kiro/steering/. It names each file and the key or lines, prints the fix, and warns when a block namesmemory_consolidate, which ai-memory also has with different arguments. The check only reads; it never edits or deletes another tool's files.doctor --jsoncarries the findings asagentmemory_residue. - Added
ai-memory auth enable, which turns on bearer auth for an install created without it. It reuses or creates an owner-only token at<data_dir>/auth-tokens/default, records it inconfig.toml, gives the installed hooks the token when they hold no other credential, and re-registers every MCP client whoseai-memoryentry points at this server the wayinstall-mcpnow wires a token-file install. It lists each client it changed, each entry for another server it left alone, and anything that needs a manual change. Running it again changes nothing, and the token is never printed or written into a harness config. Restart the server afterwards. - Added
ai-memory mcp-bridge --no-session-id, a plain stdio-to-HTTP MCP bridge for any client that runs local commands. It authenticates with the token from config (AI_MEMORY_AUTH_TOKEN,[auth] bearer_token, or the file[auth] bearer_token_filenames), forwards no session identity (the client passesworkspaceandprojectlike any static MCP client, and an inheritedCLAUDE_CODE_SESSION_IDis ignored), and exits at once with401 Unauthorizedwhen the server refuses it. Without the flag the bridge keeps Claude Code's session forwarding and still refuses to start withoutCLAUDE_CODE_SESSION_ID. - Added the
[auth] bearer_token_fileconfig key (envAI_MEMORY_AUTH__BEARER_TOKEN_FILE): a file holding the root bearer token, read once at startup. A relative path resolves against the data dir.AI_MEMORY_AUTH_TOKENstill takes precedence; setting the key together with[auth] bearer_tokenis a startup error, and a server whose configured file is missing or empty refuses to start. A token read from the file is never written into a harness config:install-mcpregisters Claude Code through the session-awareai-memory mcp-bridge(Muse Code always gets the bridge, asmcp-bridge --agent muse), gives Codex and Prime Agent a reference to$AI_MEMORY_AUTH_TOKEN, and writes every other client a stdio entry forai-memory mcp-bridge --no-session-idin that client's own config format (OpenCode, OpenCode 2, Cursor, Claude Desktop, Gemini CLI, OpenClaw, OMP, Antigravity CLI, Zero, Devin, Grok, Kimi Code, Kiro CLI, Command Code, Copilot CLI, Swival, VS Code, Zed; DeepSeek Harness's printed patch too). Only ZCode, whose config keeps HTTP entries only, is refused with the manual option.uninstallrecognises these entries.install-hooks --applystores the token in the hook credential files only. - Changed
ai-memory statusandai-memory doctorto show the server's auth posture, and for an unauthenticated loopback server printauth: none (loopback)with the fix,ai-memory auth enable. - Added a server-side drop list for tool events:
[ingest] drop_tool_use_id_prefixesanddrop_tool_names(both empty by default, at most 64 entries of 128 characters). A PreToolUse or PostToolUse event whose tool-call id starts with a listed prefix, or whose tool name matches exactly, is answered with 202 and counted as a policy drop in/hookand/hook/batch, before it is sanitized, rate-limited or stored. The id and name are read from each harness's own fields (Claude Code'stool_use_id, OpenCode'scallID, Hermes'sextra.tool_call_id, and so on). The lists apply to every project on the server. (#1241, #1244) - Added
--project-idtopurge-project,rename-projectandmove-project(andproject_idto their admin endpoints), as an alternative to the project name. A legacy twin row whose name collides with another project'slegacy_name(the empty leftover an older migration leaves behind) was refused as ambiguous by name, so it could not be purged or renamed. The id is looked up inside the named workspace: sending both or neither is a 400, a malformed UUID is a 400, and an id that names no project in that workspace is a 404.list-projectsnow prints each project's id so an operator can copy it, andGET /api/v1/projectsrows carryworkspace_idandproject_id(hidden with the row for a restricted project). (#1208, #1226) - Added
ai-memory project init, which creates a.ai-memory.tomlmarker at the current worktree root (or the current directory outside Git) after asking forworkspaceandproject. The defaults are the scope the CLI and hooks already resolve for the checkout, so accepting them keeps the memory where it is. It writes only those two keys, needs an interactive terminal, and never overwrites an existing marker. (#1227) - Added first-class Factory Droid CLI support.
install-mcp --client droid(aliasfactory-droid) merges an HTTP entry into~/.factory/mcp.json, with the URL written literally and a bearer referenced asBearer ${AI_MEMORY_AUTH_TOKEN}: Droid expands variables in headers but never in the URL.install-hooks --agent droidmerges nativeai-memory hook --event … --agent droidcommands for seven events into~/.factory/hooks.json, keeps your own entries, changes nothing on a re-run, and refuses to create that file while~/.factory/settings.jsonholds hooks, which Droid would then stop reading. Sessions are recorded as agentdroid(migration V78). TheSessionStarthook delivers the pending handoff, and Droid's ownSessionEndcloses the session. Droid'sRead,Create,Edit,ApplyPatch,LS,Grep,GlobandExecutetools map to the file, search and shell families, so[capture] ignore_pathsapplies to them: verified against the Droid 0.240.0 tool schemas; no tool-call id in Droid payloads.setup-agent --agent droidprints the hook config anduninstallremoves both entries. (#1240) - Added first-class Meta Muse Code support.
install-hooks --agent muse(aliasesmuse-code,musecode) merges nativeai-memory hook --event … --agent musecommands for eight events (SessionStart,UserPromptSubmit,PreToolUse,PostToolUse,PostToolUseFailure,PreCompact,Stop,SessionEnd) into thehookskey of${XDG_CONFIG_HOME:-~/.config}/muse/settings.json, with an empty matcher, a 30-second timeout and the server URL and data dir baked in, since Muse runs hooks with a cleared environment. It keeps"schema_version": 1, every other key and your own hooks, writes a backup when it changes the file, and changes nothing on a re-run. Sessions are recorded as agentmuseunder Muse's own session id (migration V80). TheSessionStarthook delivers the pending handoff throughhookSpecificOutput.additionalContext, theSessionStartandUserPromptSubmithooks print nothing when there is nothing to deliver (Muse adds that stdout to the model's context), and Muse's ownSessionEndcloses the session. Muse'sread_file,write_file,edit_file,apply_patch,search,bash,monitor,web_fetchand memory tools map to the file, search and shell families, with a relativepathresolved lexically againstcwd, so[capture] ignore_pathsand the server drop list apply; Muse'stool_use_idis kept as the call id.SubagentStartandSubagentStopare not wired, because Muse fires them every turn for its built-in reminder observers.install-mcp --client musenow writes a stdioai-memory mcp-bridge --agent museentry with"mode": "optional"and the install's--data-dir, replacing the streamable-HTTP entry, so Muse is a session-aware client: the newmcp-bridge --agent museforwardsMUSE_SESSION_IDand reads no other harness's variable (--agent claude-code, the default, reads onlyCLAUDE_CODE_SESSION_ID). The entry goes undermcpServers, or under the legacymcp_serverswhen the file already uses it, never both; a file holding both maps, which makes Muse ignore every MCP server, is refused and reported bydoctor. The settings path followsXDG_CONFIG_HOME. The Claude Code plugin launcher records an event asmusewhen Muse runs it (MUSE_PLUGIN_IDwithMUSE_SESSION_ID), skipping the events the Muse settings hooks deliver, its subagent events and folderless sessions.setupdetects Muse by its config or data directory,setup-agent --agent museprints the hook config,doctorreports the Muse wiring,uninstallremoves the hooks and the entry from either map, the Docker wrapper forwardsMUSE_SESSION_ID, and the routing snippet lists Muse Code among theAGENTS.mdharnesses. Verified offline against Muse Code 1.4.7 hook payloads, and end to end with the real Muse binary in an isolated network namespace against a stand-in model provider: capture of all eight events, exclusions on relative and absolute paths, MCP calls routed by session, handoff injection and the timeout unit. Not verified: interactiveSessionStartsources andSessionEndreasons,PreCompactpayloads,apply_patchandmonitorinput shapes, macOS and Windows paths, and the plugin from the marketplace, which Muse Code 1.4.7 refuses to install because its.mcp.jsonuses${user_config.*}placeholders. - Added an optional TypeScript client SDK in
companions/ai-memory-tsfor programs that are not coding-agent harnesses: custom agents, bots, editor and app extensions, scripts and CI jobs.AiMemoryClientwraps the page, query, status, recent, handoff, message, briefing, explore, session-observation and feedback tools, the read-only/api/v1routes (with cursor paging andETagrevalidation) andGET /identity, with typed results, a typed error taxonomy and scope checks before any request.pages.updatereads a page, applies a change and writes it back guarded byexpectedPageId, keeping the page's metadata. A separateai-memory-ts/adminentry point covers checkpoints, page restore and session or project purges, which default to a dry run and need bothdryRun: falseandconfirm: trueto delete. The SDK has no runtime dependencies, needs Node 22 and a 3.0.0 server, and is built from a checkout for now; it is not published to npm and is never part of the server's tarballs, Docker image, Homebrew, AUR, RPM or Nix packages, whichscripts/check-native-packaging.shnow enforces. CI runs it against the server built from the same commit, including a schema drift test that fails when an MCP tool changes without an SDK update. Seecompanions/ai-memory-ts/README.mdanddocs/design-client-sdk.md. (#1165)
- Documented the 2.x to 3.0 upgrade in
docs/upgrading-to-3.0.md: what changes, what replaces the removed token commands, the backup, upgrade and check steps, and how to roll back by restoring the backup with the 2.x binary. README,docs/install.mdanddocs/MIGRATION-2.0.mdlink to it. - Changed the web UI's project sidebar to link each folder to its namespace
listing with a page count, and to show only its five most recently updated
pages with a "View all N" link. The namespace listing now sorts by recent
update or by title and shows 25 pages at a time with previous and next links
(
?sort=title&page=2). (#1247) - Changed scoped
/api/v1/search(a singleworkspace+project, orscopes) to rank with the same hybrid ranker asmemory_queryinstead of FTS5 alone, so it returns the same order asmemory_query, andrankis now the fused rank. Cross-project search without a scope is unchanged. - Documented hand-written MCP recipes for Warp, Cline, Kilo Code, Goose,
Qwen Code and OpenHuman in
docs/install.md, and listed them indocs/support-matrix.mdasMCP-only (documented): each recipe follows the client's own documentation at a named version and has not yet been run end to end. Aider is not listed because it has no MCP support. - Published
docs/benchmarks/longmemeval-s-decomposition-2026-10-12.md, which splits the LongMemEval-S retrieval number into capture cost, ranking quality and harness artifacts, and records what the observation stream recovered (zero-LLM FTShit@50.662 → 0.951 and local embeddings 0.800 → 0.945 on all 470 questions). The benchmark, evals and comparison docs no longer blame the 2 KB excerpt cap for the gap (pages holding exactly the captured text score 0.968 against 0.972 for whole sessions) and no longer attribute agentmemory's 0.967 to LongMemEval-S: it is the grep baseline on agentmemory's in-house corpus, and its LongMemEval-S figure is 95.2% from a component harness with the reranker disabled, not like-for-like with ai-memory's pipeline numbers.docs/comparison.mdgains a "From agentmemory" migration note. - Changed
ai-memory initon a fresh data dir (no config file and no database yet) to create a root bearer token at<data_dir>/auth-tokens/defaultand record[auth] bearer_token_file, so a new install's server requires the token on every route except/healthz. The hooks, the CLI andai-memory mcp-bridgeread it from the data dir; client commands of a packaged install, whose config lives outside the data dir, find it there too when they read no config file and target a loopback server. Existing installs are unchanged and keep running without auth untilai-memory auth enable; so is aninitthat finds a database or a bearer supplied another way.uninstallkeeps this token, since it is the server's credential. The Docker wrapper hands a host command (install-hooks,mcp-bridge,run) the token of a server initialized on its volume as an owner-only file thatinstall-hookscopies from the volume, never on a command line. The wrapper deletes that host copy wheninstall-hooksfinds no token in the volume and after a successfuluninstall --apply. With no human accounts the built-in web UI answers a browser with a Basic sign-in prompt instead of redirecting to a login form that could not succeed: any username, and the token file's contents as the password.ai-memory setupprints that URL and the file to read, never the token. The macOS menu bar app reads the token from its data dir when none is saved in Settings, so an install it initialized turns green without pasting anything. - Changed
ai-memory-wikisyncfiles to carry a small frontmatter (title, plustags,pinnedand a non-defaulttierwhen set) above the body, so metadata survives a round trip. The first run after upgrading rewrites unedited files of an earlier export in place. (#1164) - Changed the header search box in
/webto search the project you are viewing. On a project, page, namespace or history view it now sendsworkspaceandprojectwith the query, and its label and placeholder name the project. The search page gained a "Search in" selector (scope=<workspace>/<project>) that lists only the projects the viewer may read, an "in workspace/project ×" chip that clears the scope, a breadcrumb, and a "Search all projects" button when a scoped search finds nothing. (#1245)
- Removed the 1.x compatibility-token commands
ai-memory user add,user expire,user reviveanduser rotate-token, and their admin endpointsPOST /admin/usersandPOST /admin/users/{name}/expire,/reviveand/rotate-token. They were deprecated in 2.0. Tokens they issued keep authenticating after the upgrade as ordinary API keys with the labellegacy-user-token: list them withai-memory api-key list, replace one withapi-key rotate <id>and retire one withapi-key revoke <id>. A token expired before the upgrade stays refused. Add people withuser add-humanand machine credentials withapi-key add. Migration V79 drops the unusedusers.token_hashandusers.token_expired_atcolumns and the triggers that mirrored them intoapi_credentials, so user records from/admin/usersandai-memory user list --jsonno longer carrytoken_expired_at, anduser listno longer shows anexpiredstatus. - Removed the
OPENCODE_ZEN_BASE_URLconstant from theai-memory-llmcrate, deprecated since 2.1.0; useOPENCODE_GO_BASE_URL, which holds the same URL. Theopencode-zenprovider alias is unchanged.
- Fixed SessionEnd consolidation repeating the same expensive prompt on the
durable queue when the structured LLM response fails deterministically: a
truncated or empty response, malformed JSON, or an unexpected response
shape now ends the queue on the first attempt, keeping the heuristic page
the hook already wrote; pre-send connection and capacity (
503) failures retain the bounded backoff. (#1131) - Fixed structured LLM responses stopped at the output budget
(
finish_reason = "length") or returned without usable content: they now fail with redacted terminal errors, without copying the response. (#1130)
- Fixed HTTP Basic sign-in to the web UI minting a 30-day
ai_memory_authcookie that held the root bearer token. Basic sign-in sets no cookie now; a cookie set by an older build keeps working until human auth is enabled. - Fixed
memory_query,/web/searchand/api/v1/searchshowing another operator's captured observations on a multi-user server. Observation excerpts, the observation ids inexplain, and raw-observation fallback hits now come only from sessions the caller may read under the session-timeline owner rule. Another operator's observations still rank shared session pages.