anomalyco / anomalyco/opencode

SessionReminders: build-switch banner mislabels/repeats when a custom primary agent (e.g. read-only 'ask') is used between plan and build

Open
#38,066 1 comment 0 reactions 1 assignee View on GitHub

@jlongster is already working on this.

Since Jul 21, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Summary

SessionReminders.apply's legacy (non-experimentalPlanMode) branch decides whether to inject the "operational mode has changed from plan to build" reminder using:

https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/reminders.ts#L37

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:

  1. User is in plan, does some read-only exploration.
  2. User switches to a custom agent (e.g. ask) for a few turns of Q&A, unrelated to planning.
  3. 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

  1. Define a custom primary agent, e.g.:
    ---
    name: ask
    mode: primary
    permission:
      edit: deny
    ---
    You are a read-only advisor.
    
  2. In a session: switch to plan, send one message. Switch to ask, send one or more messages. Switch to build, send a message.
  3. Inspect the synthetic parts on that last user message (or just observe the model's response) — the BUILD_SWITCH reminder appears and claims the transition was "from plan to build", despite the immediately preceding turn being ask.

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 dev branch source (same logic present, reminders.ts unchanged between the two as of this writing)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.