Skip to content

Course · OpenCode · Daily workflow

Prompt well and move at the speed of the keyboard

Watch yourself drive a coding agent for a stretch and the same frictions surface over and over: you paste a file path the agent could have found, you tab away to a terminal to run git log and paste the result back, you write three sentences of procedure when one sentence of outcome would have done it, and you ship a parser tweak on whatever model the session happened to open with. None of that is the tool fighting you - it’s missing reflexes. This lesson is about closing the gap between the thing you want and the keys you press to ask for it, with feedmill - the course’s illustrative Go feed aggregator - as the working repo (if you’re starting from scratch, install OpenCode first).

Describe the outcome, then get out of the way

Section titled “Describe the outcome, then get out of the way”

The single most common way to slow yourself down is to do the agent’s thinking out loud. Say feedmill’s dedupe is letting near-identical items through - the same article syndicated to two feeds with slightly different titles shows up twice in the reading queue. The slow way to ask is to narrate the fix:

> open internal/dedupe/hash.go, find the function that builds the key,
change it so it hashes a normalized title, lowercase it and strip
punctuation, then add a test...

You’ve just written the patch in prose. If you knew the fix that precisely you’d have typed it yourself. The reflex to build is the opposite - state the what and let the agent work the how, the same way you’d hand it to a teammate:

> near-duplicate items are getting through dedupe - the same article from
two feeds with slightly different titles shows up twice in the queue.
Tighten the matching so those collapse to one, and add a test that pins
the case that's leaking through now.

You didn’t name the file, the function, or the hashing strategy. You described the symptom and the outcome, and the agent does what it’s good at - reads the relevant code and works out the approach. Naming where the problem lives is still fine: if you already know the file, point at it with @ (next section), because location is context that saves the agent a search. What slows you down is dictating the fix step by step. Under default permissions the build agent (OpenCode’s default coding agent) then writes the change straight to disk; to see the approach before any edit lands, ask in plan (next section) or set edit to ask in opencode.json, the project’s config file, with "permission": { "edit": "ask" } (permissions documentation, as of October 2026). Outcome-first prompting isn’t just less typing; it lets the agent surface the right fix instead of faithfully implementing the half-formed one in your head.

The second reflex kills the tab-away habit. Two characters do almost all the work.

@ is a fuzzy file reference. Type @ and start typing a filename and OpenCode searches the working tree as you go; pick a match and that file’s contents go into the prompt. You don’t paste paths and you don’t make the agent guess which util.go you meant when there are five of them (here you already know the fix, so the prompt names it too):

> the Atom adapter in @internal/adapter/normalise.go copies each entry's
`updated` string through without converting it to UTC. Parse it with
parse.Date and cover it in a new internal/adapter/normalise_test.go.

! runs a shell command inline and feeds the output straight into the conversation as context. This is the one that ends the alt-tab dance - instead of switching to a terminal, running a command, and pasting the result back, you stay in the prompt:

> !git log --oneline -10
> something in the last few commits broke the sync server's /sync
endpoint. Look at those commits and find the regression.

The agent now sees the actual recent history, not your paraphrase of it. The same move works for !go test ./... to drop a failing-test wall in front of the agent, or !go run . -once to show it what a single sync actually does. Both are typed directly into the TUI (terminal user interface) prompt, with no slash command involved (TUI documentation, as of October 2026). Files in with @, reality in with !, both without your hands leaving the prompt - that’s the flow you’re protecting.

Plan, then build - Tab is the reflex, not a menu

Section titled “Plan, then build - Tab is the reflex, not a menu”

OpenCode’s two primary agents make planning cheap: plan is the restricted one, which by default denies file edits and leaves bash ungated, and build is the default coding agent with full tool access (the plan definition in agent.ts as of October 2026, v1.18.32, which this page follows; the agents documentation describes plan’s edits and bash as ask; the plan/build lesson covers the pair in depth). The reflex worth drilling is that switching between them is a Tab keypress mid-session - the agent_cycle action, bound to tab by default (as of October 2026; keybinds documentation) - not a thing you set up before you start. So the natural rhythm for anything bigger than a one-liner is: open in plan, ask for an approach, read it, then Tab into build and turn it loose - same session, so the plan it just wrote is the context it builds against. An illustrative exchange:

[plan] > the sync server has no scheduler yet - /sync only prints a
message. I want per-feed intervals so a fast news feed and a
daily digest don't poll at the same cadence. Outline the
change - don't edit yet.
⏷ read cmd/sync/main.go, internal/feed/store.go,
internal/sync/limiter.go (3 files)
Plan: add an interval column to the store, a new scheduler in
internal/sync that polls each feed on its own interval through
the existing Limiter, and wire it into cmd/sync/main.go. Three
files, one new, one schema change.
[Tab → build]
> good - do it.

The point isn’t that planning is mandatory. It’s that the cost of planning first dropped to one keystroke, so there’s no longer a reason to skip it on a change that touches three files. When the plan reads wrong, you Tab nowhere - you just correct it in plan, where the edit tools are refused. Bash is not gated there, so a shell command can still change files; for a strict read-only pass, set bash to ask or deny for plan under agent.plan.permission in opencode.json.

Pick the model for the task, not for the session

Section titled “Pick the model for the task, not for the session”

The last reflex is treating model choice as a per-task decision instead of a session default you forget about. OpenCode is provider-agnostic on purpose - /models switches the active model mid-session (TUI documentation, as of October 2026), <leader>m (the leader key, ctrl+x by default) pops the same picker without typing, and f2 cycles through your recently used models without opening the picker at all (model_list, model_cycle_recent; defaults as of October 2026, keybinds documentation). That’s a routine move, not a settings trip. The matching rhythm:

  • A wide refactor across feedmill’s parsers, or a gnarly concurrency bug in the sync server - reach for a stronger reasoning model and pay for it knowingly.
  • A rote chore - renaming a field across the tree, writing the obvious test, regenerating fixtures - drop to something cheap and fast. You don’t need a frontier model to rename a struct field.
> /models
(pick the cheap fast model)
> add table-driven tests for parse.Atom and parse.Date covering the
empty-feed and malformed-date cases.

What turns this from a guess into a decision is having the numbers. opencode stats reports token usage and cost across your sessions, so “the heavy model is where my spend goes” stops being a feeling and becomes something you can read (as of October 2026; CLI documentation). An illustrative summary:

$ opencode stats --models
Sessions 42 Tokens 8.1M Cost $14.20
By model
<stronger reasoning model> 6.2M $12.80
<cheap fast model> 1.9M $1.40

Glance at that regularly and the model reflex calibrates itself. The breakdown is per model, not per task, so it shows where the spend concentrates. If most of it sits on the expensive model, check whether rote work like that rename ran there out of habit. The numbers show model spend only. The cost principle also counts retries and your own repair time, so when the cheap model needs a second attempt or a manual fix on a kind of task, the stronger model’s capability is the cheaper buy for that task. Cheap-by-default, expensive-on-purpose, checked against the numbers and your retries - that’s the discipline.


Four reflexes, and none of them is a feature you didn’t already have - they’re the difference between knowing OpenCode and driving it: outcomes over steps, @ and ! to stay on the keyboard, Tab to plan-then-build, /models to spend where it pays. With the daily loop tight, the course’s synthesis page steps back from the keys to weigh what OpenCode distinctively offers and where to point it next - The OpenCode difference, and where to point it next.