Repository navigation
Description
Summary
SessionReminders.apply's legacy (non-experimentalPlanMode) branch decides whether to inject the "operational mode has changed from plan to build" reminder using:
const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan")
if (wasPlan && input.agent.name === "build") {
// ...push BUILD_SWITCH synthetic reminder
}.some() here checks "has this session ever contained a plan-agent message", not "was the immediately preceding turn plan". input.messages comes from MessageV2.filterCompactedEffect(sessionID) (see prompt.ts around the main agent loop), which is the full uncompacted session history, not a per-turn window.
Why this matters for custom agents
opencode explicitly supports custom primary agents (https://opencode.ai/docs/agents), which can be switched to via Tab like build/plan. With only the two built-in agents, "session ever had a plan message" and "previous turn was plan" are almost always equivalent in practice, so this has presumably gone unnoticed.
Once a third primary agent is in rotation — e.g. a custom read-only advisor agent (config agent: { ask: { mode: "primary", permission: { edit: "deny" } } }) — the assumption breaks:
- User is in
plan, does some read-only exploration. - User switches to a custom agent (e.g.
ask) for a few turns of Q&A, unrelated to planning. - User switches to
build.
At step 3, wasPlan is still true (the plan message from step 1 is still in the uncompacted history), so BUILD_SWITCH fires — even though the actual previous turn's agent was ask, not plan. The model receives:
<system-reminder>
Your operational mode has changed from plan to build.
...
</system-reminder>
which is factually wrong for that turn (it changed from the custom agent to build, not from plan). Worse: because the check scans the entire uncompacted history rather than being edge-triggered, this reminder can re-fire on every subsequent build turn for the rest of the session (until compaction eventually drops the old plan-agent message out of the window), regardless of how many other agents were used in between. A reminder that's supposed to be a rare, high-signal "your mode just changed" notice instead becomes noisy and occasionally false, which risks the model learning to discount it.
Reproduction
- Define a custom primary agent, e.g.:
--- name: ask mode: primary permission: edit: deny --- You are a read-only advisor.
- In a session: switch to
plan, send one message. Switch toask, send one or more messages. Switch tobuild, send a message. - Inspect the synthetic parts on that last user message (or just observe the model's response) — the
BUILD_SWITCHreminder appears and claims the transition was "from plan to build", despite the immediately preceding turn beingask.
Confirmed against a live 1.18.3 session with exactly this custom-agent setup (transcript inspected via opencode db / sqlite).
Suggested fix
Mirror the pattern already used a few lines below in the same file for the experimentalPlanMode branch (assistantMessage = input.messages.findLast((msg) => msg.info.role === "assistant"), then check assistantMessage?.info.agent === "plan"), which is edge-triggered and doesn't have this issue. I've opened a PR with this fix: (linked below).
Environment
- opencode 1.18.3 (macOS, Homebrew install)
- Reproduced conceptually against
devbranch source (same logic present,reminders.tsunchanged between the two as of this writing)
Activity
github-actions commentedon Jul 21, 2026
This issue might be a duplicate of existing issues. Please check:
.some()check inreminders.tscauses the plan-to-build switch reminder to fire on every subsequent turn rather than just once. That issue was closed with a fix referenced in PR fix: plan-build switch reminder spam, provider config limit optional #31897.If the fix from those issues was not fully applied or regressed, this may be a reopen/regression rather than a new report. It may be worth confirming whether the
reminders.tsfix is present on the currentdevbranch before proceeding.github-actions commentedon Sep 21, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.