How to Code with AI
Most advice about coding with AI stops at "use a good prompt." That is not the hard part. The hard part is building a loop where the AI's output gets checked by something other than your optimism — tests, types, a diff you actually read.
This guide covers that loop. It uses OpenCode for the examples because it is open source and works with any model, but the workflow transfers to any agent.
What changed: from autocomplete to agents
Three generations of tooling, and they ask different things of you.
Autocomplete predicts the rest of your line. You stay in control of structure and it saves keystrokes. Low risk, low ceiling.
Chat generation produces a block of code you paste in. Higher ceiling, and the first point where a subtle bug can slip past because you did not write the code and are reading it out of context.
Agents take a task, explore your codebase, edit several files, run your tests, read the failures, and try again. The ceiling is much higher — real tasks get finished — and the risk shifts. The agent will hand you a diff that passes tests and still be wrong about intent.
The practical implication: as tools get more autonomous, your job moves from writing code to specifying and reviewing it. If your review process is weak, more autonomy makes things worse, not better.
Step 1 — Choose a model, not just a tool
Model quality dominates the outcome. The same agent with a frontier model versus a small one is barely the same product.
A workable default: use a flagship model for anything involving architecture, unfamiliar code, or multi-file changes, and a cheaper model for mechanical work like renames, test scaffolding, and boilerplate. The cost difference between models is often 10-20x while the quality difference on simple tasks is nearly zero.
This is the main argument for a provider-agnostic agent. OpenCode connects to 75+ providers and switching models mid-session is one command, so you can match model to task instead of paying flagship prices for renames. See the Fable 5 setup guide for a concrete walkthrough with Claude Fable 5, Sonnet 5, and GPT-5.6, or OpenCode Zen if you want a curated list including free models.
If cost is the binding constraint, start with free options — several are good enough to learn the workflow on.
Step 2 — Use an agent, not just a chat window
Copy-pasting between a chat tab and your editor loses the thing that makes AI coding work: feedback. An agent that can run your test suite gets to find out it was wrong and fix itself. A chat window never finds out.
Set up matters less than people expect — installing OpenCode takes about 30 seconds — but two settings are worth getting right on day one:
- Permissions. Start with bash set to
askso every shell command waits for approval. Once you trust the loop, allow specific safe commands by pattern (git status *,npm test *) and keep the rest gated. Never start with everything allowed. - MCP servers. Only if you need them. Each one adds tool schemas to every request, so a dozen servers eat your context window before work starts.
Step 3 — Write down your conventions
This is the single highest-leverage thing in this guide and most people skip it.
Without instructions, a model writes the statistically most common code on the internet for your language. That is why generated code so often looks foreign in your repo — wrong error handling, a library you removed two years ago, tests in a style you abandoned.
Fix it with a rules file (AGENTS.md) at your project root. Keep it short and specific:
## Conventions
- TypeScript strict mode. No `any`, no `@ts-ignore`.
- Use the existing `Result<T>` type for fallible operations, not exceptions.
- Tests use Vitest with the helpers in `test/support/`. Do not add new test utilities.
## Do not
- Do not add dependencies without asking.
- Do not edit anything under `generated/`.Vague rules ("write clean code") do nothing. Rules that name a type, a directory, or a banned pattern change the output immediately. AGENTS.md is also read by several other agents now, so the file survives a tool switch.
For rules that must hold across a whole organisation and cannot be loosened per project, use policies instead.
Step 4 — Give the task the right size
The most common cause of bad AI output is a task that was too big or too vague.
Tasks that go well share a shape: a clear definition of done, and a way for the agent to check itself. "Make the failing test in auth.test.ts pass" works. "Refactor the auth module" does not — there is no finish line and no verification.
Three patterns that hold up:
- Test-first. Write the failing test yourself, then hand it over. You have specified the behaviour precisely and the agent knows when it is done.
- One concern at a time. A diff that changes one thing is reviewable. A diff that renames variables and changes logic hides the logic change.
- Explore before editing. For unfamiliar code, ask the agent to explain the flow first, then make the change. If its explanation is wrong, you found out cheaply.
Step 5 — Review every diff
Non-negotiable. AI-generated code that passes tests can still be wrong: right behaviour, wrong approach; a silent behaviour change in a path no test covers; a dependency added to solve something your codebase already handles.
What to look for, in order of how often it bites:
- Scope creep. Did it change files outside the task? This is the most common problem and the easiest to catch.
- Silent fallbacks. Empty catch blocks, defaults that mask failures, retries that hide a real error. Models add these to make tests pass.
- Duplicated logic. Did it write a helper that already exists three directories over?
- Test quality. Tests that assert the implementation rather than the behaviour will pass forever and catch nothing.
Committing frequently makes this tractable — small diffs get read, large ones get skimmed. Formatters remove style noise from the diff so you are reading logic, not whitespace.
Where intelligent code generation actually fails
Being specific about failure modes is more useful than a disclaimer.
Context it cannot see. Why a workaround exists, which of two callers matters, what the customer actually asked for. The model will confidently fill this gap with a plausible guess.
Confident wrong APIs. Models invent method signatures for libraries they half-remember, especially for recent versions. Types and a build step catch most of this, which is why typed languages have a real advantage here.
Large refactors across many files. Consistency degrades as the change spreads. Break it up and verify each step.
Anything where being subtly wrong is expensive. Auth logic, money handling, migrations, permission checks. Use AI to draft and explain, then review as if a stranger wrote it — because one did.
Debugging without a reproduction. An agent with a failing test is effective. An agent guessing at an intermittent production bug generates plausible-looking noise. Get a reproduction first.
Automating the parts worth automating
Once the interactive loop is reliable, some of it can run without you.
Code review on pull requests. An agent reading a diff and flagging problems is a genuinely good fit — the task is bounded, the input is small, and a false positive costs nothing but a glance. OpenCode runs in CI through its GitHub and GitLab integrations.
Repetitive migrations. Renaming an API across 200 files, updating a deprecated call pattern, adding types to untyped modules. Mechanical, verifiable, and tedious enough that humans make mistakes machines do not.
Test backfill. Pointing an agent at an untested module and asking for coverage works well, with one caveat: review the assertions. Tests that pin the current implementation rather than the intended behaviour are worse than no tests, because they break on every legitimate refactor.
What not to automate: anything where you would not review the output. An unreviewed agent commit is a change nobody understands, and it stays that way until it breaks.
Working this way on a team
Two things change when more than one person is involved.
The rules file becomes shared infrastructure. AGENTS.md belongs in version control and deserves review like any other config. When one person discovers that the agent keeps making the same mistake, the fix should land for everyone.
Review standards need to be explicit. "AI wrote it" is not a review exemption, and it is worth saying out loud, because the temptation runs the other way — a diff that passes CI and came from a tool feels pre-validated. It is not. Whoever opens the pull request owns the code in it.
Worth agreeing on early: whether AI-assisted commits get marked as such. There are arguments both ways, but ambiguity is worse than either choice.
A loop that works
Pulling it together:
- Write
AGENTS.mdonce. Update it when the agent gets something wrong for a reason you can name. - Pick a bounded task with a verifiable finish line.
- Let the agent explore before it edits.
- Read the diff. Reject scope creep.
- Commit small. Run the full suite before moving on.
- When output is consistently wrong in the same way, fix the rules file rather than re-prompting.
That last point is what separates people who get value from AI coding from people who fight it. Repeated failures are usually a missing instruction, not a bad model.
Keep reading
- Best AI for coding in 2026 — nine agents ranked, with stated criteria
- Free AI coding agents — what "free" actually means, tool versus inference
- OpenCode vs Claude Code · OpenCode vs Cursor
- All guides