AI Agents Intermediate ⏱ 30 min AI agentsagent looptool useLLMpermissionssub-agentsagentic codingarchitecture

Anatomy of an AI Coding Agent

Published 2026-07-10 — Dr Neal Aggarwal
📚 Lessons series — #3. This lesson assumes you understand what a neural network is and roughly how an LLM predicts text. If you don't, start with Lesson 1 and Lesson 2, then come back. The full curriculum lives on the Lessons page.

Anatomy of an AI Coding Agent

In the first two lessons we went down to the metal: what a neural network actually computes, and how gradient descent tunes it. This lesson goes the other direction — up the stack — to answer a question I kept asking myself when I first watched a coding agent refactor one of my Flask projects on its own: what is this thing, structurally?

Not "how does the model work" — we covered that. The question is: what is wrapped around the model to turn a text predictor into something that reads your files, runs your tests, notices the tests failed, fixes the bug, and runs them again — without you touching the keyboard?

It turns out the answer is surprisingly compact. Strip away the polish and every serious coding agent — and there are now several in production use — is the same small set of parts arranged the same way. Once you can see those parts, agents stop being magic and start being engineering. That's the goal of this lesson.

🎧 Listen to this post: The six organs of an autonomous agent — an audio companion to this lesson for the commute.

🎬 Watch the video overview: Anatomy of an AI agent in eight minutes — the visual companion to this lesson.

A program that writes its own instructions

Start with what an agent is not.

A classic command-line program is a fixed function. You give grep a pattern and a file; it searches and exits. It never decides that, actually, what you really needed was to also edit the file. The sequence of operations is frozen at the moment the programmer wrote it. That's the contract we've had with software for seventy years: the instructions are decided before the program runs.

An agent tears that contract up. You hand it a goal in plain English — "add error handling to the login function" — and the sequence of operations is invented at runtime by a language model. The model reads the goal, decides it should first look at the file, then reads the result of that action, decides what to do next, and so on until it judges the job done.

Here's the reframe that made it click for me, as someone who has written a lot of conventional code:

💡 Key idea. In an agent, the LLM's output is the control flow. The "program" is just a loop that keeps asking the model "what next?" and executing whatever it answers. Everything else — the file access, the shell, the safety rails, the memory — is plumbing around that loop.

