Page 3 of 13 · The engine

The agent loop and its tools

Every agent, from a chatbot with search to Claude Code, is the same loop: the model reads its context, picks a tool, your code runs it, the result goes back into the context, repeat until a stop condition. Understand this loop and every framework becomes a variation of it.

6 runnable samples1 prediction3 questions

Level 1 · The rules

Anatomy of one loop

The model never runs anything. It asks; your harness decides and executes.

Contextprompt + history + results Modeldecides next step Harnesscheck, run, record Final answer: stop Budget hit: stop tool call tool result appended
The model proposes, the harness disposes. Every arrow is a place to add control.

The four rules of the loop

  1. The model only proposes a tool call; the harness validates and executes it.
  2. Every tool result, including errors, goes back into the context.
  3. The loop ends on a final answer or a budget: max steps, tokens, time or money.
  4. Anything not in the context does not exist for the model on the next step.
python[ RUNS ]

A complete agent loop with a scripted model

def fake_model(context):    if not any(m["role"] == "tool" for m in context):        return {"tool": "run_tests", "args": {}}    return {"final": "Tests pass: 12 of 12."}TOOLS = {"run_tests": lambda: "12 passed, 0 failed"}def agent(task, max_steps=5):    context = [{"role": "user", "content": task}]    for step in range(max_steps):        action = fake_model(context)        if "final" in action:            return action["final"], step + 1        result = TOOLS[action["tool"]](**action["args"])        context.append({"role": "tool", "content": result})    raise RuntimeError("budget exhausted")print(agent("Check that the test suite passes"))

[ OUTPUT ]recorded · Python 3.14.8

Press Run to see the output this code printed when the course was built.

Level 1 · The rules

Tools are APIs for a model

A tool is a name, a description and an input schema. The model reads all three to decide when and how to call it, so they are documentation and interface at once.

python[ RUNS ]

A tool definition the model will read

import jsonread_file = {    "name": "read_file",    "description": (        "Read a UTF-8 text file from the repo. Use before editing. Returns at most 400"        " lines."    ),    "input_schema": {        "type": "object",        "properties": {"path": {"type": "string", "description": "Repo-relative path"}},        "required": ["path"],    },}print(json.dumps(read_file["input_schema"]["required"]))

[ OUTPUT ]recorded · Python 3.14.8

Press Run to see the output this code printed when the course was built.

When the model asks for a tool that does not exist, a harness without a check crashes. That is one of the most common agent failures: the model invents a plausible tool name.

python[ RAISES ToolNotFoundError ]

The model calls a tool that is not registered

class ToolNotFoundError(Exception):    passTOOLS = {"read_file": lambda path: "contents"}def dispatch(call):    if call["tool"] not in TOOLS:        raise ToolNotFoundError(call["tool"])    return TOOLS[call["tool"]](**call["args"])dispatch({"tool": "open_file", "args": {"path": "README.md"}})

[ TRACEBACK ]recorded · Python 3.14.8

Press Run to see the output this code printed when the course was built.

python[ RUNS ]

Errors as observations: the loop recovers

TOOLS = {"read_file": lambda path: "# README"}def dispatch(call):    if call["tool"] not in TOOLS:        return (            "error: unknown tool " + call["tool"] + ". Available: " + ", ".join(TOOLS)        )    return TOOLS[call["tool"]](**call["args"])print(dispatch({"tool": "open_file", "args": {"path": "README.md"}}))print(dispatch({"tool": "read_file", "args": {"path": "README.md"}}))

[ OUTPUT ]recorded · Python 3.14.8

Press Run to see the output this code printed when the course was built.

Level 2 · Why it works

Why stop conditions and budgets are not optional

A model can loop forever: retrying a failing command, re-reading the same file, or "almost done" for 80 steps. Only the harness can stop it.

python[ RAISES BudgetExceededError ]

A model that never finishes, stopped by a budget

class BudgetExceededError(Exception):    passdef stuck_model(context):    return {"tool": "run_tests", "args": {}}def agent(max_steps=3):    context = []    for _ in range(max_steps):        action = stuck_model(context)        context.append("tests failed")    raise BudgetExceededError(f"stopped after {max_steps} steps")agent()

[ TRACEBACK ]recorded · Python 3.14.8

Press Run to see the output this code printed when the course was built.

  • Step budget

    Max loop iterations. Catches retry loops.
  • Token budget

    Max tokens per run. Catches context bloat.
  • Cost budget

    Max dollars per run. Catches expensive fan-out.
  • Time budget

    Wall-clock limit. Catches hung tools.

Structured outputs (contract)

When another program consumes the model's answer, ask for JSON that matches a schema and validate it. Free text is for humans; schemas are for code.

python[ RAISES SchemaValidationError ]

Validate before you trust

class SchemaValidationError(Exception):    passdef validate(obj, required):    missing = [k for k in required if k not in obj]    if missing:        raise SchemaValidationError("missing: " + ", ".join(missing))    return objmodel_output = {"title": "Add login", "points": 3}validate(model_output, ["title", "points", "acceptance_criteria"])

[ TRACEBACK ]recorded · Python 3.14.8

Press Run to see the output this code printed when the course was built.

Level 3 · Mastery

Trade-offs seniors talk about

ChoiceGainCost
More toolsMore capabilityEvery definition costs context on every call, and choice gets harder
Parallel tool callsLower latencyHarder to reason about ordering and side effects
Higher step budgetSolves longer tasksHigher cost and more room to drift
Errors returned as resultsSelf-correctionCan loop on the same error without a budget
Strict schemasReliable integrationSome loss of nuance; needs retry on validation failure

Predict the output

Level 1 answer before you look

Check your understanding

Levels 1 to 3 open a question to see the answer

Who executes a tool call: the model or the harness?

The harness. The model only emits a structured request. That separation is what makes permissions, hooks and validation possible.

Why return tool errors to the model instead of raising?

The model can read the error and correct itself on the next step. Raising ends the run and wastes the work so far. Pair it with a budget so a repeated error cannot loop forever.

Name four budgets a harness should enforce.

Steps, tokens, cost and wall-clock time.