OpenCode plugin that gives your agent persistent memory across sessions and projects, backed by Memory Box.
Two memory scopes out of the box:
- User — preferences that follow you into every project (style, workflow, communication)
- Project — knowledge scoped to this codebase (config, architecture, patterns, known errors)
Both live in isolated Memory Box namespaces and use Memory Box's hybrid (BM25 + vector) search — so bun, pnpm, Dockerfile, and other exact tokens retrieve the way you'd expect them to in a code context.
On the first message of every session, your agent silently receives relevant memory as a synthetic part in its context — it never has to ask, you never have to remind it:
[MEMORYBOX]
User Preferences:
- Prefers concise responses, no trailing summaries
- Uses functional components, never classes
- Always feature branches — never commit to main
Project Knowledge:
- [92%] Uses Bun, not Node. Commands: bun install, bun run dev, bun test
- [85%] API routes in src/routes/, handlers in src/handlers/. Hono framework.
- [78%] Auth uses Redis sessions — src/lib/auth.ts. Not JWT.
(… 4 more; use `memorybox` tool to search)
On every message, the plugin watches for phrases like "remember", "save this", "don't forget" and nudges the agent to persist the memory via its tool:
You: Remember that this project uses pnpm workspaces, not yarn
Agent: memorybox(mode:"add", scope:"project", bucket:"project-config",
content:"Uses pnpm workspaces, not yarn")
→ saved
And the agent can read, write, and prune memory itself via the memorybox tool whenever it decides that's useful.
Memory Box is API-key auth'd with per-tenant keys in the form mbx_<tenant>_<secret>. If you operate your own Memory Box instance, issue yourself a key via the admin API. If you're using someone else's, ask them for one.
bunx opencode-memorybox@latest installThe installer:
- Adds
opencode-memoryboxto~/.config/opencode/opencode.jsonc - Creates the
/memorybox-initslash command - Prints instructions for configuring your API key
Pass --no-tui for non-interactive (LLM-driven) installs.
Either environment variable:
export MEMORY_BOX_API_KEY="mbx_..."Or ~/.config/opencode/memorybox.jsonc:
The env var wins if both are set.
You're done. On your first message of every new session, relevant memory will be silently pulled in. The memorybox tool is also now exposed to the agent.
┌─────────── one Memory Box API key ───────────┐
│ │
│ ┌─────────────────┐ ┌─────────────────┐│
│ │ User namespace │ │ Project namespc.││
│ │ │ │ ││
│ │ X-Agent-ID: │ │ X-Agent-ID: ││
│ │ opencode-user- │ │ opencode-proj- ││
│ │ <hash(email)> │ │ <hash(repo)> ││
│ │ │ │ ││
│ │ Cross-project. │ │ This project ││
│ │ Follows you. │ │ only. Isolated. ││
│ │ │ │ ││
│ │ buckets: │ │ buckets: ││
│ │ preferences │ │ project-config ││
│ │ style │ │ architecture ││
│ │ workflow │ │ error-solution ││
│ │ │ │ learned-pattern││
│ │ │ │ conversation ││
│ └─────────────────┘ └─────────────────┘│
└──────────────────────────────────────────────┘
Scopes map to Memory Box agent IDs. Agent IDs in Memory Box are independent Turbopuffer namespaces — hard data isolation, no cross-namespace leakage. Two scopes means two agent IDs means two namespaces.
Buckets categorize within a namespace. They're filterable metadata (?bucket=architecture), not another isolation layer. Pick any string matching ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ up to 100 chars — the ones in the diagram are recommendations the plugin's tool schema and the /memorybox-init command steer toward.
| Scope | Default agent ID derivation |
|---|---|
| user | opencode-user-<sha256(git user.email)[:8]> |
| project | opencode-proj-<sha256(git remote.origin.url || directory)[:8]> |
If there's no git email, falls back to $USER, then "anonymous". If there's no git remote, falls back to the current working directory. Stable and deterministic: open the same project in two different clones, get the same project namespace. Use two different machines with the same git email, get the same user namespace.
Override both in config — see Agent ID overrides.
Everything in ~/.config/opencode/memorybox.jsonc is optional:
{
// Auth — env var MEMORY_BOX_API_KEY takes precedence if set
"apiKey": "mbx_...",
// Point at a self-hosted Memory Box instance
"baseURL": "https://memory-box-api.fly.dev",
// Agent ID overrides (share across machines / teammates)
"userAgentID": "jason-personal",
"projectAgentID": "memory-box-team",
// How many memories to pull on first-message injection
"maxUserMemories": 10, // chronological from user scope
"maxProjectMemoriesChronological": 10, // chronological from project scope
"maxProjectMemoriesSemantic": 5, // hybrid-search relevance to user's msg
// Character budget for the injected [MEMORYBOX] block
"maxContextChars": 8000,
// Similarity threshold for hybrid search (0-1)
"similarityThreshold": 0.6,
// Kill switch for first-message injection (tool still works)
"injectOnFirstMessage": true,
// Extra regex triggers for the "save this" keyword nudge
"keywordPatterns": ["log\\s+this", "write\\s+down"]
}All numeric fields are clamped to safe ranges. Invalid regex patterns in keywordPatterns are dropped silently (and logged).
The two-agent model means you can choose how portable each scope is.
| Scenario | What to set |
|---|---|
| Share user preferences between a laptop and a workstation | Same userAgentID on both machines |
| Share project memory with teammates | Same projectAgentID across the team |
| Different git emails per project (e.g. work vs personal) but one pool | Override userAgentID to a personal constant |
| Already using Memory Box via MCP — want to reuse existing data | Set userAgentID / projectAgentID to your MCP agent IDs |
| Isolate project memory per-developer on a shared repo | Override projectAgentID to include your username |
Agent IDs must match ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ and be ≤128 chars. The plugin sanitizes overrides for you.
Available to your agent automatically. Single tool, mode-dispatched:
| Mode | Required | Optional | What it does |
|---|---|---|---|
add |
content |
scope, bucket |
Store a memory |
search |
query |
scope, bucket, limit |
Hybrid (BM25 + vector) search |
list |
— | scope, bucket, limit |
Recent memories, chronological |
related |
memoryId |
scope, limit |
Find memories similar to an existing one |
forget |
memoryId, scope |
— | Delete a memory |
help |
— | — | Usage guide (shown to the agent on demand) |
scope defaults to project for every mode except search — if scope is omitted on search, the plugin fans out to both namespaces in parallel and merges by similarity. Useful when the agent doesn't know which scope a query belongs to.
forget requires an explicit scope — there's no cross-scope delete. Agents mis-guess too often; this keeps the blast radius small.
# Bootstrap from scratch
/memorybox-init → agent explores the repo, populates project memory
# User-scope preference
"Hey, from now on give me terse responses and skip the summary at the end"
→ memorybox(mode:"add", scope:"user", bucket:"preferences",
content:"Prefers terse responses, no trailing summary")
# Project-scope configuration
"Remember — we use pnpm workspaces, not yarn"
→ memorybox(mode:"add", scope:"project", bucket:"project-config",
content:"Uses pnpm workspaces, not yarn")
# Agent self-querying mid-task
"Let me check how this codebase handles auth..."
→ memorybox(mode:"search", scope:"project", query:"authentication session flow")
# Pulling adjacent context
"I found the bug in auth.ts — let me see what else in project memory relates"
→ memorybox(mode:"related", memoryId:"mem_abc12345", scope:"project")
/memorybox-init— kicks off a deep-research pass where the agent explores your codebase (git history, package manifests, configs, architecture, conventions) and populates project memory. Expect 50+ tool calls. Run once per new project, or any time the codebase shifts significantly.
Wrap sensitive bits in <private>…</private> and they're redacted before anything hits Memory Box:
"The production API key is <private>sk-prod-abc123</private>"
↓ stored as ↓
"The production API key is [REDACTED]"
Content that's entirely inside private tags is rejected outright — nothing to store.
The plugin design leans on four Memory Box features that make it a good fit for coding agents specifically:
Hybrid search (BM25 + vector with RRF fusion). Code is full of exact tokens — framework names, commands, file paths, error codes. Pure vector search misses those. Memory Box's hybrid mode fuses full-text and semantic scoring so "Bun", "Hono", "src/routes/auth.ts" all retrieve reliably alongside fuzzy semantic matches. The plugin uses hybrid mode for every agent-initiated search and for the first-message relevance pull.
Storage intelligence. Every memory is automatically prefixed with [YYYY-MM-DD] at store time, embedded with the prefix. Temporal reasoning ("what did we figure out last week?", "recent pagination fixes") works out of the box with zero client-side date tracking. Memory Box scored 87.5% on LOCOMO's temporal category (81.4% overall) largely because of this.
Per-namespace isolation. Agent IDs in Memory Box map to independent Turbopuffer namespaces — not shared indexes with metadata filters. This is why the two-scope model (user vs project) works cleanly: there's no way for a project memory to accidentally leak into another project's retrieval, and no way for cross-project user preferences to be trapped in a single project's namespace.
Co-occurrence awareness. Memory Box's search endpoint accepts access_context=interactive|automated|internal to gate its co-occurrence graph (which memories get surfaced together). The plugin tags its automatic first-message fan-out as automated — so the co-occurrence signal only reflects genuine agent-driven access patterns, not plugin housekeeping.
Memory Box also exposes an MCP server at /mcp with OAuth 2.1 auth. Use whichever fits your client:
| Client | Recommended |
|---|---|
| Claude Desktop, Claude web, other MCP hosts | MCP server |
| OpenCode | This plugin — adds first-message context injection, keyword nudges, privacy redaction, scope-aware tool, and the /memorybox-init command on top of what MCP alone provides |
You can run both simultaneously; they hit the same Memory Box data. If you were previously using the MCP server in OpenCode and want continuity, override userAgentID / projectAgentID in the plugin config to match the agent IDs you were using with MCP.
Nothing seems to happen. Check ~/.opencode-memorybox.log — every request logs path, agent ID, status, and duration. First look for plugin: init to confirm the plugin loaded, then client: ok / client: http error for actual Memory Box requests.
Plugin says "Memory Box inactive" on startup. MEMORY_BOX_API_KEY isn't reaching the plugin. Confirm it's set in the shell OpenCode was launched from, or in ~/.config/opencode/memorybox.jsonc.
Context injection feels slow on first message. Expected: three parallel requests (~400-700ms total, depending on Fly.io region). Only happens on the first message of each session. Subsequent messages have zero added latency.
Context block is empty. Either you haven't populated memory yet (run /memorybox-init), or the plugin is pointing at a different agent ID than expected. Check agents: derived in the log — that tells you exactly which user and project agent IDs are being used.
The agent keeps forgetting things it was told to remember. Check chat.message: keyword detected in the log. If the nudge fired but nothing got saved, look for client: http error right after — likely a validation error (e.g. content over 100K chars, invalid bucket name).
git clone https://github.com/amotivv-inc/opencode-memorybox.git
cd opencode-memorybox
bun install
bun run typecheck
bun run buildTest locally before publishing:
// ~/.config/opencode/opencode.jsonc
{
"plugin": ["file:///absolute/path/to/opencode-memorybox"]
}Issues and pull requests welcome at github.com/amotivv-inc/opencode-memorybox.
MIT — see LICENSE.
{ "apiKey": "mbx_..." }