Findings from an independent review of this branch. The exit-time flush was gated by its own cooldown. beforeExit called the same flush() that opens with a retryNotBefore check, so after any failed delivery a process exiting inside the 2s to 60s window sent nothing and the queue died with it. That is precisely the loss this branch exists to stop, and timer.unref makes beforeExit often the only remaining chance. flush(force) now skips the cooldown and beforeExit passes it. Overflow kept the newest and evicted the batch being retried, which threw away exactly the events the retry exists to save. Both the failure path and capture() now keep the backlog and drop the new event instead, matching Python's record(), which refuses new events once the spool is full. That change needs a bound, so delivery now gives up after five attempts, as the Python core does. Without one a payload the server will never accept would be retried for the whole session and, with the backlog now preferred, would hold the queue against everything behind it. Events carry a capture-time timestamp. They now sit through backoff and across an entire outage, so without one PostHog records them at whatever moment delivery happened to succeed. It also matters for the uuid dedupe, whose key includes the event date. openclaw wiped a resolved account on any capture without an apiKey: the fingerprint comparison was `undefined === ""`, so a keyless call looked like a key change. Guarded on a real key being present. Masked today because every call site supplies one, which is why only a test found it. Two smaller ones. opencode's PostHog source was "plugin", which named no particular plugin and matched no vocabulary; it is now OPENCODE_PLUGIN like every other surface, and a saved insight filtering source = "plugin" needs repointing. And an em dash in opencode's published description had been rewritten to a — escape by my own json.dumps when adding the test script; restored. One openclaw test asserted only not.toThrow() under a name claiming it checked the identity, and under the fingerprint gate the path it exercised no longer uses the email at all. It now pins the real condition. core 35, openclaw 432, opencode 35, pi-agent 89, deepseek 47. The wire e2e still passes 9 of 9 against a real local server, and the identity e2e 7 of 7. Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
@mem0/opencode-plugin
Persistent memory for OpenCode. Your agent remembers decisions, preferences, and learnings across sessions automatically.
Current package version: 0.3.0. This native TypeScript integration keeps its own tools and scopes while sharing redaction and lifecycle utilities with agent-plugin-core.
Sidekick is available only in the Claude Code plugin.
Install
opencode plugin @mem0/opencode-plugin
This adds the plugin to your ~/.config/opencode/opencode.json. The plugin registers its memory tools and skills. No MCP server configuration is needed.
Or let your agent do it: paste this into OpenCode:
Install @mem0/opencode-plugin by following https://raw.githubusercontent.com/mem0ai/mem0/main/integrations/opencode-plugin/README.md
Get your API key (free): app.mem0.ai/dashboard/api-keys
echo 'export MEM0_API_KEY="m0-your-key"' >> ~/.zshrc && source ~/.zshrc
Restart OpenCode.
What's included
| Component | Description |
|---|---|
| 10 Native Memory Tools | add_memory, search_memories, get_memories, update_memory, delete_memory, and more, backed by the mem0ai SDK |
| Lifecycle Hooks | Auto-search on session start and every prompt, error memory lookup, compaction context, secret redaction |
| 7 Skills | /mem0-remember, /mem0-tour, /mem0-search, /mem0-status, /mem0-scope, /mem0-forget, /mem0-context-loader. Discovered through OpenCode's skills.paths |
Hooks
Written in TypeScript. Memory operations are native OpenCode tools backed by the mem0ai SDK directly.
| Hook | Event | What it does |
|---|---|---|
| Config | config |
Registers the /mem0-* slash commands (via config.command) and adds the plugin's own opencode-skills/ dir to OpenCode's skills.paths for skill discovery without copying files |
| Chat message | chat.message |
Loads prior memories on session start, searches relevant memories before each prompt, auto-captures learnings periodically |
| Pre-tool | tool.execute.before |
Blocks MEMORY.md writes, steering them to the add_memory tool |
| Post-tool | tool.execute.after |
Scans bash errors and pre-fetches related memories |
| Messages transform | experimental.chat.messages.transform |
Injects memory context (session memories, search results, error lookups) into the prompt |
| Compaction | experimental.session.compacting |
Stores session state memory, then injects prior memories into compaction context |
| Shell env | shell.env |
Exports MEM0_USER_ID, MEM0_APP_ID, MEM0_SESSION_ID, and MEM0_BRANCH to shell |
Memory Tools
| Tool | Description |
|---|---|
add_memory |
Save text or conversation history |
search_memories |
Semantic search across memories |
get_memories |
List memories with filters and pagination |
get_memory |
Retrieve a specific memory by ID |
update_memory |
Overwrite a memory's text by ID |
delete_memory |
Delete a single memory by ID |
delete_all_memories |
Bulk delete all memories in scope |
delete_entities |
Delete an entity and its memories |
list_entities |
List users/agents/apps stored in Mem0 |
get_event_status |
Check the processing status of an asynchronous memory event |
Memory scope
add_memory, search_memories, get_memories, and delete_all_memories accept an optional scope. You can set the default
scope (used when none is passed) with the /mem0-scope skill:
| Scope | Reads | Writes |
|---|---|---|
project (default) |
this repo (user_id + app_id) |
this repo |
session |
this run (adds run_id) |
this run |
global |
all your projects (filtered by your user ID) | user-wide (drops app_id) |
/mem0-scope # show the current default scope
/mem0-scope global # save & search across all your projects by default
/mem0-scope project # back to repo-only (default)
The default persists in ~/.mem0/settings.json (default_scope) and is read
fresh on each memory operation, so changes apply without a restart.
delete_all_memories always requires an explicit scope="global" to delete
user-wide, so changing the default can't trigger a cross-project wipe.
Capture and session context
Automatic capture saves every third qualifying user prompt. Other exchanges and assistant conclusions can be saved through add_memory or the remember skill; this is not a complete transcript recorder. Captured and explicitly saved text is redacted without the former 6,000-character cutoff.
Automatic capture uses the user and repository IDs, with the session ID in metadata. Explicit session-scope writes and searches use the top-level run_id filter. A session-scoped search therefore does not automatically include project memories that only carry metadata.session_id.
These project/session/global scopes are specific to this integration, not the Python plugins' repo/dir/mine scopes. Global tool access requires the user to enable it through /mem0-scope global or plugin settings first.
Verify
Start OpenCode and ask: "Search my memories for recent decisions"
If the mem0 tools respond, you're all set.
Troubleshooting
| Problem | Fix |
|---|---|
| No tools appearing | Restart OpenCode after installing |
| 401 Unauthorized | Check that MEM0_API_KEY is set to a valid key without printing it |
| Plugin not loading | Run opencode plugin @mem0/opencode-plugin again |
License
Apache-2.0