This plugin runs on both hosts from a single package entry:
- opencode 1.x reads
server()(V1 hooks,opencode.json→agentmap). - opencode2 reads
id+setup()(V2 context API,src/v2/*adapter).
Install for opencode2:
All V1 plugin options (generate_memories, use_memories,
dedicated_tools, disable_on_external_context, extract_model,
consolidation_model, numeric clamps, codex_interop, claude_import)
apply unchanged — option parsing is shared (applyPluginOptions).
The entire memory pipeline (extraction → consolidation → injection →
citation feedback against the global ~/.local/share/opencode workspace)
runs byte-identical on both hosts. src/v2/shim.ts presents a V1-shaped
client façade over the V2 plugin context, so phase1/phase2/capture/llm
execute the same code paths. Only genuinely missing V2 surfaces are
adapted; everything else is hook translation (src/v2/plugin.ts):
| V1 | V2 |
|---|---|
config hook agent injection |
agent.transform ensure (update creates) |
returned tool map |
tool.transform (same tools/* logic via adapter) |
chat.message pump |
prompt hook |
system.transform injection |
context hook (system.push({type:"text",…})) |
text.complete + messages.transform citations |
session.text.ended durable accounting + context reconciliation/strip |
tool.execute.before pollution |
tool.execute.before (same hook name) |
session.status idle / session.idle pump |
session.execution.succeeded event |
session.deleted cleanup |
public session.remove with liveness fallback |
experimental/session global discovery |
authenticated public session.list with cursor pagination |
V1 session.messages |
authenticated public message.list (full persisted history) |
- Registered service is required for global reads. V2 discovers the
local service with
Service.discover(), preserves its auth headers, and accepts it only when/healthreports the plugin host's own PID. It never starts a service withService.ensure(). If no matching service is registered, global discovery reports a clear unavailable error. - Global discovery is complete. The adapter follows public
session.listcursors and uses publicmessage.listfor full persisted history. Helper sessions are excluded by durable metadata and the cleanup sweep reclaims them after a restart;session.contextis not used for transcript capture. - Agents are location-scoped. V2 provisions the agents in the active plugin location and creates helper sessions in that same location. The consolidation agent's read/edit/search/glob permissions are allowlisted only under the memory workspace; all other actions remain denied. A global OpenCode plugin install therefore provisions the agents as each active location loads the plugin.
- Citations are accounted durably.
session.text.endedis the primary hook because it contains the completed text after durable commit. A SQLite reconciliation table deduplicates(assistant message, cited session)pairs across duplicate events, context calls, and process restarts. The context hook strips citation markup before the next model call; retained markup in persisted history is harmless and can be rendered by the TUI. - Config and models are explicit. V2 adapts public config documents for
the shared resolver. V2 configs do not provide V1's
small_modelfield; unsetextract_modeluses the session default, whileconsolidation_modeluses the configuredmodelwhen present. Set both plugin options explicitly for deterministic routing. Cancellation is verified through request signals plus interrupt-and-wait cleanup. - Both agents ship; only
memorizeworks. Extraction runs sessionless throughgenerate.text, somemorize-extractis provisioned hidden and unused (V1 likewise skips injecting unused agents).
The package's ./tui entry adds a Memory section to the session sidebar.
OpenCode2 loads it automatically alongside the server plugin. /memory-status
(also Show memory status in the command palette) opens effective models,
read/write settings, import status, retry eligibility, and warnings.
- Status is global, using the same job snapshots as
memory_inspect. - The UI refreshes on pipeline events and reconciles every five seconds while loaded, including changes made by another worker; viewing it never starts jobs.
- A disconnected or unavailable server shows Unavailable, not stale Idle.
- Last success is shown only when the latest recorded consolidation attempt
succeeded;
—means no clean success timestamp is available for that attempt. - TUI dependencies are optional peers supplied by OpenCode2; V1 loads only the
./serverentry and does not import V2 runtime dependencies. The build compiles Solid JSX and ships the result underdist/. - TUI rules learned the hard way:
setup()must only claim slots —keymap.layerthrows outside a Solid component scope, so it lives in anapp-slot component; never render<Show>(or any conditional) with element children directly under<box>— its empty placeholder is a bare text node and the renderer rejects it. Use unconditional lines with placeholders. - The TUI bundle must import only
@opencode/plugin/tui,solid-js, and@opentui/solid: the CLI sandbox does not resolvezodor@opencode/plugin/rpc, so the status contract (src/v2/status-rpc.ts) is plain JSON Schema with a hand-written guard.
For a local wrapper, add tui.ts beside its index.ts, re-exporting the built
dist/src/v2/tui.js default export, then run bun run build in this repository.
bun run typecheck && bun test && bun run build
bun run smoke # V1 entry
bun run contract # V1 host surface
bun run contract:v2 # V2 host surface (needs the opencode2 service)