Persistent memory for OpenCode.
This package has two entrypoints:
- a server plugin for recall, auto-ingest, and memory tools
- a TUI plugin for interactive setup inside OpenCode
opencode plugin --global @mem9/opencodeThat adds @mem9/opencode to these files:
macOS/Linux:
~/.config/opencode/opencode.json~/.config/opencode/tui.json
Windows:
%APPDATA%\\opencode\\opencode.json%APPDATA%\\opencode\\tui.json
mem9 works best with one global plugin install plus project mem9.json overrides.
OpenCode merges plugin lists across scopes, so one install keeps recall, ingest, and tools predictable.
/mem9-setup is the main entrypoint for:
- shared mem9 credentials
- OpenCode mem9 settings
When no usable profile exists, it shows two actions:
Get a mem9 API key automaticallyAdd an existing mem9 API key
When usable profiles already exist, it shows four actions:
Get a mem9 API key automaticallyAdd an existing mem9 API keyUse an existing mem9 profile in a scopeAdjust scope settings
The last two actions do different things:
Use an existing mem9 profile in a scopechanges which profile a user or project scope usesAdjust scope settingschangesdebug,defaultTimeoutMs, andsearchTimeoutMsfor a user or project scope
Keep the plugin install global.
Use <project>/.opencode/mem9.json when one repository needs a different profile, debug flag, or timeout.
Example:
{
"schemaVersion": 1,
"profileId": "default",
"debug": false,
"defaultTimeoutMs": 8000,
"searchTimeoutMs": 15000
}- Shared credentials:
macOS/Linux:
$HOME/.mem9/.credentials.jsonWindows:%USERPROFILE%\\.mem9\\.credentials.json - Global mem9 config:
macOS/Linux:
~/.config/opencode/mem9.jsonWindows:%APPDATA%\\opencode\\mem9.json - Project mem9 override:
all platforms:
<project>/.opencode/mem9.json - Debug logs:
macOS/Linux:
~/.local/share/opencode/plugins/mem9/log/YYYY-MM-DD.jsonlWindows:%LOCALAPPDATA%\\opencode\\plugins\\mem9\\log\\YYYY-MM-DD.jsonl
MEM9_HOME defaults to $HOME/.mem9 on macOS/Linux and %USERPROFILE%\\.mem9 on Windows.
Shared credentials live in:
$MEM9_HOME/.credentials.json
Example:
{
"schemaVersion": 1,
"profiles": {
"default": {
"label": "Personal",
"baseUrl": "https://api.mem9.ai",
"apiKey": "..."
}
}
}profiles stores credentials only.
User and project mem9 config use the same schema:
{
"schemaVersion": 1,
"profileId": "default",
"debug": false,
"defaultTimeoutMs": 8000,
"searchTimeoutMs": 15000
}Field meanings:
profileId: which shared profile this scope should usedebug: enable redacted JSONL debug logsdefaultTimeoutMs: request timeout for normal mem9 callssearchTimeoutMs: request timeout for recall search
Project config overrides user config for the current repository.
These environment variables still override disk config at runtime:
MEM9_API_KEYMEM9_API_URLMEM9_DEBUGMEM9_HOME
Legacy compatibility:
MEM9_TENANT_ID
MEM9_TENANT_ID is treated as the API key source for older setups.
OpenCode caches npm plugins by package specifier.
When config points at @mem9/opencode, OpenCode resolves it as @mem9/opencode@latest.
Upgrade flow:
- Quit OpenCode.
- Delete the cached folder that matches the installed specifier.
macOS/Linux default:
~/.cache/opencode/packages/@mem9/opencode@latestWindows default:%LOCALAPPDATA%\\opencode\\packages\\@mem9\\opencode@latestIf you pinned an exact version such as@mem9/opencode@0.1.1, delete that exact folder name instead. - Run:
opencode plugin --force --global @mem9/opencode- Restart OpenCode.
For prerelease testing, install an explicit npm specifier such as @mem9/opencode@rc or an exact version.
The server plugin does three things:
- recalls relevant mem9 memories before each chat turn
- exposes mem9 memory tools inside OpenCode
- starts best-effort background smart ingest when the session becomes idle and when compaction begins
OpenCode integration uses four runtime hooks:
| Hook | What mem9 does |
|---|---|
chat.message |
Captures the latest real user prompt and updates in-memory session state. |
experimental.chat.system.transform |
Searches mem9 with the captured prompt and injects a <relevant-memories> block. |
event with session.idle |
Starts a best-effort background smart-ingest pass for the recent transcript window. |
experimental.session.compacting |
Pushes a compaction hint and starts another best-effort background smart-ingest pass. |
The plugin captures the latest real user prompt from chat.message, cleans it, bounds it, and injects a formatted recall block during experimental.chat.system.transform.
The plugin ingests from two points:
session.idleexperimental.session.compacting
Both paths fetch up to 24 recent session messages, keep real text-only user and assistant turns, strip injected memory blocks, and upload the last 12 cleaned messages in the background.
Identical transcripts are deduped per in-memory session state, so a matching idle ingest and compaction ingest share one upload while that session state stays warm.
Two timing details matter:
- hook completion happens before the background upload finishes
- the dedupe window is in-memory and TTL-bound to about 15 minutes, so restart or cache expiry can upload the same transcript again
The plugin registers these tools:
memory_storememory_searchmemory_getmemory_updatememory_delete
Setup pendingmeans the plugin could not find a usable runtime identity. Run/mem9-setup, addMEM9_API_KEY, or point the activemem9.jsonscope at a profile with a non-emptyapiKey.- If
/mem9-setupis missing, confirm@mem9/opencodeis listed in your globaltui.json. - If recall or tools work in one project and not another, check whether the project has its own
.opencode/mem9.jsonoverride. - If recall, auto-ingest, or debug logs appear to run twice, check for duplicate plugin registration across user scope, project scope, npm, or local file paths.
- If the selected profile exists but has no
apiKey, update that profile in$MEM9_HOME/.credentials.json. - If debug logging is enabled and no file appears, confirm OpenCode can write to its state directory.