Dive into the Agent Loop
The Agent Loop, Taken Apart
Most people picture an agent as a straight line: the user sends a prompt, the model thinks, calls a tool, gets a result, writes up an answer. Reasonable enough, but that line leaves out the thing that makes an agent an agent. It's a loop.
This article takes the chain apart piece by piece: who calls the model, who runs the code, where Skills and MCP each sit, and whether there's a model anywhere in the middle.
Three roles, clearly separated
The whole system has only three roles. Draw the boundaries between them properly and every later question gets easier.
The model judges and generates. It reads context and emits one of two things: text, or a tool call request. It holds no network connections, doesn't know whether a tool is backed by HTTP or a local script, and has no ability to execute anything itself.
The MCP server executes. It wraps external systems — databases, Jira, the filesystem, internal APIs — as a set of callable tools. It has no idea the model exists. A request comes in, it does the work, it returns a result.
The client (host/client in the MCP spec; in practice the Claude desktop app, Claude Code, Cursor, or the while loop you wrote yourself with an SDK) sits in between. One side holds the API key and calls the model. The other side speaks MCP to the servers. It is the only role that can see both sides at once.
This split has a direct consequence: the model doesn't speak MCP. What the model emits is a tool_use, roughly "I want to call search_files with these arguments." Translating that into an MCP tools/call request is the client's job. When the result comes back, the client is also what appends it to the context as a new message — only then does the model see it.
This isn't pedantry. It explains several practical things: why you can swap models without touching a single MCP server; why auth, approval prompts, and call logs all live in the client rather than the model; and why server authors need to write good tool descriptions — that natural-language blurb is the only part of the entire protocol the model ever lays eyes on. Everything else is invisible to it.
Skills and MCP aren't layered
A common mental model puts Skills on top and MCP underneath. Directionally it works — a Skill can call MCP tools, rarely the reverse — but the metaphor hides half the picture. They live on different axes and merely happen to converge in the context window.
A Skill sits closer to the model. It's procedural knowledge written for the model to read: how to approach this kind of task, in what order, what to watch out for, plus optional scripts and reference files. It adds no new capability; it reorganizes capabilities the model already has. That puts it squarely in context engineering.
Worth noting: a Skill is not "a human steering the model in real time" — that's what prompts do. A Skill is know-how frozen ahead of time, and the trigger belongs to the model: it decides the current task matches, then loads the SKILL.md. The person who wrote it often isn't the person using it — it might ship with the product, get written by a colleague, or come from a third party.
MCP is closer to a capability and transport layer. It answers "what can be done and what am I connected to," not "how should this be done."
The two don't depend on each other, either. A Skill can use nothing but bash scripts and never touch MCP; MCP works fine with no Skills in sight.
Also, "MCP never touches the model" isn't quite true. It reaches the model in at least three places: tool descriptions get injected into context and are themselves prompts (which is why connecting too many servers eats your context and can hurt performance); a server can use sampling to ask the client to run an inference; and servers can expose prompt templates and resources.
No model in the middle
From the moment a tool_use is generated to the moment the result lands back in context, the pipe contains zero models by default.
The client does format conversion: wrap the model's JSON into an MCP request. The server executes: hit a REST API, run SQL, shell out to a script. All ordinary, deterministic code. Once arguments are generated they run as written; nothing rewrites them along the way.
That property is a feature, not a gap. It means calls are auditable, replayable, and assertable. Stick another model in the middle to "interpret" or "improve" the arguments and you've added an uncontrolled step that's hard to even reproduce when it misbehaves.
There are two exceptions where a model does appear:
Sampling. MCP lets a server ask the client to run an inference in reverse. A document server holding 50 pages that wants to return a summary instead of the raw text sends sampling/createMessage. Note that the client still holds the model — the server has no API key and shouldn't. That's deliberate: model access stays concentrated in the host, which keeps approval and billing in one place.
A server with its own LLM inside. At that point it's just an ordinary AI application and has nothing to do with the protocol.
The loop is the point
Back to that straight line. What's wrong with it?
The model, having seen a tool result, does not necessarily wrap up. It reassesses: is this enough? If not, it emits another tool_use. Maybe the same tool with different arguments, maybe a different tool, maybe a tool it couldn't have known to call until it saw the first result — list the directory, then you know which file to read.
This can go around many times. Only when the model stops requesting tools and emits plain text is that output the answer to the user.
Roughly:
messages = [user_prompt]
loop:
response = call_model(messages, tools=available_tools)
messages.append(response)
if no tool_use in response:
return response.text # the only exit
for each tool_use in response:
result = dispatch_via_mcp(tool_use)
messages.append(result)
Without the loop you have a function call. With it, you have an agent.
Skill loading can happen mid-loop too. The model gets halfway through, realizes this is a PDF task, reads the relevant SKILL.md at that point, and continues under its guidance. It doesn't have to be settled up front.
The hard parts all live in the loop
Once you accept that it's a loop, the real engineering problems surface — and none of them appear on the straight line.
Context grows without bound. Every turn appends the model's output and the tool's return to the message list. A few rounds in you can hit the window ceiling. Raw tool returns are often enormous and mostly noise: a few thousand lines of JSON where the model needed three fields. What to trim, what to summarize, when to compress early turns — all real design work.
Failures. Tool calls fail: bad arguments, insufficient permissions, timeouts. Feed the error straight back and the model will usually correct itself and retry, which is exactly where an agent beats a hardcoded script. But it can also get stuck retrying the same wrong argument forever. You need retry ceilings and escape hatches.
Knowing when to stop. "I have enough information now" is a probabilistic judgment. The model can quit early and hand back something half-finished, or spin — calling tools repeatedly without converging. A turn limit is a backstop, not a good answer.
Where permissions live. Because the client is the only role that sees both the model and the servers, every confirmation dialog, dangerous-operation gate, and audit log has to be built at that layer. The model shouldn't be trusted to police itself, and the server has no idea who initiated the request.
Multi-agent: a loop inside a loop
Everything so far is a single loop. So what is "multi-agent"?
It introduces no new mechanism. It reuses the same loop — except that behind one particular tool call sits another complete loop.
The parent's tool list contains something like dispatch_agent, taking a natural-language task description. When the model calls it, the client doesn't reach for an MCP server — it starts a new loop: fresh message list, fresh system prompt, its own subset of tools. The sub-loop runs its dozens of turns and emits a final chunk of text, which returns to the parent's context as a tool_result.
From the parent's point of view this is indistinguishable from a database query. A request went out, a result came back. It has no idea how many turns happened inside.
"Agents with different functions" differ only at the configuration level, never architecturally:
- Different system prompts. One is "you review code and only flag security issues," another is "you write documentation."
- Different tool subsets. The reviewer gets read access; only the deployer gets write and execute. This is the most real form of isolation — capability boundaries are drawn by the tool list, not by instructions in a prompt.
- Different loadable Skills.
The underlying model can be the same or different. Routing simple classification and extraction to a cheaper model is a common way to save money.
The real motivation is context isolation
Most people assume multi-agent is about "division of labor." The main payoff is actually context isolation.
Back to the earlier problem: run one loop long enough and context blows up. A sub-agent hunting down a bug might read 40 files and run a dozen greps, producing hundreds of thousands of tokens of intermediate work. What the parent actually needs is one sentence: "null pointer, auth.py line 88."
Burn that process inside a separate window and the parent's context grows by one line. That's the hardest value multi-agent delivers.
Common topologies
Orchestrator-worker (the mainstream one). A main agent decomposes, dispatches, and aggregates. Workers don't talk to each other, only to the orchestrator.
Pipeline. A's output feeds B's input in a fixed order. This mostly doesn't need agents at all — a deterministic workflow is usually better.
Peer collaboration (multiple agents negotiating with each other). Great in demos, rarely stable in production — there's no clear termination condition, so they tend to congratulate each other in circles.
What it costs
- Token cost multiplies. Every sub-agent reloads its own system prompt and tool descriptions.
- Parent and child can only exchange text, so information is lossy by construction. Anything the child judged unimportant and left out of its summary is gone forever. And if the task description was vague, the child drifts — with the parent finding out only at the end.
- Parallelism only helps for genuinely independent work. Tasks with ordering dependencies or shared intermediate state get worse when split.
- Debugging is painful. When something goes wrong you're reading several disconnected traces.
A workable test: can the task be compressed into a one-sentence input and a one-paragraph output? If yes, split it out. If no, keep it in the main loop.
Skills vs. sub-agents: the actual difference
Both look like "hand specialized work to specialized handling," but the underlying distinction is basic: a Skill is knowledge; a sub-loop is a new judging entity.
Loading a Skill produces no new model call. Text goes into the current context and the same model, on the same message chain, keeps going. It shifts the model's behavior, not the execution structure. A sub-agent starts a fresh inference loop with its own message list, its own turns, its own sense of when it's done. One is handing someone a manual; the other is hiring a second person.
Several practical differences follow:
| Skill | Sub-agent | |
|---|---|---|
| New inference loop | No | Yes |
| Can restrict tool permissions | No — runs with the parent's full tool set | Yes — give it only what it needs |
| Intermediate work | Stays in main context, reviewable | Discarded; only the summary returns |
| Parallelism | No | Yes |
| Cost | One text load | Each reloads prompt and tool descriptions |
| Failure semantics | The main agent breaks | Can fail and retry independently |
The permissions row deserves emphasis. "Don't delete files" in a SKILL.md is advice, not a constraint — it runs with the parent's full tool set. Real capability isolation only comes from sub-agents, and it works by leaving dangerous tools out of the list.
The two also compose rather than compete. A sub-agent can load Skills of its own; giving a particular subagent a dedicated Skill set is a common pattern.
To choose between them, ask: does the main thread need to see this work happen? If yes, use a Skill. If no — and there's a lot of it — split it out.
Summary
The whole chain in one line:
The user's prompt enters context → the model judges, possibly loading a Skill first → the model emits a tool_use → the client translates it into an MCP request → the server runs deterministic code → the result returns to the client → the client appends it to context → the model reassesses, and goes around again if it's not enough → until it emits plain text, which goes back to the user.
Three roles, cleanly divided: the model judges but connects to nothing, the server executes but perceives nothing, the client sits in the middle holding all the control. The pipe in between contains no model by default, which is what makes calls auditable and replayable. And what makes an agent an agent is entirely that "go around again."
Multi-agent just recurses the loop one level: behind one tool sits not a server but another loop of the same shape. Once you can see the single loop, multi-agent introduces no new concepts — only the configuration changes. The diagram stays the same.