Make your opencode agent actually use agentmemory.
Companion plugin to agentmemory-capture.ts. Capture already works —
this plugin forces the agent to write to memory proactively.
agentmemory ships 54 MCP tools and a rock-solid auto-capture plugin
(agentmemory-capture.ts). Sessions get recorded. Semantic memories get
generated. Insights get extracted. The read side works.
But the write side stalls:
- The agent rarely calls
memory_saveon its own - Pinned slots (
persona,project_context,user_preferences,tool_guidelines) sit empty for weeks memory_lesson_saveis almost never invokedmemory_crystallizenever fires- Users type "remember this" and the agent says "ok" without saving
You can write a rules/memory.md policy file, but the LLM may ignore it.
This plugin turns that policy into a per-turn directive the model
cannot miss — same pattern as caveman-mode reinforcement, applied to
memory hygiene.
| Metric (per session) | Without oh-my-agentmemory | With oh-my-agentmemory |
|---|---|---|
| Pinned slots filled | 0 of 4 | 4 of 4 (auto-bootstrap on session.created) |
memory_save calls |
0 | 1–3 (directive reinforced every turn) |
memory_lesson_save calls |
0 | 2–5 (auto-captured from file history) |
memory_crystallize calls |
0 | occasional (suggested when ≥3 actions done) |
| "remember this" actually saved | ~30% | ~90% (keyword detection → directive) |
Numbers are illustrative — actual lift depends on session shape. The
learning hook (auto-lesson) is the largest contributor; the
enforcement directive is the largest enabler of explicit memory_save
calls.
Five hooks, each owning one slice of the write side. Each has a short
purpose name used in OH_AM_DISABLE and a corresponding opencode
hook. All coexist with agentmemory-capture.ts (which keeps doing
passive observation).
flowchart LR
subgraph OC[opencode events]
SC[session.created]
CM[chat.message]
ST[system.transform<br/>every LLM turn]
SI[session.status idle]
FE[file.edited]
end
subgraph OH[oh-my-agentmemory]
INIT[init<br/>bootstrap empty slots]
INTENT[intent<br/>keyword detection]
ENF[enforcement<br/>per-turn directive]
ARCH[archive<br/>crystal suggestion]
LEARN[learning<br/>auto-lesson from history]
end
subgraph AM[agentmemory HTTP API]
SLOTS[slot / replace]
OBSERVE[observe]
LESSON[lesson / save]
end
SC --> INIT --> SLOTS
CM --> INTENT --> OBSERVE
ST --> ENF
SI --> ARCH --> OBSERVE
FE --> LEARN --> LESSON
INTENT -.queued intent.-> ENF
ARCH -.flag.-> ENF
INIT -.cache invalidate.-> ENF
| Hook (opencode event) | Purpose | What it does |
|---|---|---|
experimental.chat.system.transform |
enforcement | Pushes a policy directive into output.system[] every turn. Header + recall rules + write rules + crystal rules + state flags (empty slots, pending keywords, done-action count). Cached per-session to keep the hot path HTTP-free. |
event: session.created |
init | Lists slots, finds empties among the four core pinned slots, fills them with templates derived from cwd (project map). Logs a oh_am_bootstrap observation. |
chat.message |
intent | Matches user text against bilingual patterns: "remember", "save this", "don't forget", "기억해", "저장해", "잊어". Queues matches for the next directive. |
event: session.status (idle) |
archive | When ≥3 actions are done, records a oh_am_crystal_candidate observation. The next directive surfaces the candidate IDs to the LLM. |
event: file.edited |
learning | Fetches the file's history, looks for error signals (error, fail, bug, 에러, 실패, …), skips if edit is tiny or no error pattern, dedupes against lesson_recall, then calls lesson/save. 5-min per-file history cache + 60-second per-file debounce. |
AGENTMEMORY POLICY ACTIVE. Use agentmemory MCP tools proactively.
Rules below override default behavior.
---
## Recall
• Need past context (recent work, decisions, prior bugs)? Call memory_recall
or memory_smart_search BEFORE exploring files.
• Pinned slots (persona, project_context, user_preferences, tool_guidelines,
pending_items, guidance) are auto-injected every turn — do NOT re-recall them.
• Trivial tasks (single-file read, arithmetic, grep) skip memory calls.
## Write
• After architectural or non-obvious technical decisions, call memory_save with
the decision and concepts.
• Discovered an effective or ineffective approach? Call memory_lesson_save with
what worked / what to avoid.
• Project structure / build pipeline / new module added? Call memory_slot_replace
on project_context.
• Unfinished work or follow-up promised? Call memory_slot_replace on pending_items.
• Session about to end with loose ends? Call memory_slot_replace on guidance for
the next session.
• Never claim 'memory updated' without an actual memory_* tool call in this turn.
## Crystal
• Three or more actions with status=done? Call memory_recall first to check for
an existing crystal, then memory_crystallize with the done action IDs.
• Fewer than three done actions, or pure exploration with zero file changes →
skip crystallize.
[STATE] Pinned slots empty: project_context, user_preferences. If session.created
bootstrap missed them, fill via memory_slot_replace before session ends.
[USER INTENT] User said: save:"remember this". Act on it via the matching memory_*
tool this turn.
---
Pinned slots are already in your context — do not re-recall them. Calling memory_*
without an actual tool call in this turn = false report (forbidden).
Edit src/core/policy.ts to retune any rule. The directive is rebuilt
automatically from data — no string-surgery required.
- opencode
≥1.14(provides theexperimental.chat.system.transformhook) - agentmemory server running on
http://localhost:3111:npx @agentmemory/agentmemory
- Bun for plugin runtime + dev tooling
git clone https://github.com/dev-hann/oh-my-agentmemory.git ~/Documents/oh-my-agentmemory
cd ~/Documents/oh-my-agentmemory
bun installln -sfn ~/Documents/oh-my-agentmemory/src/adapters/opencode \
~/.config/opencode/plugins/oh-my-agentmemoryEdit ~/.config/opencode/opencode.json:
{
"plugin": [
"./plugins/agentmemory-capture.ts",
"./plugins/oh-my-agentmemory/plugin.ts"
]
}Keep agentmemory-capture.ts — this plugin is write-side only and
depends on capture.ts continuing to observe.
for f in am-recall am-save am-bootstrap am-status; do
ln -sfn ~/Documents/oh-my-agentmemory/src/adapters/opencode/commands/${f}.md \
~/.config/opencode/commands/${f}.md
doneDefaults work out of the box (localhost agentmemory, auto mode). For
persistent settings — remote server, custom project map,
health-check tuning — create ~/.config/opencode/oh-am.jsonc:
cp ~/Documents/oh-my-agentmemory/examples/oh-am.full.jsonc \
~/.config/opencode/oh-am.jsonc
# then edit to taste — every field is optionalSee Configuration for the full schema.
Verify with /am-status — it should report pinned slots filled.
| Command | Action |
|---|---|
/am-recall <query> |
Search past observations + lessons via memory_recall |
/am-save <text> |
Save an insight to long-term memory via memory_save |
/am-bootstrap |
Force re-bootstrap empty slots right now (use when cwd project detection was wrong) |
/am-status |
Show slot fill state + recent sessions + latest lessons + crystal candidates |
Three layers, in descending precedence:
- Process env vars (override everything for one-shot runs / CI)
~/.config/opencode/oh-am.jsonc(persistent settings, JSONC = comments allowed)- Built-in defaults (see
src/core/config-types.ts)
| Var | Default | Effect |
|---|---|---|
AGENTMEMORY_URL |
http://localhost:3111 |
agentmemory server base URL |
AGENTMEMORY_SECRET |
"" |
Bearer token if auth enabled on server |
OH_AM_MODE |
auto |
auto | full | mcp-only |
OH_AM_DISABLE |
"" |
Comma-list of purpose names to disable: enforcement, init, intent, archive, learning |
OH_AM_DEBUG |
0 |
Set to 1 for verbose stderr logging |
Example: OH_AM_DEBUG=1 OH_AM_DISABLE=learning opencode
Create ~/.config/opencode/oh-am.jsonc:
Full reference with every field documented: examples/oh-am.full.jsonc.
Set "mode": "mcp-only" (or OH_AM_MODE=mcp-only) when running without
agentmemory-capture.ts — e.g. on Cursor, Claude Desktop, or a second
machine that only consumes a shared agentmemory instance.
In this mode:
learningandarchivehooks skip entirely (their data sources — file history, done actions — are empty without capture.ts)enforcementdirective gains a stronger banner warning the LLM that no auto-capture is running, and the write responsibilities shift to it entirely
Auto-detection ("mode": "auto") probes the agentmemory server on first
session.created; if recent sessions average <5 observations each it
switches to mcp-only. Set explicitly to skip the probe.
By default, the plugin pings ${url}/agentmemory/health on init. If
unreachable:
- Default (
healthCheckFatal: false): logs a warning, continues running (hooks will silently fail their HTTP calls) healthCheckFatal: true: plugin returns no hooks, effectively disabled
Hexagonal (ports & adapters). The core/ layer is pure TypeScript with
zero I/O — easy to unit-test, easy to port to other agents later.
oh-my-agentmemory/
├── src/
│ ├── core/ # agent-agnostic, pure TS, no I/O
│ │ ├── directives.ts # buildDirective(ctx) → string
│ │ ├── bootstrap.ts # SLOT_TEMPLATES + detectProject(cwd)
│ │ ├── keywords.ts # pattern loader + matchKeywords()
│ │ ├── lessons.ts # buildLessonFromFileHistory() → LessonCandidate
│ │ ├── policy.ts # rules/memory.md encoded as data
│ │ ├── config-types.ts # OhAmConfig + ResolvedConfig types
│ │ └── types.ts # shared types
│ │
│ ├── data/ # locale-keyed keyword / signal data
│ │ ├── keywords.ts # save / forget / recall patterns per locale
│ │ └── error-signals.ts # error keywords per locale (lessons filter)
│ │
│ └── adapters/
│ └── opencode/ # current; claude-code/codex later
│ ├── plugin.ts # single entry, registers all hooks
│ ├── config.ts # JSONC parser + env/file/default merge
│ ├── mode.ts # auto-detect full vs mcp-only
│ ├── client.ts # agentmemory HTTP wrapper
│ ├── hooks/
│ │ ├── _shared.ts # isPhaseDisabled / isMcpOnly helpers
│ │ ├── system-transform.ts # enforcement
│ │ ├── session-created.ts # init
│ │ ├── chat-message.ts # intent
│ │ ├── session-idle.ts # archive
│ │ └── file-edited.ts # learning
│ └── commands/
│ ├── am-recall.md
│ ├── am-save.md
│ ├── am-bootstrap.md
│ └── am-status.md
│
├── examples/
│ └── oh-am.full.jsonc # complete config reference
│
└── tests/
├── core/ # unit tests (no network)
│ ├── directives.test.ts
│ ├── keywords.test.ts
│ ├── bootstrap.test.ts
│ └── mcp-only.test.ts
└── adapters/
└── config.test.ts # JSONC parse + mergeConfig
adapters/claude-code/ and adapters/codex/ will reuse core/
unchanged — only the hook glue differs. The hexagonal split keeps the
porting cost down to "write one adapter file per agent."
bun install
bun run test # vitest, 62 tests, ~200ms
bun run typecheck # tsc --noEmit, strict modeAll tests target core/ and the JSONC config layer — pure functions,
deterministic, no network. Adapter behavior is verified manually against
a running agentmemory server.
| Built-in (CLAUDE.md / rules/) | agentmemory-capture.ts |
oh-my-agentmemory | |
|---|---|---|---|
| Layer | Static policy file | Plugin (read side) | Plugin (write side) |
| Captures observations | No | Yes (22+ hooks) | No (capture.ts does it) |
Forces LLM to call memory_save |
Honor system | No | Yes (per-turn directive) |
| Fills empty slots | No | No | Yes (cwd-based bootstrap) |
| Reacts to "remember" / "기억해" | No | No | Yes (keyword detection) |
| Auto-saves lessons from bug history | No | No | Yes (file.edited hook) |
Suggests memory_crystallize |
No | No | Yes (idle + done ≥3) |
| Config file | n/a | env vars only | oh-am.jsonc (JSONC) + env vars |
| Profiles (multi-instance switch) | n/a | No | No |
| MCP-only mode branching | n/a | No | Yes (auto-detect or explicit) |
| Health check on init | n/a | No | Yes (optional self-disable) |
| Cloud dependency | None | None | None |
| Cost | $0 | $0 | $0 |
This plugin is complementary, not competitive, with capture.ts. Disable either one and you lose half the loop.
opencode-supermemory is the right choice if you want cloud-hosted memory, Notion/Drive connectors, auto user-profiles, and a one-line install.
oh-my-agentmemory is the right choice if you:
- Already run agentmemory locally and have invested in 50+ sessions of data
- Want to keep everything self-hosted (no cloud, no API keys)
- Need agentmemory's 54-MCP-tool surface (slots, lessons, crystals, actions, insights, consolidation pipeline) rather than supermemory's 3
- Prefer "directive reinforcement" over "automatic API-side extraction"
Both plugins can coexist — they push to different memory systems.
Directive doesn't appear in the system prompt
- Confirm plugin loaded: check opencode logs for
[oh-am] plugin loaded(withOH_AM_DEBUG=1ordebug: truein config) - Confirm
opencode.jsonhas both entries (capture.ts AND oh-my-am/plugin.ts) - Confirm symlink target exists:
ls -la ~/.config/opencode/plugins/oh-my-agentmemory/plugin.ts - Confirm agentmemory server is up:
curl http://localhost:3111/agentmemory/health— or set"healthCheckOnBoot": true, "healthCheckFatal": falseto see the warning on plugin init - Confirm
enforcementis not disabled viaOH_AM_DISABLE=enforcementenv var or"disabled": ["enforcement"]inoh-am.jsonc
Slots stay empty after session.created
- Run
/am-bootstrapto force re-bootstrap and see proposed content - Check stderr with
OH_AM_DEBUG=1(or"debug": truein config) for[oh-am] bootstrap filled N/N slots - If detection picks the wrong project, either:
- Add your cwd to
PROJECT_MAPinsrc/core/bootstrap.ts, or - Add an entry to
projectMapin~/.config/opencode/oh-am.jsonc(no code edit needed)
- Add your cwd to
- The
inithook may be disabled viaOH_AM_DISABLE=initenv var or"disabled": ["init"]in config
Too many lessons being saved (learning noise)
The learning hook is conservative by default — it requires both an error signal in
file history AND a meaningful edit size. If still too noisy:
- Disable temporarily:
- Env:
OH_AM_DISABLE=learning opencode - Config:
"disabled": ["learning"]inoh-am.jsonc
- Env:
- Tune filters in
src/core/lessons.ts:- Raise
MIN_EDIT_LINES(default 5) - Add exclude patterns to skip test files or generated code
- Edit
src/data/error-signals.tsto expand or narrow error keywords
- Raise
Directive is too verbose / hurts token budget
The directive body is ~600 tokens. To shrink:
- Edit
src/core/policy.tsto shorten rule texts (or drop sections by removing entries fromRECALL_POLICY/WRITE_POLICY/CRYSTAL_POLICY) - Or use compact mode by editing
system-transform.tsto callbuildDirective(ctx, { compact: true })— this drops the rule bodies and keeps only state/keyword lines
Conflict with caveman or other plugins
opencode runs all plugins' hooks in sequence. Multiple plugins can push
to output.system[] without conflict — caveman pushes its reinforcement
line, oh-my-am pushes its directive, both reach the LLM.
git clone https://github.com/dev-hann/oh-my-agentmemory.git
cd oh-my-agentmemory
bun install
bun run test # 35 unit tests
bun run typecheck # strict TSThe core/ layer has no I/O — every function is testable in isolation.
Adapter tests require a running agentmemory server.
- Skill layer —
using-agentmemoryopencode Skill (auto-loaded by opencode when relevant, stronger than directive) - Claude Code adapter —
adapters/claude-code/(.claude/settings.jsonhook scripts that invokecore/) - Codex adapter —
adapters/codex/(Codex hook format) - npm publish —
bunx oh-my-agentmemory install --agent XCLI installer
Contributions welcome. Open an issue first to discuss scope.
# Remove from opencode.json plugin[]
# Remove symlinks
rm ~/.config/opencode/plugins/oh-my-agentmemory
rm ~/.config/opencode/commands/am-{recall,save,bootstrap,status}.md
# Optionally remove the source tree
rm -rf ~/Documents/oh-my-agentmemoryYour agentmemory data is untouched — only the directive plugin is removed.
MIT © dev-hann
- agentmemory — the memory engine this plugin drives
- agentmemory-capture.ts — the canonical observer plugin this complements
- opencode-supermemory — reference implementation for keyword detection and reasoned-recall directive patterns
- caveman — reference implementation for per-turn system.transform reinforcement
{ // agentmemory server "url": "http://localhost:3111", "secret": "", // operating mode: "auto" | "full" | "mcp-only" "mode": "auto", // purpose names to disable "disabled": ["learning"], // extend built-in project map without code edit (always prepended) "projectMap": [ { "match": "my-new-project", "projectId": "mnp", "displayName": "My New Project", "stack": ["Go", "Postgres"] } ], // health check on plugin init "healthCheckOnBoot": true, "healthCheckTimeoutMs": 2000, "healthCheckFatal": false, // verbose stderr logging "debug": false }