diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6efe89b..a099e6bcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,53 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ## [Unreleased] +## [0.9.5] — 2026-05-09 + +Bug-fix patch focused on **search recall correctness** and **plugin compatibility**. Pins `iii-engine` to v0.11.2 because v0.11.6 ships a regression that breaks the agentmemory worker. Adds a hard guard against silent vector-index corruption, fixes BM25 indexing for memories saved via `memory_save`, and lands four Hermes plugin fixes that make the memory provider actually usable end-to-end. + +If you've been seeing `memory_smart_search` return empty results for memories you just saved, this release fixes that. If you've been hitting `hermes memory status` reporting "not available" against a healthy systemd-managed install, this release fixes that too. + +### Fixed + +- **BM25 search now indexes memories saved via `memory_save`.** `mem::remember` was writing to `KV.memories` but never calling `getSearchIndex().add()`, so `memory_smart_search` and `memory_recall` returned empty for everything saved through that path — for **every** version since v0.9.0. Synthesizes a `CompressedObservation` from the saved Memory (title + content + concepts + files) and adds it to BM25 right after the durable write. `rebuildIndex()` now walks `KV.memories` so a fresh rebuild covers the full corpus, and a startup backfill retroactively indexes pre-existing memories on first start after upgrade — no manual reindex required. New `SearchIndex.has(id)` is the idempotency gate. (#258, closes [#257](https://github.com/rohitg00/agentmemory/issues/257) — thanks @Nizar-BenHamida for the precise repro and log capture) + +- **Embedding providers no longer silently corrupt the vector index when an API returns wrong-dimension vectors.** `cosineSimilarity` returns `0` on length mismatch instead of throwing, so a wrong-size vector got stored, never matched anything, and the corresponding memory became invisible without a single log line. `withDimensionGuard()` now wraps every embedding provider at the factory boundary in `src/providers/embedding/index.ts` — `embed()`, `embedBatch()` (per-vector, indexed errors like `embedBatch[3]`), and `embedImage()` all throw a descriptive error when the returned `Float32Array` length doesn't match `provider.dimensions`. The persistence-restore path got the same defense: `IndexPersistence.load()` now refuses to start when persisted vectors mismatch the active provider, with an actionable error spelling out the recovery paths (re-embed / `AGENTMEMORY_DROP_STALE_INDEX=true` / switch back). (#248, closes [#247](https://github.com/rohitg00/agentmemory/issues/247) and [#256](https://github.com/rohitg00/agentmemory/issues/256) — thanks @AmmarSaleh50 for the issue analysis, the fix PR, and the test coverage) + +- **Hermes plugin: `handle_tool_call` now returns JSON strings, not raw Python dicts.** Hermes stores the return value as the tool result `content` field in session history. Anthropic-protocol providers reject non-string content with a 400 on the next request — once triggered, every subsequent request in the affected session 400s until the session JSON is hand-cleaned. Wrapped all four return paths (`memory_recall`, `memory_save`, `memory_search`, unknown-tool) in `json.dumps()` and tightened the return-type annotation `Any → str` on both the abstract base and the concrete class. Matches the contract that `src/mcp/standalone.ts` already honors. (#255, closes [#254](https://github.com/rohitg00/agentmemory/issues/254) — thanks @KyoMio for the Anthropic-protocol-specific repro) + +- **Hermes plugin: `hermes memory status` now reflects the real service state on systemd / launchd installs.** When agentmemory runs as an external service whose runtime config lives in `~/.agentmemory/.env`, those values never reach the Hermes CLI shell. Hermes status reads `os.environ` against `get_config_schema()`'s `env_var` keys, finds them unset, and reports the plugin as "not available" — even though the service is healthy. The plugin now preloads `~/.agentmemory/.env` at import time using `os.environ.setdefault`, bridging the agentmemory-managed and Hermes-managed config source-of-truths. Anything explicitly exported in the shell still wins. Best-effort: malformed / absent file is silently skipped. Both `~/.agentmemory/.env` and `$XDG_CONFIG_HOME/agentmemory/.env` are checked. (#253, closes [#250](https://github.com/rohitg00/agentmemory/issues/250) — thanks @OptionalCoin for the systemd repro and tracing it to env-source divergence) + +- **Hermes plugin: memory provider hooks accept passthrough kwargs.** Hermes calls memory provider hooks with extra context kwargs (e.g. `session_id`) at runtime that the existing strict signatures rejected with `sync_turn() got an unexpected keyword argument 'session_id'`. Hooks "succeeded" from Hermes's perspective but every conversation turn silently failed sync. Added `**kwargs: Any` to `sync_turn`, `on_session_end`, `on_pre_compress`, `on_memory_write`, `prefetch`, `queue_prefetch`, and `shutdown`. Where Hermes passes `session_id`, the patch prefers it over the cached `self._session_id` so multi-session gateway contexts route to the right session. Same change applied to the abstract `MemoryProvider` fallback for the import-error path. (#252, closes [#249](https://github.com/rohitg00/agentmemory/issues/249) — thanks @OptionalCoin for the precise log analysis) + +- **`agentmemory demo` now actually seeds observations.** `seedDemoSession` posted to `/agentmemory/observe` without `project` and `cwd`, which the API requires as non-empty strings, so every observation 400'd and the demo silently reported "Seeded 0 observations across 3 sessions". Two-line fix: re-stage `project` + `cwd` into the observe payload alongside `sessionId`. The smart-search queries the demo prints will now return real hits. (#251, closes [#229](https://github.com/rohitg00/agentmemory/issues/229) — thanks @seishonagon for the precise root-cause analysis) + +- **LLM compression / summarization timeouts increased.** Larger sessions were hitting the 120s consolidation timeout under heavier workloads, leaving partial state. Bumped per-step ceilings to give slow providers (esp. local models) room to finish. (#213 — thanks @xuli500177) + +- **`pi` / OpenClaw / Hermes integration fixes.** Tested round-trip fixes across the three integration plugins to keep them aligned with the latest hooks contract. (#230 — thanks @deepmroot) + +### Changed + +- **`iii-engine` pinned to v0.11.2 across every install path.** v0.11.6 introduces a new architecture where workers run inside sandboxed microVMs registered via `iii worker add`. agentmemory still uses the older `iii-exec watch + node dist/index.mjs` worker model from `iii-config.yaml`, which doesn't pass the new engine's stricter trigger validation cleanly — the worker drops into an EPIPE reconnect loop and recall stops working. Pinning to v0.11.2 (the last engine that runs agentmemory's current architecture cleanly) until we refactor agentmemory to register itself via `iii worker add` and run inside the new sandbox model. + - `src/cli.ts` auto-installer downloads `github.com/iii-hq/iii/releases/download/iii/v0.11.2/iii-.tar.gz` directly. Per-arch coverage: darwin arm64/x64, linux x64/arm64/armv7, win32 x64/arm64. + - Docker fallback pulls `iiidev/iii:0.11.2` instead of `:latest`. + - `docker-compose.yml` uses `image: iiidev/iii:${AGENTMEMORY_III_VERSION:-0.11.2}` so the override env var actually takes effect for compose users. + - Install instructions and Windows guide updated to point at the v0.11.2 release page. + - **Escape hatch:** `AGENTMEMORY_III_VERSION=` overrides the pin for users who've moved to the sandbox model manually. + - Windows ZIP path detection in `runUpgrade` so the auto-installer doesn't try to pipe a `.zip` through `tar -xz`. (#260) + - **Follow-up tracked separately:** refactor agentmemory to register as a sandboxed worker via `iii worker add` so the pin can be lifted. + +- **README documents how to extend agentmemory with `iii worker add`.** New "Powered by iii" section maps each `iii worker add ` to a concrete agentmemory capability — multi-instance memory, scheduled consolidation, durable retries on embeddings, sandboxed code exec, SQL state, extra MCP host. Lists only workers actually published to [workers.iii.dev](https://workers.iii.dev) with direct links. (#242) + +- **README iii Console section corrected.** The console ships with `iii` as a subcommand; there's no separate installer. Replaced the bogus `curl install.iii.dev/console/main/install.sh` line, simplified the launch command to `iii console --port 3114`, and added the missing console pages to the capability table (Workers, Queues, Config, Flow). Replaced the dashboard screenshot with the Workers page so users see real agentmemory instances connected. (#243) + +### Notes + +If you're upgrading from <0.9.5 and have an existing vector index on disk, the new dim-guard will refuse to load if your active embedding provider declares a different dimension than what's persisted. This is the intended safe default — set `AGENTMEMORY_DROP_STALE_INDEX=true` to discard and rebuild from live observations, or re-embed against the new provider before starting. + +If you've been on `iii-engine` v0.11.6 and noticed search returning empty after save, install agentmemory 0.9.5 fresh (or run `npx @agentmemory/agentmemory upgrade`) to pull pinned engine v0.11.2. v0.11.6 brings a new sandbox-everything-via-`iii worker add` model that agentmemory hasn't been refactored for yet — that work is tracked as a follow-up; this release just keeps existing users unblocked. + +[0.9.5]: https://github.com/rohitg00/agentmemory/compare/v0.9.4...v0.9.5 + ## [0.9.4] — 2026-04-29 Bug-fix patch. Fixes a silent gap where the knowledge graph never auto-populated despite `GRAPH_EXTRACTION_ENABLED=true`, and adds a doctor check that detects when Claude Code fails to load plugin hooks. diff --git a/README.md b/README.md index f079c1ee4..1ee96e117 100644 --- a/README.md +++ b/README.md @@ -413,7 +413,7 @@ npm install && npm run build && npm start This starts agentmemory with a local `iii-engine` if `iii` is already installed, or falls back to Docker Compose if Docker is available. REST, streams, and the viewer bind to `127.0.0.1` by default. -Install `iii-engine` manually. **agentmemory currently pins `iii-engine` to `v0.11.2`** — `v0.11.6` ships a regression where engine-internal cron/http triggers fail validation and the agentmemory worker drops into an EPIPE reconnect loop. Override with `AGENTMEMORY_III_VERSION=` once compat is verified manually. +Install `iii-engine` manually. **agentmemory currently pins `iii-engine` to `v0.11.2`** — `v0.11.6` introduces a new sandbox-everything-via-`iii worker add` model that agentmemory hasn't been refactored for yet. Pin lifts once the refactor lands. Override with `AGENTMEMORY_III_VERSION=` if you've migrated to the sandbox model manually. - **macOS arm64:** `mkdir -p ~/.local/bin && curl -fsSL https://github.com/iii-hq/iii/releases/download/iii/v0.11.2/iii-aarch64-apple-darwin.tar.gz | tar -xz -C ~/.local/bin && chmod +x ~/.local/bin/iii` - **macOS x64:** swap `aarch64-apple-darwin` for `x86_64-apple-darwin` @@ -431,7 +431,8 @@ agentmemory runs on Windows 10/11, but the Node.js package alone isn't enough ```powershell # 1. Open https://github.com/iii-hq/iii/releases/tag/iii%2Fv0.11.2 in your browser -# (we pin to v0.11.2 — v0.11.6 has a regression that breaks agentmemory) +# (we pin to v0.11.2 until agentmemory refactors for the new sandbox +# model that engine v0.11.6+ requires) # 2. Download iii-x86_64-pc-windows-msvc.zip # (or iii-aarch64-pc-windows-msvc.zip if you're on an ARM machine) # 3. Extract iii.exe somewhere on PATH, or place it at: diff --git a/docker-compose.yml b/docker-compose.yml index 65b4b8c81..2e379ce2a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,11 @@ services: iii-engine: - # Pinned to v0.11.2. v0.11.6 has a regression where engine-internal - # cron/http trigger registrations fail validation, causing the - # agentmemory worker to drop into an EPIPE reconnect loop and BM25 - # search to return empty after save. Bump only after compat is - # verified end-to-end against a fresh agentmemory install. + # Pinned to v0.11.2 — the last engine that runs agentmemory's current + # worker model cleanly. v0.11.6 introduces a new sandbox-everything- + # via-`iii worker add` model that agentmemory hasn't been refactored + # for yet; the architectural mismatch surfaces as EPIPE reconnect + # loops and empty search after save. Bump only after agentmemory is + # refactored to register as a sandboxed worker. # # Override per-shell or via .env file: # AGENTMEMORY_III_VERSION=0.11.7 docker compose up diff --git a/package.json b/package.json index de92bc9e2..ee60531c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@agentmemory/agentmemory", - "version": "0.9.4", + "version": "0.9.5", "description": "Persistent memory for AI coding agents, powered by iii-engine's three primitives", "type": "module", "main": "dist/index.mjs", diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index b070050c5..e59abb2e1 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "agentmemory", - "version": "0.9.4", + "version": "0.9.5", "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 51 MCP tools, 4 skills, real-time viewer.", "author": { "name": "Rohit Ghumare", diff --git a/plugin/scripts/session-end.mjs b/plugin/scripts/session-end.mjs index 5a36612c1..8e1de092e 100755 --- a/plugin/scripts/session-end.mjs +++ b/plugin/scripts/session-end.mjs @@ -28,7 +28,7 @@ async function main() { method: "POST", headers: authHeaders(), body: JSON.stringify({ sessionId }), - signal: AbortSignal.timeout(5e3) + signal: AbortSignal.timeout(3e4) }); } catch {} if (process.env["CONSOLIDATION_ENABLED"] === "true") { @@ -37,7 +37,7 @@ async function main() { method: "POST", headers: authHeaders(), body: JSON.stringify({ olderThanDays: 0 }), - signal: AbortSignal.timeout(15e3) + signal: AbortSignal.timeout(6e4) }); } catch {} try { @@ -48,7 +48,7 @@ async function main() { tier: "all", force: true }), - signal: AbortSignal.timeout(3e4) + signal: AbortSignal.timeout(12e4) }); } catch {} } @@ -56,7 +56,7 @@ async function main() { await fetch(`${REST_URL}/agentmemory/claude-bridge/sync`, { method: "POST", headers: authHeaders(), - signal: AbortSignal.timeout(5e3) + signal: AbortSignal.timeout(3e4) }); } catch {} } diff --git a/plugin/scripts/stop.mjs b/plugin/scripts/stop.mjs index a234dbe5e..e0ffa3505 100755 --- a/plugin/scripts/stop.mjs +++ b/plugin/scripts/stop.mjs @@ -28,7 +28,7 @@ async function main() { method: "POST", headers: authHeaders(), body: JSON.stringify({ sessionId }), - signal: AbortSignal.timeout(3e4) + signal: AbortSignal.timeout(12e4) }); } catch {} } diff --git a/src/cli.ts b/src/cli.ts index eeacb583d..73535e533 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -20,12 +20,14 @@ const IS_VERBOSE = args.includes("--verbose") || args.includes("-v"); // Pinned iii-engine version. The unpinned `install.iii.dev/iii/main/install.sh` // script tracks `latest`, which made every fresh agentmemory install pull -// engine 0.11.6 — and 0.11.6 has a regression where its internal cron/http -// trigger registrations fail validation, the worker drops into an EPIPE -// reconnect loop, and BM25 search returns empty after save (visible to users -// as "demo can't reach worker" and recall always-empty). Override env var -// AGENTMEMORY_III_VERSION lets early adopters move forward when a fixed -// engine ships, without us cutting another agentmemory release. +// engine 0.11.6 — and 0.11.6 introduces a new sandbox-everything-via- +// `iii worker add` worker model that agentmemory hasn't been refactored +// for yet (we still use the old `iii-exec watch` config-file model). The +// architectural mismatch surfaces as EPIPE reconnect loops and empty +// search results after save. Pin to v0.11.2 — the last engine that runs +// agentmemory's current worker model cleanly — until the refactor lands. +// Override env var AGENTMEMORY_III_VERSION lets users on the sandbox +// model already point at a newer engine without us cutting a release. const IIPINNED_VERSION = process.env["AGENTMEMORY_III_VERSION"] || "0.11.2"; @@ -370,8 +372,11 @@ function installInstructions(): string[] { " npx @agentmemory/agentmemory mcp", "", "Docs: https://iii.dev/docs", - `Why pinned: agentmemory hits a regression in iii v0.11.6. Override with`, - `AGENTMEMORY_III_VERSION= if you've verified compat manually.`, + `Why pinned: iii v0.11.6 introduces the new sandbox-everything model`, + `(\`iii worker add\` registration). agentmemory still uses the older`, + `iii-exec config-file worker model and needs a refactor before it`, + `runs cleanly under the new engine. Override with`, + `AGENTMEMORY_III_VERSION= when you've migrated manually.`, ]; } diff --git a/src/functions/export-import.ts b/src/functions/export-import.ts index 4b3e724b5..5d6e9c261 100644 --- a/src/functions/export-import.ts +++ b/src/functions/export-import.ts @@ -176,7 +176,7 @@ export function registerExportImportFunction(sdk: ISdk, kv: StateKV): void { const strategy = data.strategy || "merge"; const importData = data.exportData; - const supportedVersions = new Set(["0.3.0", "0.4.0", "0.5.0", "0.6.0", "0.6.1", "0.7.0", "0.7.2", "0.7.3", "0.7.4", "0.7.5", "0.7.6", "0.7.7", "0.7.9", "0.8.0", "0.8.1", "0.8.2", "0.8.3", "0.8.4", "0.8.5", "0.8.6", "0.8.7", "0.8.8", "0.8.9", "0.8.10", "0.8.11", "0.8.12", "0.8.13", "0.9.0", "0.9.1", "0.9.2", "0.9.3", "0.9.4"]); + const supportedVersions = new Set(["0.3.0", "0.4.0", "0.5.0", "0.6.0", "0.6.1", "0.7.0", "0.7.2", "0.7.3", "0.7.4", "0.7.5", "0.7.6", "0.7.7", "0.7.9", "0.8.0", "0.8.1", "0.8.2", "0.8.3", "0.8.4", "0.8.5", "0.8.6", "0.8.7", "0.8.8", "0.8.9", "0.8.10", "0.8.11", "0.8.12", "0.8.13", "0.9.0", "0.9.1", "0.9.2", "0.9.3", "0.9.4", "0.9.5"]); if (!supportedVersions.has(importData.version)) { return { success: false, diff --git a/src/types.ts b/src/types.ts index 806dae6ac..3d69e218e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -279,7 +279,7 @@ export interface ExportPagination { } export interface ExportData { - version: "0.3.0" | "0.4.0" | "0.5.0" | "0.6.0" | "0.6.1" | "0.7.0" | "0.7.2" | "0.7.3" | "0.7.4" | "0.7.5" | "0.7.6" | "0.7.9" | "0.8.0" | "0.8.1" | "0.8.2" | "0.8.3" | "0.8.4" | "0.8.5" | "0.8.6" | "0.8.7" | "0.8.8" | "0.8.9" | "0.8.10" | "0.8.11" | "0.8.12" | "0.8.13" | "0.9.0" | "0.9.1" | "0.9.2" | "0.9.3" | "0.9.4"; + version: "0.3.0" | "0.4.0" | "0.5.0" | "0.6.0" | "0.6.1" | "0.7.0" | "0.7.2" | "0.7.3" | "0.7.4" | "0.7.5" | "0.7.6" | "0.7.7" | "0.7.9" | "0.8.0" | "0.8.1" | "0.8.2" | "0.8.3" | "0.8.4" | "0.8.5" | "0.8.6" | "0.8.7" | "0.8.8" | "0.8.9" | "0.8.10" | "0.8.11" | "0.8.12" | "0.8.13" | "0.9.0" | "0.9.1" | "0.9.2" | "0.9.3" | "0.9.4" | "0.9.5"; exportedAt: string; sessions: Session[]; observations: Record; diff --git a/src/version.ts b/src/version.ts index 3c4478321..229feb427 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = "0.9.4"; +export const VERSION = "0.9.5"; diff --git a/test/export-import.test.ts b/test/export-import.test.ts index f081dbd1b..2aa4d3ab6 100644 --- a/test/export-import.test.ts +++ b/test/export-import.test.ts @@ -119,7 +119,7 @@ describe("Export/Import Functions", () => { it("export produces valid ExportData structure", async () => { const result = (await sdk.trigger("mem::export", {})) as ExportData; - expect(result.version).toBe("0.9.4"); + expect(result.version).toBe("0.9.5"); expect(result.exportedAt).toBeDefined(); expect(result.sessions.length).toBe(1); expect(result.sessions[0].id).toBe("ses_1");