Working with AI Coding Agents: Workflow, Context, and Guardrails
Get reliable results from AI coding agents: the explore-plan-implement-verify workflow, AGENTS.md project instructions, task briefs, permissions, hooks, and review.
On this page· How a coding agent works
Why this matters
Coding agents can read a codebase, plan a change, edit many files, and run your tests, often in minutes. Used carelessly, they produce large diffs that look right and are subtly wrong. Used well, they let one engineer move like a small team. The difference is not the model; it is the workflow around it: what context the agent gets, how the task is framed, how its work is verified, and how carefully you review. These are engineering skills, and they are now part of the job.
How a coding agent works#
A coding agent is the agent loop pointed at your repository. Its tools read and search files, edit them, and run commands in a terminal; some connect to more systems through MCP. It works until the task is done, a check fails in a way it cannot fix, or it needs a decision from you.
Hover or tap a component to see what it does.
Show text descriptionHide text description
The agent only knows what it can read or run. Good instructions and fast checks make it dramatically better.
- Your task: Goal, constraints, acceptance criteria, and how to verify.
- AGENTS.md: Commands, conventions, architecture notes, and rules, loaded into every session. The agent's onboarding doc.
- Coding agent: Reads, searches, edits, and runs commands in a loop until the task is done or it needs you.
- Codebase: The agent explores with file reads and search, the way you would, then edits files.
- Terminal: Runs your scripts. Permissions decide which commands run without asking.
- MCP tools: Optional connections to your issue tracker, documentation, or a development database.
- Diff: The output: a change set you review, test, and commit.
Everything the agent knows comes from what it can read or run. That gives you three levers: the instructions file it reads every session, the task brief you write, and the checks it can run to know whether it succeeded.
The workflow#
Hover or tap a component to see what it does.
Show text descriptionHide text description
Most bad agent output comes from skipping the first two steps or the fourth. Planning and verification are where you add the most value.
- Explore: The agent reads the relevant files and project instructions before changing anything. Ask it to explain what it found.
- Plan: A short plan: files to change, approach, risks, how to verify. The cheapest moment to correct course.
- Implement: Edits in small, reviewable increments, following the conventions in your instructions file.
- Verify: The agent runs the same checks CI runs. A clear, fast verification command is the biggest quality lever you control.
- You review: Read the diff like a teammate's pull request. You own the code that ships.
- Explore. Ask the agent to read the relevant code and explain how it works before changing anything. You catch misunderstandings when they are cheap.
- Plan. Ask for a short plan: files to touch, approach, risks, how it will verify. Correct it. For larger changes, save the plan in a file so it survives a long session.
- Implement in small steps, ideally one logical change at a time.
- Verify with the same commands CI runs: tests, type checking, lint. The agent should run them itself and fix failures.
- Review the diff as you would a teammate's pull request, then commit.
For small, well-understood changes (a typo, a rename, a new test case), skip straight to implementing. The workflow scales with risk.
Project instructions: AGENTS.md#
An instructions file in the repository root is loaded into every session. It is the onboarding document for your agent: how to build, test, and lint; conventions; architecture; and rules. Several tools read AGENTS.md; Claude Code reads CLAUDE.md, which can simply import the shared file with a line containing @AGENTS.md. (This wiki's own repository does exactly that.)
# Acme Support — agent instructions
## Commands
- Install: `npm ci`
- Dev server: `npm run dev` (http://localhost:3000)
- Verify everything before finishing a task: `npm run check` (types, lint, unit tests; ~40 s)
- One test file: `npx vitest run path/to/file.test.ts`
## Stack
Next.js App Router, TypeScript strict, Postgres with Drizzle, Anthropic SDK. Server code lives in `src/server/`; UI in `src/app/` and `src/components/`.
## Conventions
- Validate all external input with Zod at the boundary (`src/server/schemas.ts`).
- Model calls go through `src/server/llm.ts` (tracing, cost, retries). Never import the SDK directly elsewhere.
- Database access only through `src/server/db/queries.ts`; scope every query to the current user.
- Prefer server components; add `"use client"` only for interactivity.
## Rules
- Never commit secrets or read `.env*` files.
- Do not change database migrations that have already run; add a new migration.
- Ask before adding a dependency.
- Keep diffs focused on the task; propose unrelated cleanups separately.Keep it short and specific. Every line is read on every task, so state what the agent cannot infer from the code: commands, invariants, and the mistakes you have seen it make. When an agent repeats a mistake, add a line.
Writing a task brief#
Agents do what you ask, including the parts you did not mean. A good brief has the same parts as a good ticket:
## Goal
Customers can download all invoices for a year as one ZIP file from Billing → Invoices.
## Context
- Invoice PDFs are generated by `renderInvoicePdf()` in `src/server/invoices.ts`.
- The invoices page is `src/app/billing/invoices/page.tsx`.
## Constraints
- Stream the ZIP; do not hold all PDFs in memory (some customers have 3,000 invoices).
- Only the signed-in customer's invoices.
## Done when
- A "Download year (ZIP)" button appears per year.
- A route handler returns `application/zip` with one PDF per invoice.
- Tests cover: other customers' invoices are excluded; empty year returns 404.
- `npm run check` passes.
Start by exploring the code and proposing a plan. Don't write code until I approve it.The last line is the most important habit in this article: separate planning from implementation for anything non-trivial.
Permissions and safety#
A coding agent runs commands on your machine with your credentials. Configure what it may do without asking. In Claude Code, project settings in .claude/settings.json (checked in) set permissions for the whole team:
{
"permissions": {
"allow": ["Bash(npm run check)", "Bash(npm run test:*)", "Bash(npx vitest run:*)", "Bash(git diff:*)", "Bash(git status)"],
"deny": ["Read(./.env)", "Read(./.env.*)", "Bash(git push:*)", "Bash(npm publish:*)"]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "node .claude/hooks/lint-edited-file.ts" }]
}
]
}
}- Allow safe, frequent commands (tests, lint, read-only git) so the agent does not stop to ask every time.
- Deny reading secrets and anything irreversible or outward-facing (push, publish, deploy).
- Never give agents production credentials. Use a development database and test API keys.
- For untrusted code or long autonomous runs, use a sandbox: a container or a cloud environment with no access to your secrets.
Hooks: automatic checks after every edit#
The hooks entry above runs a script after every file edit. The script receives the tool call as JSON on standard input. If it exits with code 2, its error output is shown to the agent, which then fixes the problem immediately rather than at the end of the task:
import { spawnSync } from "node:child_process";
interface HookInput {
tool_name: string;
tool_input: { file_path?: string };
}
let raw = "";
for await (const chunk of process.stdin) raw += chunk;
const input = JSON.parse(raw) as HookInput;
const file = input.tool_input.file_path;
// Only lint source files; ignore everything else quietly.
if (!file || !/\.(ts|tsx|js|jsx)$/.test(file)) process.exit(0);
const result = spawnSync("npx", ["eslint", "--fix", file], { encoding: "utf8", shell: process.platform === "win32" });
if (result.status !== 0) {
// Exit code 2 sends stderr back to the agent so it can fix the problem right away.
console.error(`ESLint found problems in ${file}:\n${result.stdout}${result.stderr}`);
process.exit(2);
}Deterministic checks in hooks are more reliable than instructions: "always run the linter" in AGENTS.md is a request; a hook is a guarantee.
Reviewing agent output#
You are responsible for every line that ships. Review agent diffs with extra attention to the failure modes agents have:
- Plausible but wrong: code that reads well and handles the case the agent imagined, not the real one. Run it.
- Tests that test nothing: assertions that mirror the implementation, or mocks that make every test pass. Read the assertions.
- Scope creep: unrelated refactors mixed into the change. Ask for them separately.
- Silenced errors:
any,// @ts-ignore, emptycatch, disabled lint rules, skipped tests. Treat each as a question. - Invented APIs: a method that does not exist in your library version. Type checking and running the code catch most of these.
- Security shortcuts: missing authorization checks, unvalidated input, secrets in code.
If you would not approve the pull request from a colleague, do not approve it from an agent.
Where agents help most#
| Great fit | Use with care | Keep for yourself (for now) |
|---|---|---|
| Exploring an unfamiliar codebase | Large refactors (do them in planned stages) | Product and architecture decisions |
| Tests for existing behavior | Performance work (measure before and after) | Security-critical logic without expert review |
| Migrations, upgrades, boilerplate | Concurrency and distributed systems | Anything you cannot verify |
| Fixing failing CI with a clear error | UI polish (check it in a browser) | |
| Scripts, data transformations |
Common mistakes#
- One giant prompt, one giant diff. Break work into reviewable steps.
- No verification command. If the agent cannot run checks, it cannot know it succeeded, and neither can you.
- Skipping the plan. Most wasted agent time comes from a misunderstanding that a plan would have revealed.
- Accepting what you do not understand. Ask the agent to explain the change; if you still do not understand it, do not merge it.
- Letting your own skills fade. Read code, debug by hand sometimes, and stay able to judge the agent's work.
Try it
Run the full workflow on a real task
- Write an
AGENTS.mdfor one of your projects with commands, conventions, and three rules. - Write a task brief for a small feature with a "Done when" section, and ask a coding agent to explore and plan only.
- Correct the plan, let it implement, and review the diff with the checklist above. Note every issue you found and add one line to
AGENTS.mdthat would have prevented it.
Check your understanding
Key takeaways
- A coding agent is an agent loop over your repository; it knows only what it can read or run.
- Work in stages: explore, plan (and review it), implement, verify, review the diff.
- Give agents a concise instructions file, clear task briefs with "Done when", and fast verification commands.
- Guard with permissions, hooks, and sandboxes, and review agent output as strictly as any pull request.
Next steps#
Finished this article? Track it to see your progress on the roadmap.
Related articles
- Code Review, Testing, and Debugging with AI: A Practical GuideUse LLMs where they help most in daily engineering: an AI pull request reviewer in CI, tests written from specs, and a hypothesis-driven debugging loop with real checks.
- Build an AI Agent Loop from Scratch in TypeScriptWrite a production-ready agent loop in TypeScript: Zod-typed tools, turn, token, and time limits, parallel tool calls, error handling, events for your UI, and unit tests.
- Model Context Protocol (MCP): Build and Use MCP Servers in TypeScriptUnderstand MCP hosts, clients, servers, and transports, build a TypeScript MCP server with tools, resources, and prompts, and use it from Claude Code and your agents.