* 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
agentmemory for OpenCode
Your OpenCode agents remember everything. No more re-explaining.
Persistent cross-session memory via agentmemory — 95.2% retrieval accuracy on LongMemEval-S.
Quick start
1. Start the agentmemory server
npx @agentmemory/agentmemory
The server starts on http://localhost:3111.
2. Configure the MCP server
Add to ~/.config/opencode/opencode.json or your project's .opencode/opencode.json:
{
"mcp": {
"agentmemory": {
"type": "local",
"command": ["npx", "-y", "@agentmemory/mcp"],
"enabled": true
}
}
}
3. Install the plugin
Add to ~/.config/opencode/opencode.json:
{
"plugin": ["./plugins/agentmemory-capture.ts"]
}
Copy the plugin file from this repo:
mkdir -p ~/.config/opencode/plugins
cp plugin/opencode/agentmemory-capture.ts ~/.config/opencode/plugins/
4. Add the slash commands
Copy the commands into your project or global .opencode/commands/ directory:
mkdir -p ~/.config/opencode/commands
cp plugin/opencode/commands/recall.md ~/.config/opencode/commands/
cp plugin/opencode/commands/remember.md ~/.config/opencode/commands/
Restart OpenCode or open a new session. The plugin auto-captures everything.
What gets captured
Session lifecycle
| Event | Hook | agentmemory API |
|---|---|---|
| Session start | session.created |
POST /session/start |
| Idle → summarize | session.idle + session.status (idle) |
POST /summarize |
| Status transitions | session.status (idle/busy/retry) |
POST /observe |
| Compaction | session.compacted |
POST /summarize + POST /observe |
| Metadata updates | session.updated |
POST /observe |
| Code change tracking | session.diff |
POST /observe |
| Session delete | session.deleted |
POST /session/end |
| Session error | session.error |
POST /observe |
Messages & prompts
| Event | Hook | agentmemory API |
|---|---|---|
| User prompt (rich) | chat.message |
POST /observe |
| User prompt metadata | message.updated (user) |
POST /observe |
| Assistant response | message.updated (assistant) |
POST /observe |
| Message removed (undo) | message.removed |
POST /observe |
Parts & steps
| Event | Hook | agentmemory API |
|---|---|---|
| Subagent start | message.part.updated (subtask) |
POST /observe |
| Tool completed | message.part.updated (tool completed) |
POST /observe |
| Tool error | message.part.updated (tool error) |
POST /observe |
| Step finish (cost/tokens) | message.part.updated (step-finish) |
POST /observe |
| Reasoning trace | message.part.updated (reasoning) |
POST /observe |
| Patch applied | message.part.updated (patch) |
POST /observe |
| Auto/manual compaction | message.part.updated (compaction) |
POST /observe |
| Agent selection | message.part.updated (agent) |
POST /observe |
| API retry | message.part.updated (retry) |
POST /observe |
File enrichment pipeline
| Event | Hook | agentmemory API |
|---|---|---|
| File tool params | tool.execute.before → stash paths |
— |
| File edited | file.edited → stash paths |
— |
| File part attached | message.part.updated (file) → stash paths |
— |
| Enrichment inject | experimental.chat.system.transform |
POST /enrich → output.system[] |
| Memory context inject | experimental.chat.system.transform |
POST /context → output.system[] |
Permissions
| Event | Hook | agentmemory API |
|---|---|---|
| Permission prompt | permission.updated |
POST /observe |
| Permission reply | permission.replied |
POST /observe |
Tasks & commands
| Event | Hook | agentmemory API |
|---|---|---|
| Task tracking (w/ priority) | todo.updated |
POST /observe |
| Command executed | command.executed |
POST /observe |
Model & config
| Event | Hook | agentmemory API |
|---|---|---|
| LLM parameters | chat.params |
POST /observe |
| Config loaded | config |
POST /observe |
| Compaction (WIP) | experimental.session.compacting |
POST /context → output.context[] |
File enrichment + memory injection (two-layer pipeline)
experimental.chat.system.transform fires before every LLM call and injects two layers of context:
-
Memory context (once per session): calls
/agentmemory/contextand injects project profile, recent session summaries, and important past observations into the system prompt. This is the OpenCode equivalent of Claude's MEMORY.md bridge — instead of syncing to a markdown file, context is injected directly into the system prompt. -
File enrichment (every turn with stashed files): calls
/agentmemory/enrichwith files stashed bytool.execute.before,file.edited, andmessage.part.updated(file parts). File-specific context (past observations, related bugs, semantic search) is injected into the system prompt.
System prompt = [OpenCode instructions] + [memory context] + [file enrichment] + [user message]
^ ^
first turn only every file-touching turn
Differences from Claude's PreToolUse:
| Dimension | Claude (PreToolUse) | OpenCode (two-hop pipeline) |
|---|---|---|
| Injection mechanism | stdout → context window | output.system[] → system prompt |
| Timing | Same turn (parallel with tool) | Next turn (before next LLM call) |
| File set | Per-tool (immediate) | Batched (all files since last enrichment) |
| Coverage | Edit/Write/Read/Glob/Grep only | Edit/Write/Read/Glob/Grep only |
| What gets injected | <agentmemory-file-context> + bug memories |
Identical /enrich response |
MEMORY.md vs AGENTS.md: how context flows
Claude Code and OpenCode take fundamentally different approaches to injecting memory context into the agent's system prompt.
Claude Code: file-backed bridge (two-hop)
agentmemory ──write──▶ MEMORY.md ──read──▶ Claude system prompt
- The
claude-bridge/syncendpoint serializes agentmemory observations into aMEMORY.mdfile in the project root - Claude Code reads
MEMORY.mdon session start and prepends it to the system prompt - Sync is periodic — sessions only get fresh context when the bridge last ran (session end, pre-compact)
- Coupling: memory data lives in a git-trackable file, visible to CI, team members, and other tools
OpenCode: direct injection (one-hop)
agentmemory ──push──▶ OpenCode system prompt
experimental.chat.system.transformcalls/contextat runtime and pushes the response directly intooutput.system[]- Always current — context is fetched at session start (once) and before file-touching turns (per-batch)
- No file intermediary — no stale copies, no merge conflicts, no disk I/O
AGENTS.mdis a static instruction file for project conventions, coding standards, and tool guidance — agentmemory does not read or write it
Tradeoffs
| Dimension | Claude (MEMORY.md bridge) | OpenCode (direct injection) |
|---|---|---|
| Freshness | Stale between syncs | Always current (fetched at call time) |
| Visibility | Human-readable file in repo | In-memory injection only |
| Simplicity | Two moving parts (bridge + file) | One step (API → system prompt) |
| Team sharing | File is git-trackable, CI-friendly | Memory shared via agentmemory server API |
| Integration | Any tool can read MEMORY.md | Requires OpenCode plugin SDK |
Why OpenCode goes direct
agentmemory already persists everything in SQLite (data/state_store.db). Adding an intermediate MEMORY.md file would duplicate data, introduce sync lag, and require the model to re-parse structured context from markdown. Direct injection delivers the same data with lower latency and zero staleness — the agent always sees what agentmemory knows right now.
Slash commands
/recall <query>— Search past observations and lessons/remember <text>— Save an insight to long-term memory
Session instruction injection
Agentmemory usage instructions are injected into the system prompt on the first turn of every session via experimental.chat.system.transform (alongside memory context from /context). This is functionally equivalent to Claude Code's skills mechanism — the agent learns which agentmemory_memory_* tools to use and when, without needing separate skill invocations.
What's not covered (vs Claude Code plugin)
| Claude feature | Reason |
|---|---|
| SubagentStop | OpenCode's SubtaskPart type has no completion/result fields; subtask lifecycle ends are not exposed as distinct events in the OpenCode SDK |
| TaskCompleted | No team/teammate concept in OpenCode; todo.updated captures task state changes as a partial equivalent |
| Stop | session.compacted event handler exists; experimental.session.compacting injection hook defined in SDK but Go binary (v1.14.41) doesn't wire it — will auto-activate when upstream implements it |
| Skills (remember/recall/forget/session-history) | Covered by injected system instructions via experimental.chat.system.transform — agent receives usage guidance on first turn |
| Consolidation pipeline (crystals/auto + consolidate-pipeline) | Now called on session.deleted — mirrors Claude's CONSOLIDATION_ENABLED=true behavior |
| Claude MEMORY.md bridge | OpenCode-specific; OpenCode uses its own AGENTS.md mechanism, not Claude's MEMORY.md |
All other Claude Code hooks have direct or pipeline equivalents in this plugin. 12 of 12 Claude hook types covered.