OpenCode plugin that makes the code-memory backend ambient:
auto-learns by re-indexing files the agent writes / edits, nudges the
agent toward the index before it greps / reads the filesystem, and
records the session as an episode on idle.
The plugin is layered on top of the existing code-memory MCP server, which
remains available for the agent to call manually (codememory_retrieve,
codememory_record, codememory_reingest).
| Hook | Behavior |
|---|---|
chat.message (first message of a session) |
Kicks off a background code-memory ingest <cwd> (git delta) to catch edits made outside OpenCode since the last session. Also installs a launchd / systemd watcher so out-of-session edits keep the index fresh. |
chat.message |
Detects durable claim intent in the user message (preference / decision / rejection / ownership / location) and queues a one-shot nudge reminding the agent to call codememory_assert_claim. Clears the per-turn gate flag (see tool.execute.before). |
experimental.chat.system.transform |
Drains pending gate nudges and claim nudges into the system prompt at the start of the next turn. |
tool.execute.before (read/bash/grep/glob) |
First-tool gate: if no explicit codememory_* MCP call has fired this turn, logs a warning and queues a one-shot nudge to drop into the next turn's system prompt. Never blocks. |
tool.execute.after (write/edit/patch) |
(a) Fires code-memory reingest <path>. (b) Schedules a debounced code-memory resolve to re-point cross-file CALLS edges. |
tool.execute.after (codememory_* MCP) |
Marks the gate flag as satisfied so subsequent reads / shells in the same turn stay silent. |
event (session.idle) |
Records the session as an episode via code-memory record. |
All backend calls are best-effort. If code-memory is not on PATH, every
hook degrades to a benign no-op — your OpenCode session is never blocked.
-
The
code-memoryCLI on PATH. Recommended installs:# one-time install (writes a binary you can call without uvx) pipx install git+https://github.com/fmflurry/code-memory # or uv tool install git+https://github.com/fmflurry/code-memory
If you only have
uvx, point the plugin at the wrapper command instead (see "Custom binary" below). -
Running infra: FalkorDB + Qdrant + Ollama with
bge-m3. See the main README. -
The repo must have been ingested at least once:
code-memory ingest /path/to/repo
OpenCode auto-discovers plugins under ~/.config/opencode/plugins/ (global)
or <project>/.opencode/plugins/ (project-local). The bundled installer
symlinks this plugin into either location:
# global (default) — pick this for everyday use
./plugins/opencode/install.sh
# project-local (only for the current repo)
cd /path/to/repo
~/Workspace/code-memory/plugins/opencode/install.sh --project
# custom directory
./plugins/opencode/install.sh --target /some/other/dirThe installer creates two symlinks:
code-memory.ts→ the plugin entrycode-memory-lib/→ the helper modules
OpenCode loads .ts files at the top level of the plugin dir directly via
Bun — no build step needed. The code-memory-lib/ subdirectory is treated
as private support code (the same convention used by worktree.ts /
worktree/ in many setups).
Restart OpenCode after install.
The plugin handles the automatic path; the MCP server still exposes the manual tools so the agent can call them when it judges retrieval / recording useful on its own.
Today the plugin reads no config file. Knobs are inline constants in
src/code-memory.ts:
| Constant | Default | Purpose |
|---|---|---|
RESOLVER_DEBOUNCE_MS |
1.5 s | Quiet period after the last write before the resolver re-runs. |
WRITE_TOOLS |
write/edit/patch | Which tool names trigger auto-reingest + resolver scheduling. |
If code-memory isn't on PATH (for example, you only have uvx), shim it:
# put this in ~/.local/bin/code-memory and chmod +x
exec uvx --from git+https://github.com/fmflurry/code-memory code-memory "$@"skills/code-memory/SKILL.md documents the tools and when the agent
should call retrieve / record / reingest manually. Wire it into your
skills config the same way you wire any other Anthropic Agent Skill.
cd plugins/opencode
npm install
npm run typecheck # tsc --noEmitThe plugin is a single TypeScript module loaded by OpenCode's Bun runtime; there is no build step.
- A missing or broken backend never crashes the session.
- Trivial replies ("yes", "ok", "continue") do not trigger retrieval.
- A re-ingest after
writeis fire-and-forget; the agent's turn returns immediately. - Session episodes are written only on
session.idle, with the captured first user message andgit diffas the patch.
The whole product proposition collapses if the agent answers from a stale graph. Code-memory has two completely different states to keep current:
- Per-file state — symbol definitions, imports, and call expressions for a single source file. Cheap to rebuild from one file.
- Cross-file state — the resolved CALLS edges that point a caller at the real defined Symbol node (instead of a placeholder). Touching one file can invalidate edges in many others, so a single-file re-ingest is not enough on its own.
Both must move forward together, and edits can come from places the plugin
can't see (vim, IDE saves, git pull, git stash pop). Below is the full
matrix of failure modes and what the plugin does about each.
| Failure mode | Mitigation |
|---|---|
| Agent rewrites a file → that file's symbols are stale. | tool.execute.after fires code-memory reingest <path> — tree-sitter re-parses, the file's nodes + edges are dropped and re-inserted, and its Qdrant chunks are replaced. |
| Agent rewrites a file → callers in other files now point at deleted/renamed symbols. | After every write, the plugin schedules a debounced code-memory resolve. The resolver scans the whole graph and re-points placeholder name::X CALLS edges to the real Symbol nodes. |
| Agent does a 20-file refactor in 2 seconds → resolver would run 20 times back-to-back. | Resolver scheduling is debounced by RESOLVER_DEBOUNCE_MS (1.5 s). A new write resets the timer, so a burst of edits collapses to exactly one resolver run after the dust settles. |
File changes outside OpenCode between sessions (vim, IDE, git pull, git checkout). |
First chat.message of each session triggers a one-shot background code-memory ingest <cwd>. The ingest is git-aware and only re-walks files whose hash moved — and it re-runs the resolver. |
| Backend (FalkorDB / Qdrant / Ollama) is down. | All CLI calls are guarded by per-command timeouts and run fire-and-forget. Failure is logged to the OpenCode log channel and silently no-op'd; the agent's turn is never blocked. |
code-memory CLI is missing on PATH. |
createMemoryClient detects this once at plugin init and short-circuits every method. The plugin remains loaded but inert. |
| Agent never explicitly records what it did. | session.idle fires code-memory record with the first user message + cumulative git diff as the patch (verdict left blank). Future sessions can recall the episode even without manual record. |
- File deletions via the
writetool.reingeston a missing path skips cleanly, but the previous File node + DEFINES edges + chunks linger until the next git-aware delta ingest evicts them. - Renames look like delete + create to the plugin. The graph keeps the old node until the next delta ingest.
- Bare external module imports (
@scope/pkg,rxjs). The resolver does not chase npm dependencies, so callers into external packages stay unresolvable. This is an architectural choice, not a freshness bug. - Pure reads. If the agent only opens files, no hook fires. That's correct: nothing changed.
Two refresh loops keep the index honest; the agent retrieves on demand via MCP when it needs context:
session start every write
│ │
▼ ▼
delta-ingest reingest + debounced
+ resolver resolver (cross-file
(catches OOB edge accuracy)
edits)
If either layer fails, the next one eventually catches up. Stale data is a
temporary state, not a steady state. Context-pack delivery is the agent's
job — call codememory_retrieve when orientation is needed.
MIT — see LICENSE.
{ "mcp": { "code-memory": { "type": "local", "command": [ "uvx", "--from", "git+https://github.com/fmflurry/code-memory", "code-memory-mcp" ], "enabled": true, "environment": { "CODE_MEMORY_PROJECT": "auto" } } }, "plugin": [ "{env:HOME}/Workspace/code-memory/plugins/opencode/src/code-memory.ts" ] }