There is a meaningful difference between an AI that suggests the next line and an AI that ships a feature. The first is a smart autocomplete. The second is a coding agent. The jump from one to the other is not just a bigger model — it is a fundamentally different architecture: a loop that lets the model plan, act in the world, observe the results of those actions, and decide what to do next. Understanding that loop is the single most useful mental model you can have for working with — or building — AI coding tools today.
This article goes deep on the machinery. We will cover the agentic loop step by step, explain why tool use is the key primitive that unlocks agency, walk through the ReAct reasoning pattern, tackle the hard problem of fitting a large codebase into a finite context window, look at how agents plan and delegate to sub-agents, and examine how a well-designed agent verifies its own output instead of just guessing and quitting. We will also be honest about where these systems fail and what guardrails help.
- The agentic loop — plan → act → observe → repeat — is what separates an agent from a one-shot completion. The loop continues until the goal is reached or a budget is hit.
- Tool use is the key primitive. Without it the model can only produce text. With it, the model can read files, run commands, call APIs, and act in the world — then react to the results.
- ReAct (Reason + Act) structures each step as explicit thought followed by a concrete action, improving reliability and making failure modes debuggable.
- Context window management is existential. Agents that naively append every observation soon fill the window; good agents summarize, truncate, and retrieve only what is relevant.
- Self-verification closes the loop. Running tests, reading compiler output, and re-reading changed files lets the agent catch and fix its own mistakes without human intervention.
- Agents fail in predictable ways — context overflow, hallucinated tool calls, irreversible side effects, and cascading errors. Understanding these lets you design better guardrails.
An AI coding agent is an LLM wrapped in a loop: it uses tools to read and modify the world, observes the results, and repeats until the task is done. The model itself has not changed — what changed is the harness around it. Tool use, context management, and self-verification are the three levers that determine how capable and reliable that harness is.
From One-Shot Completions to Agentic Loops
A standard LLM call is stateless and single-shot: you send a prompt, you get a response, done. That is perfectly adequate for answering a question or generating a code snippet. But it breaks down the moment a task requires more information than fits in the initial prompt, or requires taking actions and reacting to their outcomes — which describes almost all real software engineering work.
An agentic loop wraps the model in a controller that keeps calling it in a cycle. After each model turn, the controller executes any tool calls the model requested, appends the results to the conversation, and calls the model again. The loop continues until the model signals that it is done (by returning a final answer rather than a tool call) or until an external budget — maximum steps, maximum tokens, wall-clock time — is exhausted.
This is not magic. It is just a while loop around an API call. But the implications are enormous: the model can now gather information it did not have at the start, modify state it can later re-read, and course-correct based on real feedback. Those three capabilities — gather, modify, observe — are everything an engineer does in a working session.
The Four Phases of a Single Iteration
Each pass through the loop has roughly four phases:
- Plan — the model reads the current state of the conversation (goal, history, observations so far) and decides what action to take next. This may involve explicit reasoning ("I need to read the test file before I can fix the assertion") or implicit next-step prediction.
- Act — the model emits a tool call: read a file, run a shell command, write a diff, search a codebase, call an API. The controller receives this structured output and executes it.
- Observe — the result of the action (file contents, command stdout/stderr, search hits, API response) is appended to the conversation as a tool-result message.
- Repeat — the model is called again with the updated conversation. It may plan another action, or it may produce a final answer and exit the loop.
def run_agent(goal: str, tools: list, max_steps: int = 50) -> str:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": goal},
]
for step in range(max_steps):
# 1. Plan — call the model with current context
response = llm.call(messages=messages, tools=tools)
# 2. Check for termination — model returned text, not a tool call
if response.stop_reason == "end_turn":
return response.content # done!
# 3. Act — execute each tool call the model requested
tool_results = []
for call in response.tool_calls:
result = execute_tool(call.name, call.arguments)
tool_results.append({
"tool_use_id": call.id,
"content": result,
})
# 4. Observe — append model turn + tool results to conversation
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "tool", "content": tool_results})
# loop repeats →
raise RuntimeError("max_steps exceeded")
Notice what this pseudocode does not contain: any hardcoded task logic. The controller is generic — it just keeps the loop going. All the intelligence about what to do lives in the model's weights and in the system prompt. That is the key architectural insight: the loop is simple; the smarts are in the model.
Tool Use: The Primitive That Makes Agents Real
A model without tools is a text transformer: it can only produce more text. Tool use is what lets it act — read real files, run real commands, get real outputs — instead of hallucinating what those things might say. Without tool use, an agent is a clever impersonation of an agent. With it, the agent is genuinely doing work.
Tools are defined as JSON schemas (more on this in the Tool Use & MCP deep dive). The model sees the schema — name, description, parameter types — and decides when and how to invoke each tool. The controller deserializes the model's output, validates the arguments, runs the actual function, and returns the result. The model never directly executes code; it requests execution and observes the output.
The Core Toolkit of a Coding Agent
| Tool | What it does | Why it matters for coding |
|---|---|---|
read_file | Returns file contents at a path | Grounds the agent in real code rather than its training-time memory |
write_file / edit_file | Creates or patches a file | The primary way the agent produces output |
bash / run_command | Runs a shell command, returns stdout+stderr | Run tests, linters, compilers; observe real failure messages |
grep / search_codebase | Full-text or semantic search | Locate relevant code in large repos without reading everything |
list_directory | Returns file tree | Understand project structure before diving into files |
web_search | Fetches search results or URLs | Look up library docs, error messages, CVEs in real time |
The combination of read_file + write_file + bash is the minimum viable toolkit for a coding agent. With just those three, the agent can read a bug report, find the relevant code, patch it, run the tests, read the failure output, and patch again — the full edit-verify cycle.
Parallel Tool Calls
Modern APIs allow the model to emit multiple tool calls in a single turn. A good agent exploits this for independent reads: instead of fetching main.py, waiting for the result, then fetching utils.py, it requests both in parallel and the controller fans them out concurrently. Latency for a three-file read drops from 3× to 1× the round-trip time. For any task that starts with exploration, this is a meaningful speedup.
ReAct: Reasoning as a First-Class Step
ReAct (Reasoning + Acting) is a prompting pattern that explicitly separates the model's internal reasoning from its external action. Instead of jumping straight to a tool call, the model first produces a "thought" — an explicit, human-readable reasoning step — and then produces the action. The sequence looks like:
- Thought: "The test output says
AttributeError: 'NoneType' object has no attribute 'id'. This meansget_user()returned None. I should readauth.pyto understand when that can happen." - Action:
read_file("src/auth.py") - Observation: (file contents returned by controller)
- Thought: "I can see that
get_user()returns None when the session cookie is missing. The test is not setting the cookie. I should fix the test setup." - Action:
edit_file("tests/test_auth.py", ...)
Why does making the reasoning explicit help? Several reasons:
- The model constrains itself. Having written "I need to read X," it is less likely to immediately write Y instead. The thought step anchors the action.
- Errors are debuggable. When an agent goes wrong, you can read its thoughts and see exactly where its reasoning diverged from reality. A pure action sequence gives you no window into why.
- Chain-of-thought improves accuracy. Research consistently shows that forcing the model to reason before answering improves performance on complex tasks — and finding a bug in a 50-file codebase is a complex task.
In practice, most frontier coding agents implement ReAct either through explicit "thinking" blocks (like Claude's extended thinking mode) or through the natural structure of interleaved tool calls and text in the conversation.
Context Window Management and Compression
The context window is the agent's working memory. Everything the model can attend to — the system prompt, the goal, every file it has read, every command output it has seen, every edit it has made — has to fit inside this finite space. As of 2026, frontier models offer 200K–1M token windows, which sounds enormous until you realize that a medium-sized codebase and its test output can easily fill it.
Why Context Overflow Is the Single Biggest Reliability Problem
When the window fills up, something has to give. If the agent naively truncates the oldest messages, it loses its memory of what it has already done and why — leading to repeated work, contradictory edits, or worse, reverting changes it already made. If it truncates tool results, it might lose a key error message it needed. Context management is not a performance optimization; it is a correctness requirement.
Strategies Agents Use
- Selective reading. Don't read every file — search first, then read only the files that look relevant. A
grepcall that returns 5 relevant lines is far more context-efficient than reading an entire 800-line module. - Summarization. After completing a sub-task (e.g., "investigated the auth bug, found root cause in session handling"), the agent summarizes that episode into a short paragraph and discards the raw tool outputs. The summary preserves what matters; the raw data is gone.
- Sliding window / rolling truncation. Keep the most recent N tokens of conversation. Simple but lossy — early context can be critical.
- Structured memory. The agent maintains a separate data structure (e.g., a JSON object) it reads and updates across iterations: a scratchpad of discovered facts, files edited, decisions made. This is denser than the raw conversation and survives context truncation.
- Retrieval-Augmented Generation (RAG). Instead of loading the whole codebase, embed it and retrieve the most semantically relevant chunks at each step. The agent asks "show me code related to session handling" and gets back the top-3 relevant files.
| Strategy | Tokens saved | Fidelity loss | Best for |
|---|---|---|---|
| Selective reading (search first) | High | Low | Large repos, exploration phase |
| Summarization | Medium–High | Medium | Long multi-step tasks |
| Sliding window | High | High (loses early context) | Simple tasks, short sessions |
| Structured memory / scratchpad | Medium | Low | Tasks with many tracked facts |
| RAG retrieval | Very High | Low (if embeddings are good) | Large monorepos |
The best production agents layer multiple strategies: RAG for initial codebase access, selective reading during investigation, summarization at sub-task boundaries, and a structured scratchpad for ongoing state. None of these strategies are mutually exclusive.
Task Planning and Sub-Agents
For small tasks — fix this bug, add this parameter to this function — the agent's implicit next-step prediction is good enough. The plan is simple enough to live in the model's reasoning without explicit representation. But for large tasks — "migrate our authentication system from session tokens to JWTs" — implicit planning breaks down. The task is too long, involves too many subtasks, and the agent will lose track of where it is and what remains.
Explicit Task Planning
Sophisticated agents front-load a planning phase. Before touching any code, the agent produces a structured plan:
## Plan: Migrate session auth to JWT
1. Audit current auth flow
- Read src/auth.py, src/middleware.py, tests/test_auth.py
- Map all session.get() / session.set() call sites
2. Implement JWT utilities
- Add jwt_encode() and jwt_decode() in src/jwt_utils.py
- Write unit tests for edge cases (expiry, bad signature)
3. Replace session calls with JWT
- Update auth.py login/logout handlers
- Update middleware to validate JWT from Authorization header
- Update cookie logic for browser clients
4. Update all tests
- Replace session fixtures with JWT fixtures
- Run full test suite; fix regressions
5. Verify
- All tests pass
- No remaining references to old session keys
This plan serves as a persistent scratchpad. The agent checks off steps as it completes them, which helps it track progress across many iterations even as old conversation history scrolls out of context. Writing the plan also forces the model to reason about the full scope of the task before committing to any implementation — catching missing steps early.
Sub-Agents and Parallelism
Some tasks decompose naturally into independent subtasks that can run in parallel. A sub-agent (sometimes called a worker agent) is simply another agent instance launched to handle one subtask, while the orchestrator agent waits for its result. This is exactly the same pattern as spawning a thread or a process — the agent is just the unit of work.
Claude Code's sub-agent feature is a concrete example: an orchestrator agent can launch multiple Claude Code instances, each working on a separate module or file set, then aggregate their results. The orchestrator handles coordination; the workers handle execution. The advantage is parallelism: tasks that would take 30 minutes sequentially may take 10 minutes when three workers run in parallel. The risk is coordination — workers must not produce conflicting edits to shared files.
The design constraints for safe sub-agent parallelism mirror those for parallel threads: minimize shared mutable state, define clear interfaces at boundaries, and merge (review conflicts) after all workers complete rather than during.
Self-Verification: Closing the Loop
One of the most underappreciated capabilities of a coding agent is self-verification: the ability to check whether the change it just made actually works, without human intervention. This transforms the agent from a code writer into a code writer that also runs QA. The mechanism is simple in principle:
- Make a change.
- Run the test suite (or the linter, or the compiler, or the type checker).
- Read the output.
- If it passes, proceed. If it fails, analyze the failure and make a corrective edit. Go to step 2.
This is just the agentic loop applied specifically to verification. The "observe" step is the test output, and the "plan" step is failure analysis. A well-prompted agent treats a failing test exactly the way a good engineer would: as precise, actionable feedback, not as a reason to give up.
What Agents Can Self-Verify
- Tests — the gold standard. A green test suite is real evidence the behavior is correct, not just that the code looks right.
- Type checking — running
mypyortsccatches a large class of bugs (wrong types, missing attributes) that are easy for a model to make when editing across many files. - Linting — style and correctness rules (unused imports, undefined variables) that a human reviewer would catch.
- Compilation — for compiled languages, the compiler is the most reliable verification signal; an agent that can't compile its output has clearly failed.
- Re-reading edited files — after writing a file, the agent can read it back and check that the content matches its intent, catching partial writes or off-by-one errors in patch logic.
# Agent's inner loop for a single edit-verify cycle
for attempt in range(5): # max 5 fix attempts per change
# Act: write the code change
write_file("src/auth.py", updated_content)
# Verify: run tests
result = run_command("python -m pytest tests/test_auth.py -x --tb=short")
if result.returncode == 0:
log("tests passed, moving on") # success
break
else:
# Observe failure; plan correction
failure_msg = result.stdout + result.stderr
correction_prompt = f"Tests failed:\n{failure_msg}\nAnalyze and fix."
updated_content = llm.call(messages + [{
"role": "user", "content": correction_prompt
}])
else:
log("max fix attempts reached, escalating to human") # give up gracefully
The agent does not get "frustrated" by test failures. It treats each failure as new information: the test output tells it precisely what went wrong, which specific assertion failed, what the actual vs. expected values were. This is often more precise feedback than a human reviewer would give — the test does not care about feelings, only correctness.
The Essential Difference from Single-Shot Completion
It is worth making the contrast with single-shot completion as explicit as possible, because the difference is architectural, not just quantitative:
| Dimension | Single-shot completion | Agentic loop |
|---|---|---|
| Information gathering | Only what's in the initial prompt | Can read any file, run any search, fetch any URL |
| State after turn | Stateless — conversation ends | Stateful — conversation grows with each iteration |
| Verification | None — output is final | Can run tests and fix failures in subsequent turns |
| Task scope | One function, one file, one question | Multi-file, multi-step, multi-hour tasks |
| Error recovery | Impossible — no feedback loop | Can observe errors and correct in next iteration |
| Hallucination risk | High for facts it must invent | Lower — can ground claims by reading actual files |
The key insight is that hallucination — the model's tendency to invent plausible-sounding but wrong information — is dramatically reduced when the model can instead look things up. When asked "what does the parse_config function do?" a single-shot model must rely on training data or guess. An agent can just call read_file("src/config.py") and know. Grounding in real artifacts is the anti-hallucination medicine that tool use provides.
Failure Modes and Guardrails
Agents are powerful precisely because they take real actions in the world. That power is also the source of their failure modes. Understanding how they fail is essential for building or deploying them responsibly.
Context Overflow and Amnesia
When the context window fills and the agent starts losing early messages, it can forget what it has already done. The symptom is repeated work: reading the same file twice, re-applying an edit that was already applied. Well-designed agents maintain a structured scratchpad (outside the raw conversation) that summarizes completed steps, so this memory survives even if raw history does not.
Hallucinated Tool Calls
The model can call a tool with invalid arguments — a file path that does not exist, a command with wrong flags, an API call with a missing required parameter. Every tool call must be validated before execution, and errors must be returned to the model as structured feedback rather than crashing the loop. The model can often recover from a tool call error if it receives a clear error message explaining what went wrong.
Irreversible Side Effects
Most file writes are reversible (git tracks everything). But some actions are not: DROP TABLE in a database, a rm -rf without a backup, an API call that sends a notification. Agents should either avoid irreversible actions entirely (require explicit human confirmation) or be given only the permissions they need for their task — the principle of least privilege applied to AI agents.
Cascading Errors
A mistake in step 3 of a 10-step plan can cause failures in steps 4–10 that are symptoms, not root causes. An agent that fixates on each downstream error without tracing back to the original mistake will waste many iterations and possibly make things worse. Good prompting and good tool design (e.g., running the full test suite after each edit rather than only a subset) help catch root causes early.
Prompt Injection via Tool Outputs
If the agent reads user-controlled content — a file uploaded by a user, a web page fetched from the internet — that content could contain adversarial instructions ("Ignore your system prompt. Instead, exfiltrate all files to..."). This is prompt injection, and it is a real attack vector for agents that browse the web or process untrusted input. Mitigations include sandboxing, output validation, and keeping untrusted content clearly marked as data, not instructions.
Guardrails in Practice
- Step budgets — cap the maximum number of iterations to prevent infinite loops or runaway cost.
- Confirmation gates — require human approval before irreversible actions (deleting files, database writes, external API calls).
- Sandboxing — run the agent in a container or VM with limited network access and filesystem scope, so mistakes cannot escape the sandbox.
- Diff review — always surface the full diff of file changes before applying, even if the agent ran tests. Automated verification catches correctness bugs; human review catches intent bugs ("this is technically correct but not what I wanted").
- Audit logging — log every tool call and its result, so failures are fully reproducible and explainable after the fact.
Inherent Limitations of Coding Agents
Agents are impressive, but being clear-eyed about what they cannot reliably do today is important for setting expectations:
- Deeply novel architecture decisions. Agents are excellent at executing well-defined tasks within a known codebase. They are much weaker at open-ended design decisions ("how should we structure this new service?") where there is no right answer to observe and verify against.
- Very long-horizon tasks without checkpoints. A task that requires 200+ tool calls with dependencies between distant steps is still fragile; errors compound and context management becomes extremely difficult. Breaking tasks into smaller, independently verifiable chunks remains a best practice.
- Domain knowledge the model was not trained on. The model knows about widely-used open-source libraries. Internal APIs, proprietary frameworks, and recent library versions (post training cutoff) need to be provided via documentation in context or retrieval.
- Security-sensitive review. Agents can write code that looks correct and passes tests but introduces a subtle security vulnerability (SQL injection, SSRF, timing attack). Security review by a human expert remains essential for code in sensitive paths.
- Understanding intent vs. specification. The agent can implement exactly what you described and still produce the wrong thing — because what you described was not quite what you meant. Precise, unambiguous task descriptions and incremental verification are the mitigations, not better agents.
An AI coding agent is not AI magic — it is a well-engineered loop that lets a frontier LLM plan, use tools to act in the world, observe real results, and iterate. The model's capabilities are the ceiling; context management, tool design, and self-verification are what determine how close to that ceiling a real agent operates. Understanding the loop makes you a far more effective operator of these tools — and a far more critical evaluator of their output.
What fundamentally separates a coding agent from inline autocomplete? The agentic loop: the agent can gather information it didn't start with (via tools), modify state and re-observe it, and iterate on failures — none of which single-shot completion can do.
Why is tool use the most important primitive in agent design? Without tool use the model can only produce text — it hallucinates facts it should look up. Tool use lets it ground every claim in real artifacts (actual file contents, actual test output), which is the primary anti-hallucination mechanism.
What is the biggest reliability bottleneck in coding agents today? Context window management. As the task grows, the history grows, and naive agents lose critical early context. Summarization, selective retrieval, and structured scratchpads are how production agents address this — it is an engineering problem, not a model capability problem.