Core path - 6 of 8
Chapter 6: The Loop That Acts
This is the textbook chapter for the Agents deep dive. The README is the lab manual; this is the lecture. It covers what an agent actually is once the marketing evaporates, why the idea failed loudly in 2023 and succeeded without fanfare afterward, and the engineering judgment that separates a loop you'd run unattended from one you wouldn't.
6.1 A word with a marketing problem
Few words in technology have been stretched thinner than "agent." By 2024 it was being applied to everything from a chatbot with a nice interface to a scheduled cron job with an API call in it, and its main function in a sales deck was to raise the price. Underneath the stretch, though, there's a precise technical idea, and it fits in one sentence:
An agent is a loop: the model picks a tool, you run it, you feed the result back, until it's done.
That's the entire concept. A language model on its own can only emit text; it can't check a database, run a calculation, or save a file. Give it a set of tools it may request, wrap it in a loop that executes each request and returns the result, and the model can now pursue a goal across multiple steps, observing what happened and deciding what to do next. Everything sold under the word "agent" that isn't this is packaging.
The idea has a respectable intellectual lineage. Classical AI spent decades on the sense-think-act cycle in robotics and planning. The direct ancestor of the modern form is ReAct, a 2022 research paper showing that a model prompted to interleave reasoning ("I should look this up") with actions (a search query) and observations (the result) solved problems neither pure reasoning nor pure retrieval could. You met ReAct as a prompting pattern in Chapter 3, done by hand. Tool calling (Chapter 1) made it native: instead of parsing actions out of free text and hoping, the model emits structured requests your code can execute safely.
The recent history is a boom-bust-boom worth knowing, because it teaches the chapter's central judgment. In the spring of 2023, a project called AutoGPT became one of the fastest-starred repositories GitHub had ever seen. The pitch: give a model a goal and let it loop autonomously until done. Millions tried it. It mostly didn't work: agents wandered, looped on failed steps, burned money reconsidering their own to-do lists, and rarely finished anything nontrivial. The word "agent" acquired a smirk. Then, over the following two years, agents became some of the most-used AI products in existence, in one domain first: coding tools like Claude Code and Cursor run exactly this loop (read files, edit, run tests, read the failures, repeat). What changed wasn't the loop. It was better models, much better tools, tight feedback signals (a failing test is an unambiguous observation), and human checkpoints in the right places. The lesson to carry through this chapter: the loop is trivial; the engineering around it decides everything.
6.2 A tool has two faces
Before the loop, the thing it loops over. A tool, to your code, is a plain function. To the model, it's only three pieces of text: a name, a description, and a schema for its inputs. The model never sees your implementation and never executes anything. It reads the descriptions, decides one would help, and emits a request: call this name with these arguments.
Sit with that gap for a moment, because every bit of an agent's safety lives inside it. The model asks; your code decides. Between the ask and the run, you can validate arguments, check permissions, ask a human, log the attempt, or refuse. An agent isn't a model that has been given power; it's a model that has been given a request channel, and you own the other end. When you read alarming headlines about an AI "doing" something, the engineering question is always about the other end of that channel: what was wired to it, and what checks stood in between?
The second thing worth sitting with: since the model chooses tools purely by reading their names, descriptions, and parameter names, those strings are prompt engineering, not documentation. A tool described as "search product information" gets used for questions its author never anticipated; one described tersely as "query db" gets ignored while the model guesses from memory. When an agent misbehaves, experienced builders check the tool descriptions before the system prompt, and the lab's troubleshooting table reflects that instinct. You're not writing docs for a colleague; you're writing the only evidence the model has about what its hands do.
6.3 The loop itself, and why its control flow stays small
The lab's run_agent has one small control-flow spine. Call the model with the conversation and the tool catalog. If it answers with text, done. If it requests tool calls, send each proposal through the contract executor, append the results to the conversation, and go again. That's a while loop around the API call you learned in Chapter 1, plus an execution boundary whose details deserve to stay outside the loop.
Watch the trace on a multi-step question and you see why this small thing is a genuinely new capability rather than a convenience. Asked what a year of a subscription costs, the agent searches for the price, reads the result, and only then can it multiply by twelve, because the second step's input didn't exist until the first step returned. A single API call, however clever, can't do that; it must produce its whole answer before observing anything. The loop gives the model what programmers would call intermediate state and what the rest of us would call the ability to find things out before deciding.
Give it several tools and a second capability appears without any new code: routing. The model decomposes the task and sends each part to the right tool, chaining them, guided by nothing but those descriptions. No dispatcher was written. This is also the moment to name what the loop really is beneath the surface: a conversation that grows. Every tool result is appended to the message list, the whole list is re-sent each turn (the statelessness of Chapter 1, now with consequences), and a long-running agent is therefore an eating contest against the context window. That pressure, and what to do about it, gets its own chapter (Context Engineering); here you just need to see where it comes from.
6.4 The multiplication problem, and the guardrails it forces
Now the judgment section. A model that is right 95% of the time sounds fine until it must be right ten times in a row: the chain succeeds only about six times in ten. Twenty steps, about a third. Errors in a loop compound geometrically, which is the arithmetic behind AutoGPT's flailing, and it dictates the engineering posture for everything an agent does unattended. You don't get reliable agents from optimism; you get them from designing every layer around the assumption that some step will go wrong.
Step limits are the first layer. An unsupervised loop needs a hard ceiling so a confused agent stops and says so rather than orbiting forever, and in production the ceiling comes paired with a cost budget, because a stuck loop is a machine that converts confusion into invoices at several cents per orbit.
Error recovery is the second, and it contains this chapter's most elegant idea. When a tool raises an exception, the naive loop crashes. The right loop returns a structured error category and message as the tool's result. The model reads "tool_error: division by zero" or "schema_validation: unexpected property" the way it reads any observation, and it adapts: retries differently, picks another tool, or reports honestly that it's stuck. The category lets application code distinguish failures without parsing prose. The failure becomes information. This trick, almost insultingly simple, is a large part of why modern coding agents hold up: a failing test or a stack trace isn't a crash, it's the next observation in the loop.
Human-in-the-loop approval is the third, and it encodes a policy question in code. Some actions are safe to let the model take freely (a calculation, a read-only search); some have consequences (writing files, sending email, moving money). The lab's pattern marks consequential tools as dangerous and requires an approval callback before they run, and there's a detail here that deserves attention: a denial is delivered to the model as just another tool result, so the agent adapts to "no" the same way it adapts to an error. The human isn't an exception handler bolted on; the human is part of the environment the agent operates in. Which tools require approval isn't a technical fact, it's your policy, and declaring it explicitly on the tool is what makes the policy auditable.
Observability closes the set. An agent makes its own decisions, so when it misbehaves, "it gave a weird answer" isn't debuggable; you need the trace, which tool, what arguments, what result, at each step. The structured record the lab keeps is the same object you'd log in production, and, closing a loop with Chapter 5, it's exactly what a trajectory eval grades: right tools, sensible order, no forbidden calls, within budget. An agent you can't trace is an agent you can't evaluate, debug, or trust.
6.5 The contract between proposal and effect
The schema sent to a provider has two jobs that are easy to conflate. It teaches the model what a well-formed request looks like, and, when strict generation is available, it makes well-formed requests more likely. Neither job authorizes the request to touch your systems. Provider behavior changes, remote MCP servers publish schemas you didn't write, prompt injection changes what the model tries, and application identity doesn't belong in model-controlled text in the first place. The execution boundary therefore validates the complete schema locally before it calls Python. Exact properties reject undeclared fields; required fields, types, enums, ranges, patterns, and lengths each express an independent invariant. A test should perturb each one independently, because a schema keyword that no test can make fail may be decorative rather than operative.
Identity makes the trust boundary concrete. A refund function may need tenant_id and requested_by, but putting either field in the model-facing schema lets the model choose them. The contract instead rejects any proposal that tries to name those trusted fields, checks roles against an authenticated ExecutionContext, and injects the tenant and subject only after authorization. Rejecting the forgery rather than silently overwriting it matters: correction would turn an attack into a successful call and erase the evidence that it was attempted. The same separation keeps the course's tests honest. Requirements come from policy; model proposals are stimuli; decisions come from the executor. None is derived from another.
Consequential operations add two more controls. Approval fails closed: the absence or failure of an approval service is a denial, not permission. Mutating calls use the trusted application request ID plus provider call ID and tool name as a replay key, and that key is a promise about one specific payload: a repeat carrying different arguments is refused rather than answered from the cache, because handing back a settled decision would both answer a question nobody asked and leave the real attempt invisible in the audit trail. The lab caches every outcome reached after dispatch, including an error or timeout, because the executor can't know whether an external write committed before the connection failed. That local cache is bounded so it can't grow forever, but it isn't a production transaction manager: concurrent workers could still race, restarts forget it, and eviction permits an old request to run again. The real idempotency key must be enforced durably in the same transaction as the sink's write.
Time and output limits bound two other dimensions, with similarly important semantics. A Python thread timeout stops the caller from waiting; it can't kill code already running, so hard cancellation requires a subprocess, worker, or remote deadline. Output is counted in UTF-8 bytes, not characters, because transport, storage, and context budgets are byte-sensitive. Structured outcomes then preserve the reason (schema_validation, not_authorized, timeout, and so on), whether a completed result was replayed, and hashes of the actual effective arguments and output. The bounded audit log stores those digests rather than raw payloads. That's enough evidence to correlate a decision without casually duplicating sensitive content into telemetry.
One more asymmetry deserves naming, because it's where this chapter's ideas meet the last one's. A schema that arrives over a protocol is a schema someone else wrote. MCP doesn't require it to be closed, and most published schemas aren't, so adopting one means sealing it at the boundary rather than loosening the executor for every tool you own. That has a price worth stating plainly: a sealed schema will reject a call a sloppy server would have accepted, because it refuses to forward fields nobody declared. The reciprocal holds on the other side of the wire. A server can't verify that its client validated anything, has no idea which model is driving it or what text that model just read, and so runs the same contract on every call it receives. Both ends enforce at their own trust boundary, which is the only place enforcement means anything.
The order is the design: resolve the named tool, reject forged context, validate syntax and domain bounds, authorize the trusted principal, return a completed replay, require approval, and only then cross the effect boundary. Rearranging those checks changes behavior. Approving before authorization bothers a human about a call that can never run; validating only after invocation makes the sink your accidental policy engine; checking replay before identity could leak another tenant's result. A tool contract isn't a bag of guardrails. It's one causal decision pipeline.
6.6 Do you even need an agent?
The most valuable pattern in the lab may be the one that argues against the repo's own title. If you can draw the flowchart for a task (classify the ticket, then route by category, then draft from the template), build a workflow: fixed steps orchestrated by your code, with model calls inside the boxes where language ability is needed. It's cheaper, faster, predictable, and testable, precisely because the path isn't decided at runtime. Reach for an agent, where the model drives the control flow, only when the path genuinely can't be known up front, as in debugging, open-ended research, or "make this test pass."
Anthropic's widely circulated engineering guidance says the same thing bluntly: most "agent" use cases in the wild are workflows wearing a costume, and the composed-workflow patterns (chaining, routing, parallelization) cover most needs at a fraction of the complexity. The ladder from CHOOSING.md puts agents on the top rung for a reason, and the reason is cost, not prestige. There's a seductive counterargument you'll hear: "everyone uses agents, they can't be that expensive." Notice that almost everyone uses agents someone else built and hardened; the complexity was paid once, by the tool's author, then hidden. Building the loop into your own product is the decision the ladder prices, and there it's rarely the cheapest thing that works. Prove the lower rungs fail first, with the evals you now know how to run.
Two cheap wrappers blur the middle ground and are worth knowing. Planning asks the model to write a short plan before acting, which keeps long tasks on track and gives you a checkpoint where a human can veto a bad approach at the cost of one extra call. Reflection runs a critic pass after, catching half-answers before the user sees them, and it echoes a lesson from Chapter 3 worth repeating because it generalizes: self-correction works far better against a real external check (a test, a validator, a search result) than against the model's own feelings about its work.
6.7 Scaling the shape: sub-agents, parallelism, hosted tools
Three extensions carry the loop from demo to system, and the pleasing thing about all three is that none introduces a new concept.
Multi-agent systems sound exotic and are, mechanically, a pun. A sub-agent is a tool whose implementation happens to run its own loop, with its own prompt and its own narrower toolset. The orchestrator calls research the way it calls calculator; underneath, research is a whole second agent. Why bother? Focus. One agent with twenty tools gets noticeably worse at choosing among them (a crowded tool catalog is a crowded prompt), and a specialist with three relevant tools and a specialist's system prompt outperforms a generalist with everything. There's also a context benefit with real consequences: the sub-agent's lengthy intermediate work stays in its own conversation, and only its conclusion returns to the orchestrator, which keeps the parent's window lean. Large agent products, including the coding tools, are built exactly this way, focused agents calling each other through the same tool interface you already know.
Parallel tool calls and streaming are the user-experience half. When the model requests several independent calls in one turn, run them concurrently; the turn costs the slowest call rather than the sum. And stream everything, not just the final answer but the narration between tool calls ("let me look that up..."), because an agent that works silently for thirty seconds reads as broken while one that narrates reads as working. This isn't cosmetics; visible progress is what makes multi-step latency tolerable, and it's the pattern every production assistant uses.
Hosted tools are the one genuine architectural fork in the chapter. Everything so far was client-executed: the model asks, your loop runs the function, your code sits in the middle of every action. Providers now also offer tools they execute themselves, inside the turn, on their infrastructure: web search, code execution, and, at the frontier, computer use, where the model operates a real GUI through screenshots and clicks. You declare the tool, send one request, get one final answer, and your loop handles zero rounds. The trade is control for plumbing: a hosted tool can't be gated behind your approval callback, logged by your tracer, or sandboxed by your policy, because your code never sees the call. Real systems mix both, and the decision rule falls out of everything above: convenience is fine for actions you'd have auto-approved anyway; anything you'd want to gate, inspect, or audit belongs on your side of the channel.
The lab's MCP bonus belongs in this list as a preview: the Model Context Protocol takes "a tool is a name, a description, and a schema" and speaks it over a wire, so an agent can discover and call tools it never shipped with, served by other processes, other teams, other companies. The lab builds the protocol by hand, JSON-RPC and all, so that when Chapter 14 treats it fully, it reads as familiar plumbing rather than a new religion.
6.8 What you should now see through
The stated goal of this dive is x-ray vision, so let it be explicit about what you can now see through.
Agent frameworks, first. LangChain, CrewAI, the SDK tool-runners: having written the loop, you can now read any of them as the small control-flow spine you wrote plus an execution boundary and opinions about configuration. Some of the opinions are good. But you can now evaluate them as engineering rather than accept them as magic, and when one misbehaves you know the loop underneath is inspectable, because you've inspected yours.
Agent products, second. When a coding assistant fixes your bug, you can narrate what happened: tools for reading and editing files and running commands, a loop feeding results back, error text becoming the next observation, approval gates on the dangerous parts, a trace behind it all. The demystification isn't cynicism; the products are genuinely good. It's the difference between a user and an engineer.
And the risk conversation, third. An agent's danger isn't mystical autonomy; it's a request channel wired to consequential tools with insufficient checks between. That framing tells you exactly where to look in any agent system you're asked to assess: what tools exist, which are gated, what the arguments are validated against, what gets logged, and what happens when a step fails. It also tells you what's coming next in this course. An agent that reads documents and acts on what it reads is a machine whose instructions can arrive hidden in its inputs. You've built the loop; the next chapter attacks it.
Lab manual: README.md · Exercises: EXERCISES.md · Previous: Evals · Next: Prompt Injection & Guardrails