How coding agents work: an interactive guide
Step through a real coding agent session and see what the model receives, why edits fail and what gets forgotten when the context window fills up

You type "the price test is failing, fix it". 30 seconds later the test passes.
In between, the agent ran the tests twice, searched the code, read a file and changed 1 line. It called the model 6 times. And the model never saw your repo once.
This guide takes a coding agent apart with small examples you can click through. It uses Claude Code, but Codex, Cursor and the rest are built the same way. If you've ever wondered why an agent reads 30 files before changing 1 line, or forgets what you told it an hour ago, this is why.

Contents

  1. It's a loop
  1. The model remembers nothing
  1. Tools are text in, text out
  1. The context window is the whole game
  1. What to do with this

It's a loop

A coding agent has 3 parts:
  • The model: Claude, GPT or another LLM, running on someone's servers. It can only read text and write text.
  • The harness: the program on your computer. Claude Code is a harness.
  • Tools: things the harness can do on your machine: read a file, run a command, search the code.
The model can't run anything. When it wants to, it writes a tool call: a tool name and arguments as JSON. The harness runs it and sends the result back as the next message. This repeats until the model answers without a tool call.
Press Next step and watch 1 bug get fixed.
{
"actor": "you",
"label": "The price test is failing, fix it",
"text": "Claude Code sends your message with its system prompt, the tool definitions and your CLAUDE.md",
"tokens": 17000
},
{
"actor": "model",
"label": "Runs the tests first",
"code": "Bash({ \"command\": \"npm test\" })",
"tokens": 60
},
{
"actor": "tools",
"label": "1 test fails",
"code": "FAIL src/price.test.ts\n formatPrice > shows cents\n Expected: \"$19.99\"\n Received: \"$19.9\"\n\nTests: 1 failed, 11 passed",
"tokens": 400
},
{
"actor": "model",
"label": "Looks for the function",
"code": "Grep({ \"pattern\": \"formatPrice\", \"path\": \"src\" })",
"tokens": 50
},
{
"actor": "tools",
"label": "Found in 3 files",
"code": "src/price.ts:1:export function formatPrice(cents: number) {\nsrc/cart.ts:14: return formatPrice(total);\nsrc/price.test.ts:4: expect(formatPrice(1999)).toBe('$19.99');",
"tokens": 120
},
{
"actor": "model",
"label": "Reads the file",
"code": "Read({ \"file_path\": \"src/price.ts\" })",
"tokens": 40
},
{
"actor": "tools",
"label": "The whole file comes back",
"code": "1 export function formatPrice(cents: number) {\n2 const dollars = cents / 100;\n3 return '$' + dollars.toFixed(1);\n4 }\n5 \n6 export function withTax(cents: number) {\n7 const dollars = cents / 100;\n8 return dollars * 1.2;\n9 }",
"tokens": 150
},
{
"actor": "model",
"label": "Changes 1 line",
"code": "Edit({\n \"filepath\": \"src/price.ts\",\n \"old_string\": \"return '$' + dollars.toFixed(1);\",\n \"newstring\": \"return '$' + dollars.toFixed(2);\"\n})",
"tokens": 90
},
{
"actor": "tools",
"label": "Edit applied",
"code": "The file src/price.ts has been updated.",
"tokens": 30
},
{
"actor": "model",
"label": "Checks its own work",
"code": "Bash({ \"command\": \"npm test\" })",
"tokens": 60
},
{
"actor": "tools",
"label": "All tests pass",
"code": "Tests: 12 passed",
"tokens": 80
},
{
"actor": "model",
"label": "Answers without a tool call, so the loop ends",
"text": "Fixed. formatPrice rounded to 1 decimal, so 1999 cents showed as $19.9. It now uses toFixed(2) and all 12 tests pass.",
"tokens": 70
}
]} />
Every amber step is a separate request to the model. 1 small bug, 6 requests.
That's all an agent is. Here is the whole loop, with the Anthropic SDK:
TypeScript

Loading editor...

import Anthropic from '@anthropic-ai/sdk';
import { tools, runTool } from './tools';

const client = new Anthropic();
const messages = [{ role: 'user', content: 'The price test is failing, fix it' }];

