Skip to content

Understand one turn of the OpenCode loop

You’re authenticated, a model is wired up, and you’re standing at a live prompt inside feedmill — the self-hosted feed aggregator a colleague left you, a Go CLI plus a sync server that pulls dozens of RSS, Atom, and JSON feeds into one reading queue. You already have a suspect: one feed’s items keep showing up with the wrong timestamps, so the parser is almost certainly mishandling that source’s date format. Your instinct is to throw that bug at the agent right now. Hold it for two minutes.

Every task you’ll ever give OpenCode — from this date fix to a refactor that spans a dozen feed parsers four chapters from now — runs through the same small loop. The cheapest possible place to see that loop is on a question that can’t break anything, asked from the agent that literally can’t write to disk. Watch it once here and the real change in the next lesson will feel like something you’ve already done.

OpenCode ships two primary agents. The default is build — full development access, including editing files. The other is plan — read-only, with edits gated to ask or deny. You switch between them with the Tab key, right in the TUI; the agent you’re in shows in the interface, and Tab cycles to the other.

For your very first turn on an inherited codebase, start in plan. Tab over to it before you type anything. The point isn’t that you’re planning a feature — it’s that plan is read-only by construction, so you get to watch the agent gather context with the writing hand tied behind its back. There is no “trust me” required: it cannot edit, because that’s the agent’s permission profile, not your vigilance.

Make your first turn a question, not a command

Section titled “Make your first turn a question, not a command”

You’ve just inherited this repo. So has the agent — it knows nothing about feedmill that it hasn’t read yet. A read-only question costs you almost nothing and shows you exactly how it builds a map before it’s ever allowed to write:

> what does this project do, and where does it parse the published date out of a feed item?
⏺ read README.md
⏺ read cmd/feedmill/main.go
⏺ grep "time.Parse" internal/feed/
⏺ read internal/feed/parse.go
feedmill is a self-hosted feed aggregator: a Go CLI (cmd/feedmill)
plus an HTTP sync server (internal/server) that fetches RSS, Atom,
and JSON feeds, dedupes items, and serves a reading queue.
Item dates are parsed in internal/feed/parse.go, in parsePublished().
It tries time.RFC1123Z and then RFC3339, and falls back to the zero
time if neither matches — so a feed using a different layout silently
gets a zero/garbage timestamp instead of an error.

The exact tool-call lines and ordering above are illustrative — a real session may label or order the reads and grep differently — but the shape is what matters: the agent reads and greps for context before it answers.

Watch what that one turn bought you. The agent went and read files — and it told you which ones, the grep included, before it said a single thing about the code. It came back with a map: what the project is, the CLI-plus-sync-server split, where the date parsing lives, and an early hunch about why timestamps go wrong. You’ve learned the lay of the land for your ticket. You’ve learned something more useful too — how this agent behaves when it needs context. It reads first, then answers. And you learned it from plan, where the worst case was a wrong sentence, not a wrong edit.

That single turn is the whole loop in miniature. Strip away the specifics and every OpenCode turn — in plan or build — has the same shape:

  1. Read — it gathers the context it needs, pulling in the specific files relevant to your request (and grepping to find them) rather than guessing from the prompt alone.
  2. Propose — it tells you what it intends to do. For a question that’s an answer; for a task it’s a concrete plan and the exact edits or commands it wants to run.
  3. Approve — it stops and waits for you. Permissions in OpenCode are per-tool per-agent: in plan, both the edit and bash tools are gated so neither an edit nor a shell command can slip through silently. In build, by default the opposite holds — most permissions default to allow, so edit writes and bash commands run without pausing unless you tighten them in config. (Only a couple of permissions, like the runaway-loop guard, default to ask out of the box.) This is the checkpoint between the agent’s intent and your filesystem.
  4. Apply — only after the permission clears does it actually write the edit or run the command — and then it checks its own work, usually by running whatever verifies the change.

Read, propose, approve, apply. When a task goes well it’s because all four beats happened in order; when one goes wrong it’s usually because a beat got skipped — it proposed without reading enough, or applied something you didn’t really look at. Learning to see those beats is most of what makes you good at driving the agent, and you just watched a clean instance of the first two on a question where a wrong answer couldn’t cost you anything.

The two beats you haven’t watched yet — the write and the check — you can step through here without leaving plan. A different small fix, traced through the same loop: gather is the read beat you just saw, and each act is exactly the kind of edit build would apply and plan cannot. Watch what happens when the first verify comes back red:

Step 1 / 8You hand over a task
> The login test is failing on main. Fix it.
(prompt enters the window)

Your message lands in the context window. The model reads it and decides what it needs first — it can’t fix what it hasn’t seen, so the first move is almost never an edit.

The reason starting in plan matters on feedmill specifically: this is code you didn’t write, and it runs unattended on a cron, pulling from sources you don’t control. The approve beat is not a formality to rush past — it’s the only thing between a confident-sounding wrong edit and a sync server that quietly corrupts your reading queue at 3am. Lead with a write as your very first move and you’d be trusting a tool you’ve watched do nothing yet, on a codebase it hasn’t shown you it understands. Spend one read-only question first and that trust is earned instead of assumed.

A typed codebase gives the agent a second set of eyes

Section titled “A typed codebase gives the agent a second set of eyes”

One more thing worth knowing before the real fix, because it changes how the apply beat plays out in Go specifically. OpenCode can integrate Language Server Protocol servers. LSP is off by default, but once you enable it ("lsp": true in config, or a per-server object) it surfaces diagnostics — type errors, unresolved symbols — back into the agent’s loop, so a fix that doesn’t compile gets caught and corrected rather than handed to you broken.

That said, OpenCode’s own guidance is that in many projects the more reliable feedback is the agent running your real go vet / go build / go test directly, so errors land back in the loop the same way they would for you. feedmill is a typed codebase full of parsers — exactly the kind of place where “does it still compile and pass tests” (go build ./... and go test ./...) is the cheapest possible signal, and you’ll lean on it heavily once you start editing.

You now know where the date parsing lives, how the agent behaves when it works, and that read-only plan was a safe place to find both out. That’s exactly what you need to Tab over to build, hand it the actual bug, and watch all four beats close on a real fix. Next: ship your first reviewed change.