Summary
With the OpenCode plugin (plugin/opencode/agentmemory-capture.ts), the memory-context injection ("once per session", gated by contextInjectedSessions) is consumed by OpenCode's auto-title generation request instead of the real conversation's first turn. The main conversation therefore never receives <agentmemory-instructions> or <agentmemory-context>.
Environment
- agentmemory: current
main
- OpenCode plugin:
plugin/opencode/agentmemory-capture.ts
- Observed in the opencode request flow itself (see Root cause).
Root cause
When a new session gets its first user message, opencode runs two concurrent LLM requests, both firing experimental.chat.system.transform:
- Auto-title generation — forked in the session loop at
packages/opencode/src/session/prompt.ts:1132-1139; calls llm.stream with sessionID: <the same session> (prompt.ts:233) → LLMRequestPrep.prepare → triggers the hook with { sessionID, model } (src/session/llm/request.ts:69-73).
- The real user turn —
processor.ts:640 → same prepare → same hook, same sessionID.
Both carry the same sessionID, and the plugin's contextInjectedSessions gate is keyed only on sessionID. Whichever request reaches prepare first consumes the once-per-session injection — and the title request (tiny system prompt, small model) virtually always wins the race. The plugin cannot tell them apart.
Evidence
Server log (gen_ai.input.messages, two requests for the same first message):
- title request system prompt:
You are a title generator... + <agentmemory-instructions> (+ context) ← injected here
- build / main request system prompt: only the base instructions — no
<agentmemory-instructions> / <agentmemory-context>
Suggested fix (upstream, opencode side)
Add the agent name to the experimental.chat.system.transform hook input at packages/opencode/src/session/llm/request.ts:70 (and the plugin type in packages/plugin/src/index.ts), e.g. { sessionID, model, agent: input.agent.name }. Plugins can then reliably skip native/internal agents (input.agent === "title", and future internal agents) instead of string-matching the title prompt. This is additive and backward-compatible for existing plugin consumers.
Workaround (plugin side, brittle)
Plugins can skip the title request by matching the title-generator prompt in output.system[0] (e.g. /^You are (?:a|the) title generator/i), but that silently regresses if opencode ever rewrites the title prompt.
Related
Summary
With the OpenCode plugin (
plugin/opencode/agentmemory-capture.ts), the memory-context injection ("once per session", gated bycontextInjectedSessions) is consumed by OpenCode's auto-title generation request instead of the real conversation's first turn. The main conversation therefore never receives<agentmemory-instructions>or<agentmemory-context>.Environment
mainplugin/opencode/agentmemory-capture.tsRoot cause
When a new session gets its first user message, opencode runs two concurrent LLM requests, both firing
experimental.chat.system.transform:packages/opencode/src/session/prompt.ts:1132-1139; callsllm.streamwithsessionID: <the same session>(prompt.ts:233) →LLMRequestPrep.prepare→ triggers the hook with{ sessionID, model }(src/session/llm/request.ts:69-73).processor.ts:640→ sameprepare→ same hook, samesessionID.Both carry the same sessionID, and the plugin's
contextInjectedSessionsgate is keyed only onsessionID. Whichever request reachespreparefirst consumes the once-per-session injection — and the title request (tiny system prompt, small model) virtually always wins the race. The plugin cannot tell them apart.Evidence
Server log (
gen_ai.input.messages, two requests for the same first message):You are a title generator...+<agentmemory-instructions>(+ context) ← injected here<agentmemory-instructions>/<agentmemory-context>Suggested fix (upstream, opencode side)
Add the agent name to the
experimental.chat.system.transformhook input atpackages/opencode/src/session/llm/request.ts:70(and the plugin type inpackages/plugin/src/index.ts), e.g.{ sessionID, model, agent: input.agent.name }. Plugins can then reliably skip native/internal agents (input.agent === "title", and future internal agents) instead of string-matching the title prompt. This is additive and backward-compatible for existing plugin consumers.Workaround (plugin side, brittle)
Plugins can skip the title request by matching the title-generator prompt in
output.system[0](e.g./^You are (?:a|the) title generator/i), but that silently regresses if opencode ever rewrites the title prompt.Related