Skip to content

Tab between plan and build

The dedupe layer is the part of feedmill you trust least. Two feeds publish the same story under different GUIDs, a third re-syndicates it with a tweaked title, and the current code catches maybe half of them — so your reading queue fills with the same article three times. You want to restructure it. But this is exactly the kind of change where letting the agent start editing on its first instinct is how you end up with a clever rewrite of the wrong thing. You want a plan first — a real one, that you read and push back on — and then the execution, without losing the thread between them.

In a lot of agents that means two separate sessions: one to think, one to do. OpenCode collapses it into a single keystroke, because planning and building aren’t two modes of one agent — they’re two agents you switch between with Tab.

OpenCode ships two primary agents out of the box:

  • build — the default. Full edit access; it can write files and run commands.
  • plan — gated by design. It reads, greps, and reasons freely, but file edits and bash are set to ask, so every change is held behind an approval prompt — nothing lands silently while it’s supposed to be thinking about the dedupe layer.

Tab cycles between them — the default binding for the agent_cycle keybind — and Shift+Tab cycles back (agent_cycle_reverse). This is the thing to internalize: you’re not flipping a “plan mode” flag on one agent. You’re handing the same conversation to a different agent with a different permission policy. That distinction is the whole reason the workflow below is safe.

Plan the restructure behind an approval gate

Section titled “Plan the restructure behind an approval gate”

Tab into plan and describe the outcome, not the steps — the same way you would in build, except now you know nothing gets written without your say-so while you’re talking:

plan ›
> the dedupe layer misses re-syndicated and retitled duplicates. Read how
dedupe works today and sketch how you'd restructure it to catch those —
don't change anything yet, just the approach.
• Read internal/dedupe/dedupe.go, internal/dedupe/hash.go,
internal/feed/item.go (3 files)
Today dedupe keys on a SHA-1 of the raw GUID, so a story re-syndicated
under a new GUID — or the same GUID with an edited title — reads as new.
How I'd restructure it:
1. Split dedupe into two passes: an exact pass (GUID/link) and a fuzzy
pass (normalized title + published-date window).
2. Move the hashing out of dedupe.go into a Fingerprint type in hash.go
so both passes share one normalization path.
3. Add a small content-similarity check behind an interface, so the
fuzzy pass is swappable later without touching the caller.
This touches dedupe.go, hash.go, and the Store call in sync.go. I have
not changed any files.

It read the real code before sketching anything — you can see which files — and came back with a plan you can actually argue with, not a vague intention. So argue with it, still in plan:

plan ›
> a fuzzy title match scares me on a feed reader — false-merge a real
second article and the reader never sees it. Make the fuzzy pass
conservative and reversible.
Good constraint. I'll gate the fuzzy pass behind a high similarity
threshold and have it record both items with a "merged-into" pointer
rather than dropping one — so a wrong merge is recoverable, not silent.
No code written.

That second turn is the payoff of the gated agent. You shaped the design — narrowed it away from a genuinely dangerous behavior for a feed reader — across two turns, and not once did an edit slip in mid-conversation while you were still reasoning. The plan agent’s permission policy held every change behind a prompt for you.

Tab back to build and execute the same plan

Section titled “Tab back to build and execute the same plan”

Now Tab. You’re in build — edit access, same conversation. You don’t re-explain anything; the plan you and plan just hammered out is sitting in the history this agent picks up from.

build ›
> good — implement that, conservative fuzzy pass and all. Start with the
Fingerprint refactor, then the two passes.
⏵ Edit internal/dedupe/hash.go
+ type Fingerprint struct { ... }
Allow this edit? › Yes / No
⏵ Edit internal/dedupe/dedupe.go → approved
⏵ Edit internal/feed/sync.go → approved
⏵ Run go test ./internal/dedupe/...
• ok feedmill/internal/dedupe 0.21s
Done. dedupe now runs an exact pass then a conservative fuzzy pass;
the fuzzy pass records a merged-into pointer instead of dropping items.

The permission policy is the difference between the two agents: both plan and build ask before an edit lands, but build carries full edit access where plan is configured to gate file edits and bash behind that prompt by default. Same thread, same context, different agent, different rules. You planned with one and built with the other, and the handoff cost you exactly one keystroke and zero re-explaining.

When two agents aren’t enough, make a third

Section titled “When two agents aren’t enough, make a third”

plan and build are just the two primary agents that ship by default — the design isn’t limited to them. A custom primary agent can define its own permission profile and sit in the same Tab cycle. On feedmill the obvious one is a review primary: gated like plan, but with a prompt and a model tuned for auditing diffs rather than designing them, so after a build run you can Tab to review for a cold second read before you commit. You’ll write one in Make the TUI yours; for now the point is just that the plan/build toggle is the visible case of a general mechanism — primary agents you cycle through, each with its own leash.

The move you just learned — outline behind an approval gate, Tab, execute — is the one you’ll reach for on every change big enough to deserve a plan. Next, the other half of living in the TUI: keeping more than one of these threads alive at once. Juggle and resume sessions.