7 Commits

Author SHA1 Message Date
Rohit Ghumare 696cf7abb8 feat: recall quality, provenance, keyless graph, and connector parity (#1205)
* fix: prompt dedup, double summarize, docker-mode stop, hermetic tests

- observe: hash the hook payload when tool_input is absent so prompt_submit,
  notification, and lifecycle events dedup on content instead of collapsing
  onto one shared key that silently dropped every prompt after the first in
  a TTL window (#1173)
- stop hook: drop the direct /agentmemory/summarize POST; /session/end
  already fans out event::session::stopped which runs mem::summarize, so
  every Stop dispatched two full summarizes (#1203)
- cli: refuse to adopt or signal Docker/VM port holders (com.docker.backend,
  vpnkit, colima, ...) as the native engine unless --force; scope Docker-mode
  teardown to agentmemory's own compose services via rm -s -f instead of an
  unscoped down; reap the native worker before Docker teardown instead of
  deleting worker.pid with the process still running (#1151)
- tests: isolate HOME/USERPROFILE for the whole vitest run so suites stop
  reading the developer's real ~/.agentmemory/.env (#1178)

* fix(viewer): live stream port discovery, fresh tab data, honest states

- resolve the stream WebSocket target from /agentmemory/livez (new
  streamsPort field) instead of viewerPort-1 arithmetic, which pointed at
  the wrong server whenever the viewer bound a fallback port and silently
  degraded live updates to 10s polling — verified reaching 'live' on the
  fallback-port case
- refetch tab data on every tab entry; the loaded-once cache meant a
  memory saved by the agent never appeared until a hard browser reload
  (loading placeholders now render only on first load, so background
  refreshes don't flash)
- memories: rows expand on click/Enter to the full stored record —
  content, id, project, created, supersedes, files — plus a collapsible
  raw JSON view
- graph: a 503 with the structured disabled body renders 'Knowledge
  graph is off' with the enableHow text and docs link instead of a
  'query failed / Retry' error that sends users to server logs
- sessions: cards get role=button, tabindex, Enter/Space activation, and
  the detail panel scrolls into view on select; session ids truncate
  head…tail so the distinguishing suffix stays visible
- style search inputs and toolbar buttons on lessons/actions/crystals/
  replay (previously bare native controls); horizontal scroll containment
  for narrow viewports
- demo: only print the semantic-recall success notice when the search
  actually hit; on 0 hits explain the missing embedding key instead

* fix: thread agentId/project through save paths, per-session OpenCode scope

- REST /agentmemory/remember accepts and forwards agentId to mem::remember;
  it previously dropped the field so per-request multi-agent scoping was
  impossible over REST (#1159)
- memoryToObservation() carries the memory's agentId into the search-index
  shape; dropping it made every memory invisible to agent-scoped search
  (#1160)
- MCP memory_save path: the tool schema now exposes agentId, the in-worker
  MCP server forwards it, and the standalone stdio package parses and
  forwards both agentId and project — the stdio pipeline previously dropped
  project even though its schema advertised it (#1197)
- opencode plugin: project/cwd attribution is per-session (resolved from
  the session's own directory at session.created, pruned on session end)
  instead of module-level state that recorded every session in a
  multi-directory OpenCode process under whichever repo loaded the plugin
  first (#1188)

Live-verified: memory saved with agentId=agent-alpha is returned by
smart-search for agent-alpha and hidden from agent-beta.

* feat: hybrid recall everywhere, indexed lessons, provenance, recall hygiene

- mem::search ranks through the full BM25+vector+graph fusion when the
  vector index is populated (injected post-boot via setHybridRanker);
  the primary recall surface was keyword-only while only smart-search
  got hybrid ranking
- fusion weights normalize per item over the streams that actually
  ranked it, with a small explicit cross-stream agreement bonus; the
  old every-enabled-stream denominator permanently penalized
  single-stream hits (the graph stream is empty on default installs).
  Result order is now deterministic (score, best rank, id)
- lessons get a dedicated in-memory BM25 index built lazily from one KV
  list and maintained incrementally on save/delete/decay; recall
  previously listed and substring-scanned the whole corpus per query.
  Confidence x recency composite scoring is unchanged
- mem::remember finds supersession candidates through the search index
  (top-50) instead of walking every memory per save, with a full-scan
  fallback while the index is cold; near-miss similarity (0.4-0.7)
  is reported back as an advisory similarTo hint
- superseded memory versions leave the BM25 and vector indexes; the
  version chain stays in KV for history, but recall no longer returns
  an outdated fact as if current
- every observation and memory now carries an immutable origin block
  (channel: user|agent|tool|import|shared, detail, capturedAt) stamped
  at capture, save, and import, and inherited through both compression
  paths — the base for trust-aware retrieval and ingest screening
- regression tests: supersede index removal, similarTo hint,
  index-backed candidate discovery, lesson index recall/lazy
  rebuild/delete

* feat(viewer): two-pane sessions, navigable dashboard, motion and copy polish

- sessions: list + sticky detail panel side by side above 1100px (the
  detail previously rendered below the whole list, off-screen on any
  real corpus); selected/hover/active states with reserved left border
  so selection doesn't shift layout
- dashboard stat cards for sessions/memories/lessons/crystals/graph
  navigate to their tabs (click or Enter), with hover affordance
- observation subtitles that are raw serialized tool input now display
  the meaningful field (file path, command, pattern, url) instead of a
  JSON blob
- expanded memory rows show the new origin provenance (channel + detail)
- motion: 160ms view entrance, live-badge pulse, both gated behind
  prefers-reduced-motion; tabular numerals in tables
- mobile: header stops wrapping the dateline into the badge row
- lessons/crystals empty-state copy aligned with the header definitions
  (each concept was described two conflicting ways)

* refactor: cleanup pass over the branch diff

- shared test mocks: the three new test files use test/helpers/mocks
  (extended with update, store access, and an opt-in loose trigger)
  instead of three diverging inline copies
- lessons: record cache beside the index takes recall to zero KV
  round-trips (was up to 50 gets per call); the observation adapter
  moved next to memoryToObservation so both record kinds thread new
  fields in one place; dead reset export removed
- mem::search hybrid path carries the observations the ranker already
  loaded instead of refetching every result (halves KV I/O on the
  primary recall path); remember's candidate lookup skips ids that
  cannot resolve as memories and fails open to a full scan
- fusion: derived tiebreak field no longer rides along past the sort;
  comment trimmed to the non-obvious history
- cli: engine identity is a positive check (only the iii binary may be
  adopted or signaled; unknown port holders are refused, not just known
  VM names); worker reap extracted to one helper; demo notice picks its
  branch from a hoisted count
- api: livez and health share one instanceInfo source (health now
  reports streamsPort too) computed once at boot instead of rebuilding
  the merged env per request
- provenance: one importOrigin factory encodes the keep-or-mark rule at
  all three import sites
- opencode plugin: project resolution memoized per directory (was a
  blocking git subprocess per session event); session.created uses the
  entry it just built
- observe: origin channel derived from a named hook set, no nested
  ternary
- viewer: toolbar buttons merged into the .btn rules, one 720px media
  block, generic keyboard activation for role-carrying cards,
  scroll-into-view only on the stacked layout, 5s freshness gate on tab
  refetch (replay stays fetch-once, reason documented), subtitle
  humanizer covers the capture-side key variants

* feat(viewer): clarity pass and ambient refresh

- health notes/alerts translate their machine slugs into sentences
  (memory_heap_tight_93%_rss111mb reads as heap usage with context)
- lessons rows expand to full detail: rule, why-learned context, tags,
  learned/last-confirmed times, source sessions, raw record; column
  headers carry title hints for confidence and uses
- actions tab gets the same intro card as the other tabs (status flow
  and frontier explained on the populated view, not just when empty)
- timeline defaults to the session with the most observations instead
  of the newest, which was often a sparse just-started session
- consolidation status and top-concepts zero states explain what fills
  them and which flags gate it
- ambient background: the static dot grid becomes a slowly drifting
  ordered-dither field (quarter-res canvas, ~12fps, static frame under
  prefers-reduced-motion, theme-aware)
- dark theme: layered near-black surfaces, hairline borders, softened
  accent — replaces the flat gray borders

* feat(website): reskin on the near-black token system

- neutral token foundation in globals.css: canvas/canvas-soft/card
  surfaces, hairline borders, ink/body/mute text scale, one warm accent
  used sparingly, 8px card radius + pill buttons, focus-visible rings
- Inter display at weight 400 with tight tracking; mono uppercase
  eyebrows and captions; sentence-case body (case normalization only,
  no copy changes)
- hero: two-tone lowercase wordmark, install command as a soft input
  card, ambient drifting dot field capped at 0.12 alpha with a static
  frame under prefers-reduced-motion
- sections rebuilt on the card recipe: quiet background-shift hovers,
  hairline data table for the comparison, segmented tabs with polarity
  flip, per-vendor accent colors stripped from agent cards
- fixed two latent token misuses that resolved to nothing
- build green: 5/5 static pages, TypeScript clean

* fix(viewer): make the graph tab legible without edges

- nodes anchor to per-type cluster centers (captioned on the canvas)
  whenever relations are sparse; a pure force layout with no edges was
  an unlabeled scatter. Edge springs take over as real relations arrive
- labels always render on graphs of 30 or fewer visible nodes instead
  of only past a zoom threshold
- sidebar explains the entities-without-relations state and what
  produces edges; static legend removed (the type filter already
  carries color and shape)

* fix(viewer): graph readability on sparse data

- hover focus-fade only engages when the graph has edges; with none it
  faded every other node and suppressed all labels
- cluster anchor pull reduced and initial scatter widened so type
  groups spread instead of collapsing into blobs
- minimum node radius raised for degree-zero nodes; cluster captions
  offset above their groups

* fix(viewer): graph fits the view; site copy grounded in the repo

viewer graph:
- container height leaves room for the footer instead of running under it
- one-shot auto-fit zooms and pans to the node bounds once the layout
  settles, so first paint is framed instead of adrift
- cluster captions and small-graph labels hide below readable zoom

website copy (full pass, technical register):
- every unverifiable number removed: benchmark percentages, latency
  claims, press strip, testimonials, invented terminal output; the
  comparison table usage dropped rather than shipping stale competitor
  figures
- remaining stats are build-derived (54 MCP tools, 130 REST endpoints,
  12 hooks, 1619 tests) or live from the GitHub API (stars)
- feature copy corrected against src behavior: consolidation, graph
  extraction, and LLM compression activate with a provider key;
  provider list completed; install step numbering fixed
- release-branch capabilities surfaced: agent-scoped save and recall,
  write-time provenance channels, hybrid ranking on the primary recall
  path, indexed lessons, near-duplicate save hints, superseded-version
  recall hygiene, JSONL import deriving crystals and lessons
- em-dashes and slop phrasing removed throughout

* fix: restore featured strip, label collision avoidance, optional no-think

- website: FeaturedIn strip returns to the hero (its claims are the
  project's own credentials); rest of the grounded-copy pass unchanged
- viewer graph: canvas cluster captions removed and labels place
  greedily into free space (selected/hovered always win), so zoomed-out
  views degrade to fewer labels instead of overlapping pills
- graph extraction: AGENTMEMORY_LLM_NOTHINK=1 opt-in asks local
  reasoning models to skip their hidden thinking pass (several times
  faster, slight quality tradeoff); documented in .env.example, default
  behavior unchanged

* feat(website): testimonials return, OpenCode joins the featured connectors

- Testimonials section restored after LiveTerminal (launch-thread
  quotes are the project's own record)
- OpenCode promoted from the marquee to the featured grid: it ships a
  native capture plugin with per-session project attribution; fills the
  empty eighth slot
- full connector roster verified against src/cli/connect (18 dedicated
  adapters all present: featured grid + marquee)

* fix(viewer): official icon as the favicon (was a text placeholder)

* test(viewer): favicon assertion checks the served SVG, not a hex value

* test(viewer): favicon checks assert served SVG shape, not old artwork

* docs: readme grounded in source, changelog entry, env example consistency

* revert(website): restore measured benchmark claims and comparison table

The retrieval recall and token reduction figures are the project's own
measurements and its adoption story; earlier scrubbing was over-strict.
Backing them with a published run of the eval harness stays on the
roadmap.

* fix(website): drop the orphaned pause control on the hero field

The old animated constellation earned a pause button; the subtle dither
field does not, and prefers-reduced-motion already renders it static.

* fix(website): official OpenCode brand mark on the featured card

* chore: bump provider default models to current generations

OpenAI gpt-4o-mini to gpt-5.6-luna, Anthropic claude-sonnet-4-20250514 to claude-sonnet-5, Gemini gemini-2.5-flash to gemini-3.7-flash, MiniMax M2.7 to M3, OpenRouter default to anthropic/claude-sonnet-5. Premium cost warning matches the Sol tier; cheap-model hints lead with deepseek/deepseek-v4-flash-0731. README local picks move to qwen3 / gpt-oss / deepseek-r1 with a NOTHINK callout; cost table refreshed with verified OpenRouter list prices. Embedding defaults unchanged.

* feat: keyless heuristic graph extraction with LLM enrichment optional

Entities and co-occurrence do not need a language model: files and concepts on compressed observations already name the nodes, and appearing in the same observation is an edge. mem::graph-extract now always runs this deterministic pass, so the graph populates for keyless installs; the LLM pass layers typed relations on top only when GRAPH_EXTRACTION_ENABLED is set and a real provider exists. Session end fires extraction unconditionally.

* feat: DeepSeek Harness connector via home-level cordis patch layer

agentmemory connect dsh appends an @deepseek-ai/dsh-mcp-client row to DSH_HOME/cordis.patch.yml (default ~/.dsh), the machine-local patch layer every Harness profile loads, so the MCP tools register as mcp__agentmemory__* before the first turn. Idempotent, --force replaces the row, dry-run supported. Config shape verified against the mcp-client README and publish docs in deepseek-ai/deepseek-harness. Website agents grid and README connector table updated.

* feat: dsh --with-hooks auto-capture via Harness Claude Code bridge

DeepSeek Harness ships a first-party @deepseek-ai/dsh-hooks-claude-code plugin that runs Claude Code shaped command hooks on the harness's own interception points. connect dsh --with-hooks writes the bundled hook manifest (absolute script paths, reusing the codex-hooks merge engine) to DSH_HOME/agentmemory.hooks.json and appends a second patch row pointing the bridge at it. Auto-capture on SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop; PreCompact is outside the bridge subset and skipped. Adapter recategorized native. MCP-only installs never touch the hooks row; --force replaces both.

* feat(pi): automated connect install into pi's auto-discovery dir

connect pi was a stub printing manual copy steps because integrations/ never shipped in the npm package. The extension source now ships (integrations/pi/ in files), and the adapter copies index.ts + security.ts into ~/.pi/agent/extensions/agentmemory/, which pi auto-discovers with no settings.json edit; /reload picks it up live. Idempotent by content compare, stale copies refresh with a backup, dry-run supported. integrations/pi is also a private local pi package (pi-package keyword, pi.extensions manifest limited to index.ts so security.ts is not loaded as its own extension, peer deps on the pi core packages) so pi install ./integrations/pi works from a checkout; never published to npm. Type import moved to @earendil-works/pi-coding-agent (upstream rename).

* fix(codex): warn that hooks need one-time TUI trust approval

Codex executes only hooks with a recorded trusted_hash in config.toml, and the Hooks-need-review approval prompt appears only in the interactive TUI. A codex exec-only workflow therefore never runs freshly installed hooks and gets no signal why. connect codex --with-hooks now warns to launch codex once and choose Trust all, and to re-approve after upgrades since the refreshed absolute paths change the hash. Verified live on codex 0.147.0: before trust, exec dispatched nothing for agentmemory hooks; after TUI approval, SessionStart and UserPromptSubmit fired and the observation landed in the daemon.

* feat(pi): capture parity for the pi extension

Session registration on session_start (ordered after the health check so it fires on the first session of a fresh process), prompt capture with client-side dedup and user-channel provenance, per-tool observations from tool_result with AGENTMEMORY_TOOL_OBSERVE=0 opt-out, turn slices raised to 8000, memory_save scoped to the current project, session end plus one consolidate run on real quit only (no client summarize: session/end already fans out the summary), health accepts status ok, and refreshStatus binds the status setter before awaiting so a session replacement mid-check cannot throw a stale-context error. Live-verified on pi 0.84.2 with a local model: prompt and turn observations landed and the session closed as completed on quit.

* chore: regenerate skill reference docs

npm run skills:gen after the adapter, env, and tool changes: 20 adapters including dsh, refreshed env defaults, tool listing.

* refactor: trim oversized comments to constraint one-liners

Connector and extension comments compressed to the constraints the code cannot show; narrative headers, source citations, and restated behavior removed.

* docs(changelog): fold unreleased into the 0.9.29 release section

This branch ships as 0.9.29 (npm latest is 0.9.28; the previous 0.9.29 section was prepped but never published). One section, dated 2026-08-15, upgrade notes preserved, all 44 bullets intact.

* docs(readme): interactive-first install, dedupe, refresh stale counts

Install leads with npx and the first-run wizard (agent multi-select, provider pick, global-install offer) instead of six manual commands; Windows, EACCES, npx-cache, and iii-pin notes collapse into details blocks. Quick Start drops the duplicated install prose for an everyday-commands list. Nav drops the redundant iii Console link. Gist badge updated to the live 1.6k stars / 230 forks; test count pill and alt text updated to 1,648.

* fix: apply review round — ranking, indexes, lifecycle, connectors

Hybrid scoring normalizes once per query by the best attainable weighted score over streams that produced results, so configured stream weights survive single-stream hits; expansion merge gets the same deterministic tie-break. Graph functions register unconditionally (keyless installs previously fired mem::graph-extract at an unregistered function every session end) and the trigger goes through fireVoid; heuristic edges accumulate observation provenance for repeated pairs instead of dropping it. Supersession candidate search waits for the memory index walk (new isMemoryIndexReady signal) instead of trusting idx.size, and mem::search falls back to keyword search when the hybrid ranker throws. Lesson recall over-fetches under project/confidence filters, refreshes the index entry when reinforcement changes indexed text, and resetLessonIndex clears the cache after import and replay write lessons directly. Observe preserves primitive payloads in the dedup key so distinct prompts never collapse (regression test added). pi extension dedups prompts per session and passes project on both smart-search calls. dsh reads a corrupt hooks manifest as absent via readJsonSafe; pi install returns skipped instead of throwing when the bundled source is missing; both use the shared writeTextAtomic. Docker-mode stop clears each pidfile/state only after its shutdown succeeded and matches compose services at any indentation. Viewer livez fetch gets a 5s timeout. opencode session.deleted prunes through pruneSessionMaps (was leaking sessionProjects). MiniMax MAX_TOKENS doc says the real 4096 default. README MCP catalog: base-tools table completed to the registry's 14, the 8-tool core mode and 7-tool standalone fallback distinguished, two missing resources listed. Tests restore env vars without writing the string undefined, similarTo assertions are unconditional, vitest test home is unique per run, website meta regenerated, deprecated word-break replaced.

* docs: competitors refreshed — TencentDB Agent Memory column, entrants

TencentDB Agent Memory (TencentCloud OSS, May 2026, 22K stars) gets a full column: team memory hub captured through an LLM proxy, four asset types, PersonaMem 76% self-reported, Docker Core+Hub+Proxy stack. Stale star counts refreshed against the live API (mem0 58K to 63K, Letta 24K, Khoj 36K, supermemory 29K). A newer-entrants table covers Zep/Graphiti, Cognee, LangMem, Cloudflare Agent Memory, and Memobase, with matching choose-if sections in benchmark/COMPARISON.md. Section badge subtitle updated.

* fix: lesson index build races, rebuild ready flag, pi file backups

* docs: drop competitor links from README

* Update README.md
2026-08-15 20:37:12 +01:00
Rohit Ghumare 9c82d2aa7d chore(release): v0.9.29 with project-scope parity across capture surfaces (#1141)
* chore(release): v0.9.29 with project-scope parity across surfaces

Version trio + plugin manifests + supportedVersions + ExportData union
bumped to 0.9.29; CHANGELOG entry covering everything since v0.9.28 with
upgrade notes for the four visible behavior changes.

Fixes the endpoint-count drift on main (130 registered routes vs docs
saying 129 after #1132 landed in parallel with #1136).

Project-scope parity: OpenCode plugin, Hermes plugin, Pi extension, and
JSONL replay now resolve project the same way the hooks do (env
override, git toplevel basename, cwd basename) instead of sending raw
filesystem paths, closing #903 and #1135 and pre-empting the same bug
in pi. The filesystem watcher accepts AGENTMEMORY_PROJECT_NAME with the
old AGENTMEMORY_PROJECT kept as a deprecated alias, replay handles
Windows-recorded paths, and OpenCode file enrichment matches the
agent's lowercase tool names (the capitalized set never matched).

Tests: opencode fallback expectations updated to basenames per the
canonicalization, git-toplevel resolution covered with a fixture repo,
new project-scope-parity suite for replay and fs-watcher.

* fix(release): review findings, git-toplevel parity, doc counts

- skills generator dedupes routes on method plus path, so the REST
  reference lists all 130 registered routes instead of hiding the second
  method on ten dual-method paths (header said 119)
- fs-watcher trims AGENTMEMORY_PROJECT_NAME and the deprecated alias,
  treating whitespace as unset, and derives the git toplevel basename
  when watching a subdirectory
- replay resolves the git toplevel basename when the recorded cwd still
  exists locally (memoized per cwd), keeping the basename fallback for
  historical or cross-platform paths; no env override here since a bulk
  import spans many projects
- parity tests for replay git-root resolution, watcher git-root and
  trim behavior
- stat-tests badge updated from 1428+ to 1550+ passing

* fix(cli): refuse second-instance boot over a live daemon

Closes the class behind issue 1140: agentmemory consolidate (or any
unrecognized word) fell through the command table into the full server
boot, registering a duplicate worker on the running engine; on iii
0.11.2 the second instance's shutdown tears down the daemon's HTTP
trigger routing until a full engine restart. Unknown subcommands now
error with the supported list, and main() probes livez on the resolved
port and refuses to boot over a live daemon, so multi-instance setups
on other ports are unaffected. Verified behaviorally against the built
CLI: both paths refuse with exit 1.

Also from review: the watcher stamps each event with its own root's
project via a per-root map (an explicit config.project still overrides
for every root), and replay only accepts a non-empty string cwd from
parsed JSONL so malformed entries cannot reach the filesystem probe.

* test(watcher): two-repository flush events scope to their own project

* chore(release): bump packages/mcp, guard it, refresh CONTRIBUTING

packages/mcp was still 0.9.28 after the release bump because nothing
guarded it; a consistency test now pins it to package.json. CONTRIBUTING
release list corrected to the files a bump actually touches (no tracked
lockfile, the two extra plugin manifests, the export test derives from
VERSION now), and the subsystems table gains src/cli, integrations/pi,
and the generated-manifest note.

* fix(export): refuse over-frame export instead of dropping the worker

Closes the availability bug in issue 1142: GET /agentmemory/export
assembles the full store and returns it through sdk.trigger, so a store
whose serialized export passes the engine's 16 MiB WebSocket frame
(tungstenite max_frame_size, not raisable under the 0.11.2 pin) dies on
the worker->engine hop, drops the worker, and 404s every endpoint for
~1s. The session collections page on maxSessions/offset but ~18 others
do not, so a large store hits this at any parameter combination.

A shared frame-guard measures the serialized size before returning:
mem::export returns a small oversized error instead of the giant
object, and api::mesh-export returns 413 (same dead-end as #890). Either
way the over-frame payload never crosses the boundary, so the daemon
stays up and the failure is one clean request with a hint to narrow the
range. Full pagination of the non-session collections is a follow-up.

Layer 1 of the fix; verified with a synthetic oversized export returning
the error object (tiny) rather than the payload.

* ci: collapse to a single npm install to fix Node 24/26 CI

The two-step install (npm install --package-lock-only then npm ci) failed
only on the Node 24/26 matrix rows: their stricter npm rejects rolldown's
optional platform bindings (@rolldown/binding-android-arm64) that a
--package-lock-only pass does not fully enumerate. Lockfiles are gitignored,
so npm ci re-validation buys no reproducibility here. A single lenient
npm install resolves and installs in one pass.

* fix(mesh): scope exported memories by project like actions

api::mesh-export filtered actions by ?project but returned every project's
memories. On a mesh instance federating one project to a peer, the peer
pulled other projects' memories (cross-project leak), and those extras could
push the payload past the 16 MiB transport frame into a 413 even when the
requested project's own slice fit. Memories carry the same optional project
field as actions, so filter both before the frame-size guard runs.

Adds a regression test asserting a project-scoped export excludes other
projects' memories and that an oversized memory in another project no longer
413s the scoped request.

* chore(release): credit the Antigravity native hooks adapter in 0.9.29 notes

* chore(release): sweep stale 0.9.28 refs for 0.9.29

Deploy Dockerfiles/compose/render pins, AGENTS.md stats header, opencode
plugin manifest, website meta snapshot, test-count claims (1,428 -> 1,596)
in README/AGENTS/stat SVGs, and the missing 0.9.29 CHANGELOG compare link.

* chore(release): sync stat-tests badge to 1596+ and commit bridge exec bit

* refactor: trim frame-guard comments and drop issue refs from code
2026-08-09 13:22:25 +01:00
Som Samantray 5023cf3ccb fix(memory): honest memory_forget reporting + lesson delete path (#1132)
* fix(memory): guard mem::forget delete/count on record existence

Calling mem::forget with a lesson id (lsn_*) deleted a nonexistent key
from the memories keyspace, counted it, and reported success. Guard the
delete, index cleanup, and counter on the kv.get result, matching the
mem::governance-delete pattern, so nonexistent ids return
{ success: true, deleted: 0 } with no audit row. Closes #1120.

* feat(lessons): add mem::lesson-delete soft-delete function

Register mem::lesson-delete to set deleted: true on a lesson, mirroring
the lesson-strengthen existence guard and audit pattern. Read paths
already filter !l.deleted, and re-saving deleted content creates a fresh
lesson. Adds lesson_delete to the audit operation union.

* feat(mcp): expose memory_lesson_delete tool and REST endpoint

Wire mem::lesson-delete through the MCP tool registry and dispatch
case (memory_lesson_delete) and a POST /agentmemory/lessons/delete REST
route with 400 for a missing lessonId and 404 for a nonexistent lesson.

* chore(consistency): bump tool/endpoint counts to 54/129

Adds memory_lesson_delete to the registry, so update every count surface:
tool-count test, README badge and prose, AGENTS.md stats,
INSTALL_FOR_AGENTS.md, plugin manifests and docs, and the two code
comments this change makes stale. REST endpoint count goes 128 to 129
for the new /agentmemory/lessons/delete route.

* refactor(lessons): simplify 404 mapping and restore decay-delta test

Cast the lesson-delete trigger result once instead of twice inline, and
restore the lastDecayedAt incremental-delta decay test that was dropped
when the lesson-delete describe block was added.

* fix(review): align 404 error shape and regenerate skill references

Review fixes: the lesson-delete REST route now returns the repo-standard
{ error: 'lesson not found' } body on 404 instead of the function-shaped
{ success: false } payload, matching api::memory-by-id. Regenerated the
autogen MCP and REST skill references so memory_lesson_delete and the
lessons/delete route appear in the tables with accurate counts.

* fix(lessons): normalize lessonId at entry points and harden no-op test

Address CodeRabbit review: trim lessonId once at both the MCP dispatch
and REST route before triggering mem::lesson-delete (whitespace-padded
ids previously 404'd or looked up raw), and extend the nonexistent-
memoryId regression test to assert the no-op path performs no kv.delete
and no search-index cleanup.

---------

Co-authored-by: Rohit Ghumare <48523873+rohitg00@users.noreply.github.com>
2026-08-02 19:07:09 +01:00
Abdullah Alaqeel 9be3d2743a feat(opencode): allow AGENTMEMORY_PROJECT_NAME to override project name (#1125)
* feat(opencode): allow AGENTMEMORY_PROJECT_NAME to override project name

The OpenCode capture plugin resolved the project as ctx.worktree ||
ctx.project?.id || process.cwd() (a full path) with no way to override
it. It now consults AGENTMEMORY_PROJECT_NAME first — the same env var the
Claude hooks use in resolveProject() — falling back to the existing
resolution when unset.

This lets users pin a stable project name and aligns the two plugin
ecosystems so OpenCode and Claude sessions can share memory when the env
var is set to the same value.

Signed-off-by: Abdullah Alaqeel <abdullah.t.aqeel@gmail.com>

* test(opencode): replace source-text assertions with runtime project-name tests

Execute the plugin end-to-end, fire a session.created event, and assert
the /session/start payload's project field. Covers whitespace trimming
of AGENTMEMORY_PROJECT_NAME and the documented fallback chain
(ctx.worktree -> ctx.project.id -> process.cwd).

Signed-off-by: Abdullah Alaqeel <abdullah.t.aqeel@gmail.com>

---------

Signed-off-by: Abdullah Alaqeel <abdullah.t.aqeel@gmail.com>
2026-08-02 19:00:05 +01:00
Rohit Ghumare fd197dfda4 chore(release): v0.9.22 (#659)
Bump version across package.json + plugin manifests + version.ts +
types.ts ExportFormat union + export-import allow-list.

Strip issue-number stamps (// #NNN:) from new-wave source + test
files. CHANGELOG.md v0.9.22 entry bundles every PR merged since
v0.9.21 across Fixed / Added / Docs / Infrastructure.

1171/1171 vitest pass.
2026-05-26 10:53:03 +01:00
Rohit Ghumare f027c20309 fix(multi): stability pass for #627 #640 #474 #638 #431 #544 #563 (#648)
* fix(multi): stability pass for #627 #640 #474 #638 #431 #544 #563

Six issues, one PR. Each lands with a targeted regression test;
1119/1119 vitest pass.

#627 OpenAI thinking-model fallback
  src/providers/openai.ts now reads message.reasoning_content alongside
  message.reasoning. DeepSeek V4 / Qwen3 / GLM / Kimi return the
  underscored field — previously compress silently failed (0/700 calls)
  and the circuit breaker tripped.

#640 + #474 stop reaps the worker process
  src/index.ts writes ~/.agentmemory/worker.pid on registerWorker, clears
  it on graceful shutdown. src/cli.ts runStop now reads the worker pidfile
  and signals SIGTERM alongside the engine pids. Fixes both: the daemon
  wrapper surviving stop (#640) and the iii engine retaining stale
  function registrations because the worker reconnected to the new
  engine (#474).

#638 OpenCode session implicit-create on observe
  src/functions/observe.ts now creates the session record on the first
  observation when project + cwd are present and no session exists.
  OpenCode plugins (and any caller that skips POST /session/start) no
  longer leak observations into a session memory_sessions never lists,
  and summarize stops bailing with 'Session not found'.

#431 OpenCode auto-context (zero-config injection)
  plugin/opencode/agentmemory-capture.ts captures the context returned
  by POST /session/start into a per-session cache. The existing
  experimental.chat.system.transform hook now reads from the cache
  first, falls back to /context. Cleanup on session.deleted.

#544 paginated /memories + /export
  src/triggers/api.ts adds three query modes to /memories:
    ?count=true       — totals only, viewer status badge
    ?limit=N&offset=M — paged slice, default unlimited
  /export now forwards maxSessions + offset query params to mem::export
  (which already supported them). Viewer dashboard caps the memories
  fetch at 500; the memories tab at 2000. Both stop the iii invocation
  timeout from masking real corpora as 0 memories.

#563 viewer graph cool-down on >1000 nodes
  src/viewer/index.html adds tick-decayed damping (coolBoost), per-node
  velocity caps tiered by node count, and quiescence-based raf parking.
  Mousedown wakes the parked loop. Dense graphs settle instead of
  bouncing forever; CPU returns to idle once the layout is quiet.

#637 Windows em-dash ByteString — deferred to follow-up
  Cannot reproduce on macOS / Linux. The user-suggested defensive
  encoding fix is unsafe without a Windows repro confirming the actual
  exception path. Will land separately once a Windows runner or the
  reporter can validate.

* fix(multi): address review findings on PR #648

Addresses inline review on PR #648 — verified each finding against
current code and fixed the still-valid ones.

opencode plugin: snapshot activeSessionId into a local 'sessionId'
before await postJson('/session/start') — a second session.created
event during the await could rebind activeSessionId and cache the
context against the wrong key. The cache write + observe call now use
the snapshotted id.

src/cli.ts: clearWorkerPidfile() now runs in every stop branch:
  - Docker engine-not-running early return
  - Docker stopDockerEngine path
  - native engine-not-running 'Nothing to stop'
  - native happy path (was already there)
The worker pid is now read up front so the engine-down branch can also
reap an orphaned worker process (previously fell through to 'preserve
for manual cleanup'). A new dedicated branch reaps the worker and
exits cleanly when only the worker is lingering.

src/viewer/index.html: wakeGraphSim() shared helper consolidates the
quietTicks reset + raf restart pattern. Wheel handler, zoomGraph(),
recenterGraph(), and mousedown all now wake the parked simulation so
zoom/pan/click feedback is immediate after the layout has settled.
graphSim object initializes quietTicks: 0 alongside tickCount: 0.

src/functions/observe.ts: dedupe new Date().toISOString() into a
single 'ts' local for the implicit-create path so startedAt and
updatedAt stay consistent.

test/opencode-auto-context.test.ts: regex updated to assert the
snapshot-then-cache pattern instead of the previous direct
activeSessionId reference.

1119/1119 vitest pass.
2026-05-25 19:45:58 +01:00
Trip 68fddd418e feat: OpenCode plugin with 22 auto-capture hooks (closes #236, #244) (#237)
* feat: OpenCode plugin with 22 auto-capture hooks (closes #156)

- 22 hook handlers across session lifecycle, messages, tool lifecycle, parts, files, permissions, tasks, commands, and config
- Two-layer enrichment pipeline: /context + /enrich via system.transform
- Two slash commands: /recall and /remember
- Full Claude Code hook parity documented with gap analysis

* fix(plugin): use prompt_submit hookType so sessions get firstPrompt

mem::observe checks hookType === "prompt_submit" to extract raw.userPrompt
and set session.firstPrompt. The plugin was using "user_prompt_submit" which
didn't match, so sessions were never named.

* fix: address CodeRabbit review feedback on OpenCode plugin

- Use ctx.worktree for projectPath instead of opaque project.id
- Add Array.isArray guards before output.system/.context.push()
- Only delete stashed files after successful enrich POST
- Defensive JSON.stringify for undefined tool inputs
- Per-session Map-based dedup sets to prevent unbounded growth
- Fix negative duration_ms when time.completed is unset
- Validate props.file and enforce MAX_STASHED_FILES in file.edited
- Deduplicate /summarize call on session idle
- Buffer early config events until session.created flushes them
- Move contextInjectedSessions.add after successful context fetch
- Add DEBUG-gated error logging to network helpers
- Guard config input.agent/mcp/provider against non-object types
- Fix MCP badge count 44→51 in plugin README

* fix: add language identifier to fenced code block in plugin README

* fix: store OpenCode session title as summary/firstPrompt on creation

* fix(plugin): add session instruction injection and consolidation pipeline (closes #233)

Three gaps from the Claude Code plugin port sweep:

- Inject agentmemory usage instructions (memory_save, memory_recall, etc.) into the system prompt on first turn via experimental.chat.system.transform, replacing the skills mechanism that OpenCode lacks
- Call /crystals/auto and /consolidate-pipeline on session.deleted, mirroring Claude's CONSOLIDATION_ENABLED behavior
- Document MEMORY.md vs AGENTS.md architecture comparison (two-hop file bridge vs one-hop direct injection)

Gap A (SubagentStop) is unfixable — OpenCode's SubtaskPart type has no completion/result fields.
Gap C (Claude MEMORY.md bridge) is intentionally skipped — OpenCode uses direct injection.

* fix(plugin): address adversarial code review — 7 critical/high fixes

- Guard instructions push with Array.isArray check + fix TOCTOU race
  by moving contextInjectedSessions.add() before the await
- Session-scope stashedFiles via stashFor() helper (was process-global,
  could cross-contaminate concurrent sessions)
- Fix tool prefix in instructions (agentmemory_memory_ not agentmemory_)
- Check typeof string before pushing .context into system arrays
- Change olderThanDays: 0 → 7 in consolidation fire-and-forget
- Increase fire-and-forget timeout from 5s to 30s (consolidation
  takes minutes, 5s was guaranteed to abort)

* fix(plugin): scope stashedFiles.delete(sid) not .clear() on session.deleted

* fix(plugin): address round 2 adversarial review — 3 critical fixes

- Fix session cross-contamination: file.edited and tool.execute.before
  now use props.sessionID/input.sessionID fallback instead of only
  activeSessionId, preventing subagent hijack of parent stash
- Fix contextInjectedSessions regression from round 1: move add(sid)
  to after instructions push (synchronous) but before context fetch
  (async), so failed /context calls don't permanently skip injection
- Fix Map memory leak: prune session entries (stashedFiles,
  seenSubtaskIds, seenToolCallIds) when session.status goes idle,
  preventing unbounded growth for crash-killed sessions

* fix(plugin): address round 2 remaining medium issues

- Enforce MAX_STASHED_FILES cap on chat.message (was missing, could
  grow unbounded from 50+ file-ref messages)
- Remove activeSessionId fallback from session.deleted; log warning
  when both info.id and sessionID are missing instead of guessing
- Add subtaskSetFor/toolCallSetFor lazy-init wrappers matching
  stashFor pattern; fixes dedup failures for subagents spawned
  without a preceding session.created event
- Guard chat.params against missing input.model (TypeError crash)

* fix(plugin): address round 3 adversarial review — 10 HIGH/MEDIUM fixes

- Add safeSlice() helper replacing all unsafe (v as string || "").slice()
  calls; handles objects/BigInt/circular refs via try/catch JSON.stringify
- Restore activeSessionId fallback on session.deleted (if both info.id
  and sessionID missing, fall back like every other handler)
- Fix message.updated to check info.id before info.sessionID (matches
  session.created/session.updated pattern)
- Add props.sessionID to message.part.updated fallback chain
- Guard undefined callID and subtask ID (prevent dedup silently
  dropping all subsequent undefined-ID events)
- Cap todo.updated at 100 entries (match session.diff's slice pattern)
- Fix duration_ms || → ?? (0ms genuine duration no longer falsifies)
- Fix extractErrorMessage || → ?? (0/false error values preserved)
- Replace extractErrorMessage.slice with safeSlice in retry handler

* fix(plugin): address round 4 adversarial review — 6 CRITICAL/HIGH fixes

CRITICAL:
- Revert message.updated session ID resolution: info.id is the message
  UUID, not session ID. In message.updated, info is the message object
  (has role/tokens/modelID), not session info like in session.created.
  Use props.sessionID || info.sessionID || activeSessionId instead.
- Fix post_tool_use_failure -> post_tool_failure: server types.ts
  defines "post_tool_failure" (no "use_"); compress-synthetic.ts
  classifies by that exact string. All error observations were
  misclassified as "other" instead of "error".
- Fix contextInjected ordering: move add(sid) after the context fetch
  completes, not before. If /context times out, session is no longer
  permanently marked injected, allowing retry on next transform.

HIGH:
- Add process.cwd() fallback for projectPath (was null, causing
  silent 400s on every REST call when no workspace)
- Guard session.start against null activeSessionId with early return
  (was sending sessionId: null to API)
- Fix duration_ms: use typeof number checks instead of || 0 defaults;
  missing timing data now correctly reports null instead of 0ms

* fix(plugin): address final adversarial blockers

---------

Co-authored-by: xuli500177 <62830942+xuli500177@users.noreply.github.com>
Co-authored-by: Trip <5579540+cl0ckt0wer@users.noreply.github.com>
Co-authored-by: Rohit Ghumare <ghumare64@gmail.com>
2026-05-19 00:22:42 +01:00