* 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>
10 KiB
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.