Summary
Auto-capture and user-profile learning are 100% non-functional on OpenCode v2. The v2 plugin adapter (src/v2/) has never been published to npm, and the code on main appears to have two gaps that prevent internal structured-output sessions from ever running.
This is not gated behind configuration — with any provider/model combination, user_prompts.capture_attempts stays 0 and captured stays 0 for every single prompt.
Environment
|
|
| opencode |
v2.0.20 (@opencode/cli) |
| plugin |
opencode-mem built from main (package.json version 2.26.0) |
| Node |
v25.7.0 |
| OS |
macOS, arm64 |
| Install path |
source build, loaded via ~/.config/opencode/plugins/opencode-mem.ts re-exporting dist/plugin.js |
The published npm 2.26.0 cannot be used at all on v2 — it exports { id, server } and is rejected by the v2 plugin schema:
PluginModule.LoadError: Plugin must export a default definition with an id
and an effect or setup function.
(cause: SchemaError(Missing key at ["default"]["effect"] / ["default"]["setup"]))
src/v2/plugin.ts on main does export { id, setup } and loads fine, so the build below is from main.
Problem 1 — session.idle is never delivered (event name changed in v2)
The legacy capture trigger is:
// src/index.ts
if (event.type === "session.idle") { /* ... */ idleTimeout = setTimeout(...)
The v2 adapter forwards every event through toLegacyEvent(), but only maps one event name:
// src/v2/legacy-client.ts
const type = rawType === "session.compaction.ended" ? "session.compacted" : rawType;
session.idle has no mapping. I instrumented ctx.event.subscribe() on a real v2.0.20 session and dumped every session.* event actually emitted:
session.execution.started session.step.started
session.execution.succeeded session.step.ended
session.text.started session.step.streamed
session.text.ended session.text.delta
session.reasoning.delta session.inbox.delivered
session.instructions.updated session.inbox.enqueued
session.renamed session.usage.updated
There is no session.idle in v2. The equivalent is session.execution.succeeded. Because the name never matches, the 10s debounce timer is never armed and performAutoCapture() is never called.
Suggested fix
const type =
rawType === "session.compaction.ended" ? "session.compacted"
: rawType === "session.execution.succeeded" ? "session.idle"
: rawType;
I verified this alone changes the symptom from capture_attempts = 0 to capture_attempts > 0 (capture is attempted and then fails for the reason in Problem 2).
Problem 2 — _useSdkTransport is never enabled, so structured output always throws
After Problem 1 is fixed, every capture attempt fails three times with:
auto-capture: opencode provider failed, falling back to external API:
{"error":"Error: opencode-mem: v2 server base URL not initialized;
call createV2Client(serverUrl) first"}
Auto-capture final error after 3 attempts
Root cause, in src/index.ts:
export async function configureOpencodeHostTransport(ctx) {
const { createV2Client, ... } = await loadOpencodeProvider();
const serverUrl = hostConfig.baseUrl ?? ctx.serverUrl;
if (serverUrl) { // <-- never true on a native v2 ctx
setV2Client(createV2Client(serverUrl, { fetch, headers }));
}
}
A native v2 context has no serverUrl, so createV2Client() is never called. That means the module-level _useSdkTransport (in services/ai/opencode-provider.ts) stays false, and generateStructuredOutput() can never take its SDK branch:
if (_useSdkTransport && hasV2SessionClient(client)) {
return generateViaSdkClient(client, {...}); // unreachable on v2
}
const baseUrl = _v2BaseUrl; // always undefined
if (!baseUrl) throw new Error("opencode-mem: v2 server base URL not initialized...");
Note hasV2SessionClient() would return true for the object createLegacyClient(ctx) produces (it exposes session.create/prompt/delete) — only the _useSdkTransport flag is missing.
Workaround I used
src/v2/plugin.ts currently does:
const { setV2Client } = await loadOpencodeProvider();
setV2Client(legacyClient);
Setting _useSdkTransport requires calling createV2Client() (it is the only writer of that flag), so I added a call after it:
const { setV2Client, createV2Client } = await loadOpencodeProvider();
setV2Client(legacyClient);
createV2Client("<unused>", { fetch: globalThis.fetch });
The URL is never read — the SDK branch returns before _v2BaseUrl is consulted, and createLazyV2Client() is fully lazy so discarding its return value has no side effects. This makes capture work end to end.
I am not claiming this is the right shape for the fix — it is a demonstration that the flag is the only blocker. A cleaner fix might export an explicit setUseSdkTransport(true), or have the v2 adapter pass a real server URL.
Also worth checking (may be intentional)
createLegacyClient().provider.list() derives the connected-provider set from ctx.model.list():
const models = await ctx.model.list();
const connected = [...new Set(models.data.map(m => m.providerID).filter(Boolean))];
At plugin init time this returned only the always-available built-in provider, so any API-key provider configured in opencode.json was reported as "not connected". If the v2 host populates models asynchronously, this race would make opencodeProvider-based capture unreliable even after the two fixes above. I did not chase this further because the external-API path (memoryApiUrl + memoryApiKey) works correctly.
Separately, I hit this on the OpenCode Zen free tier, which may be expected but is easy to trip over:
Generate.UnavailableError: OpenCode's free tier can only be used from within OpenCode
so opencode/*-free models cannot be used for auto-capture's internal background sessions. Worth a note in the README.
Impact
For anyone on OpenCode v2, memory accumulates only via explicit memory tool calls or import. Automatic capture, user-profile learning, and compaction injection silently never run. There are no crash or error banners — the only signal is capture_attempts = 0 in user-prompts.db.
Given src/v2/ is still unpublished, I suspect nobody has run this path against a released v2 build yet. Related: #300 asks about hardware requirements, which is also unanswered.
Repro
- Build
main (bun install && bunx tsc && cd web && bun run build), load dist/plugin.js from a plugin file in ~/.config/opencode/plugins/.
- Have any multi-turn technical conversation on opencode v2.
- Wait >10s after the turn goes idle.
- Inspect
~/.opencode-mem/data/user-prompts.db:
select count(*) from user_prompts; -- grows
select count(*) from user_prompts where captured=1; -- stays 0
select count(*) from user_prompts where capture_attempts > 0; -- stays 0
No memory is written and ~/.opencode-mem/opencode-mem.log contains no capture lines.
Summary
Auto-capture and user-profile learning are 100% non-functional on OpenCode v2. The v2 plugin adapter (
src/v2/) has never been published to npm, and the code onmainappears to have two gaps that prevent internal structured-output sessions from ever running.This is not gated behind configuration — with any provider/model combination,
user_prompts.capture_attemptsstays0andcapturedstays0for every single prompt.Environment
v2.0.20(@opencode/cli)opencode-membuilt frommain(package.jsonversion2.26.0)~/.config/opencode/plugins/opencode-mem.tsre-exportingdist/plugin.jsThe published npm
2.26.0cannot be used at all on v2 — it exports{ id, server }and is rejected by the v2 plugin schema:src/v2/plugin.tsonmaindoes export{ id, setup }and loads fine, so the build below is frommain.Problem 1 —
session.idleis never delivered (event name changed in v2)The legacy capture trigger is:
The v2 adapter forwards every event through
toLegacyEvent(), but only maps one event name:session.idlehas no mapping. I instrumentedctx.event.subscribe()on a real v2.0.20 session and dumped everysession.*event actually emitted:There is no
session.idlein v2. The equivalent issession.execution.succeeded. Because the name never matches, the 10s debounce timer is never armed andperformAutoCapture()is never called.Suggested fix
I verified this alone changes the symptom from
capture_attempts = 0tocapture_attempts > 0(capture is attempted and then fails for the reason in Problem 2).Problem 2 —
_useSdkTransportis never enabled, so structured output always throwsAfter Problem 1 is fixed, every capture attempt fails three times with:
Root cause, in
src/index.ts:A native v2 context has no
serverUrl, socreateV2Client()is never called. That means the module-level_useSdkTransport(inservices/ai/opencode-provider.ts) staysfalse, andgenerateStructuredOutput()can never take its SDK branch:Note
hasV2SessionClient()would returntruefor the objectcreateLegacyClient(ctx)produces (it exposessession.create/prompt/delete) — only the_useSdkTransportflag is missing.Workaround I used
src/v2/plugin.tscurrently does:Setting
_useSdkTransportrequires callingcreateV2Client()(it is the only writer of that flag), so I added a call after it:The URL is never read — the SDK branch returns before
_v2BaseUrlis consulted, andcreateLazyV2Client()is fully lazy so discarding its return value has no side effects. This makes capture work end to end.I am not claiming this is the right shape for the fix — it is a demonstration that the flag is the only blocker. A cleaner fix might export an explicit
setUseSdkTransport(true), or have the v2 adapter pass a real server URL.Also worth checking (may be intentional)
createLegacyClient().provider.list()derives the connected-provider set fromctx.model.list():At plugin init time this returned only the always-available built-in provider, so any API-key provider configured in
opencode.jsonwas reported as "not connected". If the v2 host populates models asynchronously, this race would makeopencodeProvider-based capture unreliable even after the two fixes above. I did not chase this further because the external-API path (memoryApiUrl+memoryApiKey) works correctly.Separately, I hit this on the OpenCode Zen free tier, which may be expected but is easy to trip over:
so
opencode/*-freemodels cannot be used for auto-capture's internal background sessions. Worth a note in the README.Impact
For anyone on OpenCode v2, memory accumulates only via explicit
memorytool calls orimport. Automatic capture, user-profile learning, and compaction injection silently never run. There are no crash or error banners — the only signal iscapture_attempts = 0inuser-prompts.db.Given
src/v2/is still unpublished, I suspect nobody has run this path against a released v2 build yet. Related: #300 asks about hardware requirements, which is also unanswered.Repro
main(bun install && bunx tsc && cd web && bun run build), loaddist/plugin.jsfrom a plugin file in~/.config/opencode/plugins/.~/.opencode-mem/data/user-prompts.db:~/.opencode-mem/opencode-mem.logcontains no capture lines.