That loop has a name in the trade: the agent loop (you'll also hear "query loop" or "orchestration loop"). It's worth staring at, because it is genuinely the whole trick:

THE AGENT LOOP Your goal plain English Model thinks reads history → replies Any actions requested? Run the tools read / edit / shell / search Results added to the transcript Done ✓ final answer shown yes no The loop repeats — often dozens of times — until the model stops asking for actions.
Figure 1. The agent loop. The model never "does" anything itself — it only writes requests. The loop executes them, feeds the outcomes back in, and asks again. Termination is the model's choice: a reply containing no action requests ends the cycle.

Notice what's absent from Figure 1: there is no branch for "handle tool results". Results are simply appended to the running transcript and the model is called again with the longer transcript. The model sees what happened and reacts. That re-entrancy — one loop, no special cases — is what makes agents feel coherent across twenty-step tasks.

And notice how the loop ends. Nobody signals completion. The model just… stops requesting actions, and writes a final answer instead. The stop condition is a behaviour, not a flag. (Production agents add hard backstops — a turn limit, a token budget, a user abort — because a model that never stops requesting actions would loop forever and bill you for the privilege.)

The six building blocks

If the loop is the heart, what's the rest of the organism? Having pulled apart several of these systems, I keep finding the same six organs, whatever the codebase calls them. Learn these six and you can navigate any agent's source — or design your own.

SIX BUILDING BLOCKS Agent Loop 1 · Interface where you type & watch 2 · Toolbox every action it can take 3 · Sub-agents loops inside the loop 4 · State session facts & UI data 5 · Memory notes that survive sessions 6 · Hooks your rules, auto-enforced Everything else in a production agent — renderers, cost meters, config — exists to serve these six.
Figure 2. The six recurring components of a production coding agent, arranged around the loop they all serve. The names vary between products; the roles don't.

1. The interface. The surface you interact with — a terminal UI, an editor pane, a chat window. Its only real job is to feed your input into the loop and stream the loop's output back to you. A well-built agent keeps this layer thin, which is why the same core can drive a terminal, an IDE plugin, and a headless script-mode with no changes underneath.

2. The toolbox. A tool is any action the model can request: read a file, write a file, run a shell command, search the web, grep the codebase. The design insight that separates good agents from fragile ones is that a tool is not just a function. Each tool carries its own metadata: a schema describing its inputs, a declaration of whether it's safe to run in parallel with others, its own permission rules, and instructions for how its output should be displayed. The tool describes itself; the loop stays generic. Add a forty-first tool and you touch zero lines of orchestration code. Compare that with the alternative — a central dispatcher with a growing if/else chain that "knows about" every tool — and you can see why self-description wins as systems grow.

3. Sub-agents. Here's the recursive move: one of the tools in the toolbox is "spawn another agent". The child gets its own fresh transcript, its own (possibly restricted) toolbox, and runs its own copy of the same loop. The parent sees only the child's final report. This is how an agent parallelises a big refactor or delegates "go research how this library works" without polluting its own context window. The crucial design rule: a sub-agent is the same loop, not a special-cased mini-version — so it inherits every guarantee (permissions, limits, error handling) automatically. Sub-agents live through a simple lifecycle — queued, running, then finished, failed, or cancelled — which the parent can inspect at any time.

4. State. Agents keep two very different kinds of state, and mature systems keep them physically separate. First, session infrastructure: working directory, which model is in use, accumulated cost, session ID. Set once at startup, read constantly, changed rarely — a plain mutable object is fine. Second, live UI state: the message stream, pending approvals, progress spinners. This changes many times a second and must push updates to the screen, so it lives in a small reactive store. Mixing the two is a classic beginner error: make everything reactive and you pay subscription overhead on data that changes once; make nothing reactive and your UI goes stale.

5. Memory. The context window is amnesiac — wipe the session and the agent forgets your project's conventions, your preferred test framework, that painful debugging discovery from last Tuesday. Memory fixes this with plain markdown files at several scopes: per-project notes checked into the repo, personal notes in your home directory, shared notes for a team. The elegant part is retrieval: at session start the agent doesn't dump every note into context (that would burn the window). Instead a cheap model call skims the available notes and selects only those relevant to the current task. Memory is, in effect, a tiny retrieval-augmented system bolted onto the agent — same principle as RAG in clinical NLP, applied to the agent's own notes-to-self.

6. Hooks. User-defined tripwires at fixed points in the lifecycle: before a tool runs, after it finishes, when the loop is about to stop, and a couple of dozen more. A hook can be a shell script, a one-shot LLM check, or a webhook — and it can block the action, rewrite its inputs, inject extra context, or halt the whole loop. Hooks are how you encode "never touch the production config" or "always run the linter after edits" as enforced policy rather than a polite request in a prompt. That distinction matters more than it looks: prompts are suggestions the model usually follows; hooks are code, and code doesn't get creative.

⚕️ A medical analogy that holds up well: the loop is the cardiac cycle; the toolbox is the motor system; state is the working memory of the moment; memory files are the patient record; hooks are the spinal reflexes that act before conscious thought; and sub-agents are the referral — sending a well-defined question to a colleague and getting back a report, not their whole thought process.

The life of a single request

Abstractions are only useful if you can follow a concrete case through them. So: you type "add error handling to the login function" and press Enter. What actually happens?

ONE REQUEST, STEP BY STEP t=0 done 1 · Housekeeping. Transcript too long? Summarise older turns first ("compaction"). 2 · Model call. Full transcript sent; the reply streams back token by token. 3 · Eager start. Mid-stream, a read-only request appears — e.g. "open login.py". Safe tools start while the model is still talking. Reads overlap the stream. 4 · Remaining actions run. Parallel-safe ones together; risky ones one at a time. Each action passes: validate → hooks → permission check → execute. 5 · Feedback. All results are appended to the transcript as new messages. 6 · Loop test. More actions requested? → back to step 2 with the longer transcript. 7 · Finish. No actions requested — the reply is the answer. The loop reports why it ended. Steps 2–6 typically repeat several times per user request. Step 3 is the speed trick most people never notice.
Figure 3. The lifecycle of one request. The green band at step 3 is the subtle part: tool execution and model streaming overlap in time rather than strictly alternating.

Three details in that timeline repay attention.

The loop is a producer you pull from, not a callback that pushes at you. Implementation-wise, the loop is typically written as a generator — a function that yields a stream of messages which the interface consumes at its own pace. If the UI falls behind, the generator naturally pauses; if you hit Escape, the consumer simply stops pulling and the loop winds down cleanly. And when the loop finishes, it returns a typed reason: completed normally, aborted by user, ran out of token budget, hit the turn limit, or halted by a hook. Compare that to a tangle of callbacks where "why did it stop?" is a debugging session. This is a beautiful example of a general lesson: choose the language construct whose shape matches your problem, and half your edge cases evaporate.

Speculative tool execution. In step 3, the agent starts running read-only tools before the model has finished its sentence. By the time the model's reply is complete, the file contents it asked for may already be sitting in hand. It's a small gamble — very occasionally the rest of the model's output invalidates the request and the result is thrown away — but reads are cheap and latency is the thing users feel most. Anaesthetists will recognise the pattern: you draw up the likely drugs before you're certain you'll need them, because the cost of being wrong is small and the cost of waiting is not.

No separate "result-handling" phase. Step 5 is just an append. The intelligence about what the results mean lives entirely in the model, which reads them on the next pass. The plumbing stays dumb; the model stays smart. Whenever you find yourself writing clever result-interpretation logic in the loop itself, you're probably duplicating — badly — something the model would do better.

The safety problem: permissions

Everything above should make you slightly nervous. We've built a system where a stochastic text generator decides which shell commands run on your machine. It can edit files, hit the network, and rewrite git history. The gap between "useful colleague" and "rm -rf incident" is exactly one badly-chosen tool call wide.

The answer every mature agent converges on is a permission system, and its two components are worth understanding because you'll be configuring them, not just admiring them.

First, modes — named postures that set the overall trust level for a session. Rather than sprinkling ad-hoc if allowed checks through every tool, the agent resolves every action through the currently active mode. Typical postures, from paranoid to reckless: a read-only/planning mode where all mutations are blocked (lovely for "look at my codebase and propose a plan" sessions); a default interactive mode where each risky action needs your explicit yes; an auto-accept-edits mode where file edits sail through but shell commands still prompt; an auto mode where a second, lightweight LLM reviews each proposed action against the conversation and approves the ones consistent with what you asked for; and an unrestricted mode for sandboxes and CI, which you should treat like an unblinded trial — legitimate, but only in controlled conditions.

Second, the resolution chain — the fixed order in which an individual action gets judged:

WHO DECIDES? — THE PERMISSION CHAIN ① Your hooks Hard rules fire first. A match settles it — no appeal. ② The tool itself Each tool knows its own risk. It may allow, deny, or say "ask". ③ The active mode Session posture decides how "ask" cases are handled. ✓ Allowed Runs immediately. Still logged. ? You're asked Approve once, for the session, always — or refuse. ✗ Blocked The model is told no — and must plan around it. Sub-agent rule: children can't approve their own risky actions. Requests escalate upward — to the parent agent, and ultimately to you. Nothing dangerous happens out of sight.
Figure 4. How one proposed action gets judged. Deterministic user rules outrank the tool's own assessment, which outranks the session's mode. Whatever the route, the outcome is one of three colours.

The ordering is the point. Your explicit rules (hooks) always win. The tool's self-assessment comes next — a file-read can wave itself through; a shell command mostly can't. Only the ambiguous residue reaches the mode, which decides whether that means "ask the human", "ask a reviewing model", or "allow and log".

The sub-agent rule at the bottom of Figure 4 deserves a highlight, because it's the kind of thing you only think of after an incident: a child agent must escalate permission requests up to its parent rather than approve its own. Without this, delegation becomes a laundering scheme — the parent spawns a child precisely because the child would face no prompts. With it, autonomy stays bounded no matter how deep the recursion goes.

⚠️ Practical note. When you first run a coding agent, resist the temptation to switch off the prompts because they're annoying. Run in default mode for a week and read what it asks you. You'll build an accurate mental model of what the agent actually tries to do — which is exactly the calibration you need before granting it more autonomy. Trust is earned on both sides of this relationship.

One model, many doorways

A short but practically important detail. Production agents rarely hard-code how they reach the model. The same request might travel to the provider's API directly, or through an enterprise cloud platform (AWS, Google, Azure all resell frontier models behind their own authentication). Well-architected agents hide this behind a single factory: at startup, configuration decides which doorway to use, a client is constructed with a common interface, and nothing else in the system ever knows the difference. The loop, the tools, the permissions — all provider-agnostic.

Why should you care? Because it's the pattern to copy whenever you integrate LLMs into your own work. If your hospital's data-governance team insists on the Azure route while your prototype used the direct API, that switch should be one line of config — not surgery. Design the seam in from day one.

Design rules worth stealing

I want to close the loop back to your projects — many readers of this series are scientists and clinicians who will end up building small agentic tools of their own (my drug-interaction pipeline is already halfway there). Here are the transferable rules, distilled:

Make the loop the only loop. Sub-agents, headless scripts, chat UIs — every entry point should drive the same loop function. The moment you fork a "lite" variant, behaviours diverge and bugs breed in the gap.

Let tools describe themselves. Schema, risk level, parallel-safety, display format — attached to the tool, not centralised in the orchestrator. Your future self, adding tool number twelve, will thank you.

Split calm state from busy state. Configuration set once at startup goes in a plain object. Rapidly-changing display state goes in a reactive store. Two access patterns, two containers.

Name your trust levels. A handful of explicit permission modes beats a hundred scattered checks. When something goes wrong at 2 a.m., "which mode was active?" is answerable; "which of the hundred checks misfired?" is not.

Escalate, don't self-approve. Any delegated worker — sub-agent, background job, cron task — routes its dangerous decisions upward. Autonomy is granted, never assumed.

Prefer files over databases for memory. Markdown notes that a human can read, edit, and version-control turn out to be a superb memory substrate — and letting a cheap model select which notes matter at session start keeps the context window lean.

None of these requires a research lab. They're just good engineering, discovered under the pressure of making agents work for real users — and they compose into systems that feel far more capable than any single part suggests.

What's next

We now have both ends of the stack: neurons and gradients at the bottom (Lessons 1–2), and the agent architecture at the top (this lesson). The obvious gap is the middle — the transformer: how a language model actually turns a transcript into the next token, why attention was such a breakthrough, and what "context window" physically means. That's where this series goes next — in fact, Lesson 4 is now live. We'll build one, piece by piece, small enough to train yourself.

Until then: the next time you watch an agent quietly read three files, run your tests, and fix the failure — you'll know exactly which of the six organs just fired, and in what order.

— Neal

📚 Continue the series: all lessons, in order, on the Lessons page.
tags: AI agents agent loop tool use LLM permissions sub-agents agentic coding architecture