while (true) {
  const response = await client.messages.create({
    model: 'claude-sonnet-5-5',
    max_tokens: 8000,
    system: 'You are a coding agent. Use the tools to read, run and change code.',
    tools,
    messages,
  });
  messages.push({ role: 'assistant', content: response.content });

  // No tool call means the model has its answer
  if (response.stop_reason !== 'tool_use') break;

  const results = [];
  for (const block of response.content) {
    if (block.type !== 'tool_use') continue;
    const output = await runTool(block.name, block.input);
    results.push({ type: 'tool_result', tool_use_id: block.id, content: output });
  }
  messages.push({ role: 'user', content: results });
}
runTool is where the harness does its work: it runs the command, reads the file and returns the output as text. Claude Code has dozens of tools, permission prompts and a terminal UI around this loop. The loop itself doesn't change.

The model remembers nothing

The model doesn't keep anything between requests. Every request sends everything from the start: the instructions, every tool's definition, your CLAUDE.md and the whole conversation so far.
Switch between requests to see where the tokens go.
{
"label": "System prompt",
"color": "violet",
"icon": "scroll",
"tokens": 3000,
"note": "How to behave: tone, safety rules, when to use which tool, and your environment: the folder, the OS, the git status. The same in every request.",
"excerpt": "You are Claude Code, Anthropic's official CLI for Claude.\nYou are an interactive tool that helps users with software engineering tasks.\n...\nWorking directory: /Users/you/shop\nIs a git repository: true\nPlatform: darwin"
},
{
"label": "Tool definitions",
"color": "sky",
"icon": "wrench",
"tokens": 14000,
"note": "The name, description and JSON schema of every tool. Sent with every request, whether the model uses the tool or not. Every MCP server you connect adds its tools here.",
"excerpt": "{\n \"name\": \"Edit\",\n \"description\": \"Replace oldstring with new_string in a file. old_string must appear in the file exactly once.\",\n \"input_schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"file_path\": { \"type\": \"string\" },\n \"old_string\": { \"type\": \"string\" },\n \"new_string\": { \"type\": \"string\" }\n },\n \"required\": [\"file_path\", \"old_string\", \"newstring\"]\n }\n}"
},
{
"label": "CLAUDE.md",
"color": "amber",
"icon": "file",
"tokens": 1500,
"note": "Your notes about the project. Claude Code reads the file and puts it in every request.",
"excerpt": "# Shop\n\n- Run tests with npm test\n- Prices are stored in cents, never in dollars\n- Don't add new dependencies"
},
{
"label": "Conversation",
"color": "emerald",
"icon": "chat",
"tokens": [
20,
1200,
120000
],
"note": "Every message so far: yours, every tool call the model made and every result, including whole files and full test output. It only grows.",
"excerpt": "user: The price test is failing, fix it\nassistant: Bash({ \"command\": \"npm test\" })\nuser: FAIL src/price.test.ts ...\nassistant: Grep({ \"pattern\": \"formatPrice\" })\nuser: src/price.ts:1:export function formatPrice ...\nassistant: Read({ \"file_path\": \"src/price.ts\" })\nuser: 1 export function formatPrice(cents: number) { ..."
}
]} />
In request 1 the conversation is 20 tokens out of 18k. By request 40 it's 120k, and it's most of every request.
Sending the same 120k again and again sounds expensive. It's cheaper than it looks: the start of the request doesn't change, so the API caches it, and cached tokens cost 10% of the normal price. But cheap doesn't mean free, and everything still has to fit in the context window. We'll get to that.

Tools are text in, text out

A tool is a name, a description and a JSON schema for its arguments. Here is roughly what the model gets for the Edit tool:
JSON

Loading editor...

