Coding agents feel like magic. They aren't. Here's the tiny loop at the heart of it -- the one you could sketch on a napkin.
You type "fix the failing test." Claude Code reads a file, runs pytest, sees a red failure, edits the code, runs pytest again, sees green, and tells you it's done.
The first time you watch this happen, it feels like magic.
It isn't. Underneath, there's a loop so small you could sketch it on a napkin. Once you see it, coding agents stop being mysterious -- and you'll understand why they behave the way they do.
Let's build the mental model from scratch.Here's the unlock that makes everything else click:
A language model can't do everything. It can only produce text.
The model can't open your files. It can't run a command. It can't touch the network. Drop it on a desert island and it will happily generate paragraphs forever, but it will never actually run your tests.
So how does Claude Code edit files and run commands?
It doesn't. A regular program does -- and the model just tells it what to do.
Enter the harness
Around the model sits an ordinary program. Call it the harness (that's the CLI you're running).
The deal between them is simple:
- The harness tells the model, up front: "Here are the tools you can use."
- The model replies with a tool call -- not prose, but a structured request like "run
pytest -q". - The harness actually runs it, captures the output, and hands the result back to the model.
- The model reads that result and decides what to do next.
The model is the brain. The harness is the hands. Neither works without the other.
Tools are just a menu + a contract
When a session starts, the harness gives the model a menu of tools, each with a name, a description, and a schema for its inputs. Simplified, it looks like this:
{
"name": "run_bash",
"description": "Execute a shell command and return its output",
"input_schema": {
"command": "string"
}
}
Now, when the model wants to run the tests, it doesn't say "you should run pytest." It emits a structured tool call:
{
"tool": "run_bash",
"input": { "command": "pytest -q" }
}
The harness executes that command and appends the result to the conversation:
{
"tool_result": "1 failed, 4 passed\nE assert add(2, 2) == 5"
}
That result goes back into the model on the next turn. Now the model can react -- it knows the test failed and exactly why.
This back-and-forth is the whole game.
The loop
Here's the engine. This is the napkin sketch:
conversation = [system_prompt, user_message]
while True:
response = model(conversation) # the model thinks
conversation.append(response)
if response.has_tool_calls:
results = run(response.tool_calls) # the harness acts
conversation.append(results)
# loop again -- the model sees the results and continues
else:
break # no tool calls == the model is done
show(response.text) # final answer to the user
That's it. That's the agentic loop.
Every turn, the model sees the entire conversation so far -- your request, every command it has run, and every result. It picks the next action. The harness carries it out. Repeat until the model stops asking for tools.
Let's trace our "fix the failing test" example through it:
- You: "fix the failing test"
-
Model: calls
run_bash("pytest -q") -
Harness: returns
1 failed ... assert add(2, 2) == 5 -
Model: calls
read_file("math_utils.py") - Harness: returns the file contents
-
Model: calls
edit_file(...)to fix the bug - Harness: applies the edit
-
Model: calls
run_bash("pytest -q")again -
Harness: returns
5 passed - Model: no more tool calls -- replies "Fixed it. The bug was..."
Ten steps, one loop. No magic -- just a model reacting to real output, one turn at a time.
Why this design is so powerful
Once you see the loop, a lot of things make sense:
- It reacts to reality. The model doesn't guess whether the test passed -- it sees the output and adjusts. That feedback is what makes it feel smart.
- Tools are pluggable. Want the agent to query a database or hit an API? Add a tool to the menu. The loop doesn't change. (This is exactly what MCP servers do -- they extend the menu.)
- Safety lives in the harness, not the model. Before step 7 actually writes to disk, the harness can pause and ask you for permission. The model can request anything; the harness decides what's allowed. That separation is your safety layer.
Skills: giving the agent a playbook without bloating its brain
So far the model knows two kinds of things: what's baked into its training, and whatever's in the current conversation. But real work needs specifics -- your team's deploy steps, a tricky migration checklist, the exact way your repo wants commits formatted.
You could paste all that into every conversation. But then it's competing for space in that limited context window, every single session, whether you need it or not.
Skills are the fix. A skill is just a packaged set of instructions for a particular kind of task, sitting on disk until it's needed. Think of it as a playbook the agent can pull off the shelf.
The trick is how they load, and it's a pattern worth knowing: progressive disclosure.
- At startup, the harness shows the model only a one-line description of each skill -- a table of contents, not the book.
- When your request matches one ("deploy the service"), the model calls a tool to open that skill.
- Only now do the full instructions load into the conversation -- right when they're relevant, and not a moment before.
So the agent can "know" about a hundred skills while paying the context cost of only the one it's actually using. It's the same pluggable idea from earlier, applied to knowledge instead of actions: the menu stays cheap, and the details arrive on demand.
And the best part -- a skill is just files. Here's a whole one:
---
name: deploy-service
description: "Deploy the web service to staging and run smoke tests"
---
1. Run `make build`.
2. Push the image with `make push TAG=staging`.
3. Wait for the rollout: `kubectl rollout status deploy/web`.
4. Hit `/healthz` and confirm a 200 before reporting success.
Drop that markdown file in, and you've taught the agent a new procedure -- no retraining, no changes to the loop itself.
Skills vs MCP, quickly: MCP servers add new tools (new actions the agent can take). Skills add new instructions (know-how for using the tools it already has). One grows the hands; the other grows the playbook.
The one catch: context isn't infinite
Notice that every tool result gets appended to the conversation. Read a big file? It's in there. Run a chatty command? That's in there too.
But the model can only look at a limited amount of text at once -- its context window. On a long session, all those results pile up and eventually won't fit. (This is the very problem skills are designed to sidestep.)
The fix is compaction: when the conversation gets too big, the harness summarizes the older parts to keep the important bits and drop the noise. It's the tradeoff that lets a session run for hours without the model forgetting why it started.
(That's a whole post of its own -- maybe part 2.)
The napkin, one more time
Strip away everything and a coding agent is a handful of pieces:
- A model that only produces text
- A set of tools it can request, with a clear contract
- A loop that runs those requests and feeds results back
- A permission gate in the harness that decides what's allowed
- Optional skills -- playbooks the agent loads only when they're relevant
That's the whole machine. The intelligence is in the model; the agency is in the loop.
Next time you watch Claude Code fix a test on its own, you'll know exactly what's happening under the hood -- one turn at a time.
If this was useful, next part will dig into context management: how an agent keeps working long after the conversation outgrows its memory. Follow along.
Top comments (0)