{
  "name": "Edit",
  "description": "Replace old_string with new_string in a file. old_string must appear in the file exactly once.",
  "input_schema": {
    "type": "object",
    "properties": {
      "file_path": { "type": "string" },
      "old_string": { "type": "string" },
      "new_string": { "type": "string" }
    },
    "required": ["file_path", "old_string", "new_string"]
  }
}
The model never opens a file. It writes the exact text to find and the text to put in its place. The harness looks for it, and refuses if the text isn't in the file exactly once.
Try it on src/price.ts from the session above. Pick a preset or type your own:
{
"label": "Copied from the file",
"old": "return '$' + dollars.toFixed(1);",
"new": "return '$' + dollars.toFixed(2);"
},
{
"label": "Too short",
"old": "const dollars = cents / 100;",
"new": "const dollars = Math.round(cents) / 100;"
},
{
"label": "From memory",
"old": "return \"$\" + dollars.toFixed(1);",
"new": "return \"$\" + dollars.toFixed(2);"
},
{
"label": "Wrong indent",
"old": " return '$' + dollars.toFixed(1);",
"new": " return '$' + dollars.toFixed(2);"
}
]} />
1 wrong quote and the edit fails. A string that's too short matches twice and fails too, because the harness won't guess which one you meant.
That's why agents read a file right before they edit it, even a file they read 10 minutes ago. Writing old_string from memory is a gamble. When an edit fails, the error goes back to the model as text, it reads the file again and retries. That's 2 more requests.

The context window is the whole game

Every model has a limit on how much text fits in 1 request: the context window. For Claude it's 200k tokens by default, about 150,000 words. That sounds like a lot until the agent starts reading. A 500-line file is 5-8k tokens. A verbose test run can be 20k.
When the window is nearly full, Claude Code compacts the conversation: the model writes a summary of everything so far, and the session goes on from the summary. The summary keeps the gist. Details are gone.
Work through the session below. Before it compacts, save to CLAUDE.md what you don't want to lose.
{
"label": "Read the bug report",
"tokens": 3000,
"fact": "Prices show 1 decimal, should show 2",
"keep": true
},
{
"label": "You: don't add new dependencies",
"tokens": 100,
"fact": "Don't add new dependencies",
"keep": false
},
{
"label": "Run the test suite",
"tokens": 12000,
"fact": "Tests run with npm run test:unit",
"keep": true
},
{
"label": "Read price.ts and 6 files that import it",
"tokens": 45000,
"fact": "withTax returns a number, not a string",
"keep": false
},
{
"label": "Run the tests again, 2,000 lines of output",
"tokens": 25000,
"fact": "3 tests in cart.test.ts fail too",
"keep": true
},
{
"label": "Read cart.ts and its tests",
"tokens": 35000,
"fact": "cart.ts rounds before it adds tax",
"keep": false
},
{
"label": "Try a fix, the tests still fail",
"tokens": 20000
},
{
"label": "Read the git history of price.ts",
"tokens": 25000,
"fact": "Rounding was changed on purpose in March",
"keep": false
}
]} />
The instruction you gave in passing, "don't add new dependencies", is 1 line in 180k tokens of file contents and test output. The summary has no reason to keep it. CLAUDE.md is read from disk and sent with every request, so it survives every compaction.

What to do with this

Once you see the loop, most agent advice is about the context window:
  • Put rules in CLAUDE.md, not in chat. Chat gets compacted. CLAUDE.md doesn't. Keep it short: it's in every request.
  • Start a new session for a new task. Run /clear in Claude Code. The old task's files and test output only take space.
  • Name the files. "The bug is in src/price.ts" saves a search and a few reads.
  • Let it run the tests. A loop that can check its own work fixes its own mistakes. Without tests it stops at "looks right".
  • Hand big searches to a subagent. A subagent is the same loop with a fresh, empty context. It reads 50 files and sends back 1 paragraph, and your main window gets only the paragraph.
For a map of everything else inside Claude Code, its 50+ tools, slash commands and features that haven't shipped yet, see Claude Code Unpacked. To extend the loop with your own tools and instructions, browse the MCP servers and skills directories.

Ship faster than
your competition

Focus on customers and sales while we handle product delivery. Hire a dedicated AI maker or a whole product team.

Launch new products
Fix your delivery
Hit fundraising milestones
[Analytics Chart]
Wireframe v0.1
User
Sarah Chen CEO, Founder
$127k
MRR
2,847
Customers
+23%
Growth
Sales Performance
Latest Deal
Customer
Enterprise plan closed
Acme Corp · $12k/mo
© 2026 Paralect, Inc 651 N Broad St, Suite 206, Middletown, 19709, Delaware, United States