AI Engineer Dojo Contents
AI Agent Engineer · Chapter 1

What an Agent Actually Is

Strip away the hype and an agent is one small idea: a model that runs in a loop, calls tools, sees the results, and decides when it's done. Everything hard about the job comes from that loop — the cost of each turn, the ways it fails, and knowing when you shouldn't have used one at all.

Here is the shape of every agent, from a two-line script to a coding assistant that edits your repo:

The agent loop, in words

while not done:
    decision = model(context)         # the LLM picks the next action
    if decision.is_final_answer:
        done = True                   # stop: return the answer
    else:
        result = run_tool(decision)   # act on the world
        context += result             # observe: feed the result back

That's it. The model doesn't "have agency" in any mystical sense — it emits either a final answer or a request to call a tool, your code runs the tool, and you paste the result back into the conversation and ask again. The intelligence is the model's; the agency — the loop, the tools, the stopping rule, the guardrails — is yours to engineer. An AI Agent Engineer owns that scaffolding, and almost every production failure lives in it, not in the model.

Agent vs. workflow — the distinction that decides your architecture

The word "agent" gets stretched to cover things that aren't agents, and the difference is not academic — it changes what you build and how it fails.

A workflow is a fixed path you wrote: retrieve, then summarize, then classify. The LLM fills in steps, but the control flow is hard-coded. It's predictable, cheap, and easy to test.

An agent lets the model decide the control flow at runtime: which tool, in what order, how many times, when to stop. That flexibility is the whole point — and the whole danger. You trade predictability for the ability to handle tasks whose steps you couldn't enumerate in advance.

The rule that saves you money and grief

Use a workflow when you can write down the steps. Reach for an agent only when the steps depend on what you discover mid-task — variable-length, branching work you genuinely can't script. Most "agent" projects that fail should have been workflows: they paid the agent tax (latency, cost, non-determinism) for a problem that had a fixed recipe.

Why the loop is expensive — the numbers that shape every design choice

Each turn of the loop is a full model call that re-reads the entire growing conversation. That has two consequences engineers consistently underestimate.

Cost grows super-linearly with steps. Turn 1 sends 1,000 tokens. But turn 2 resends turn 1's context plus the tool result, turn 3 resends all of that, and so on. A task that takes 8 tool-calling steps doesn't cost 8× a single call — because the context accumulates, it's closer to quadratic. Concretely: a single 1,000-token call is trivial; the same task dragged out to 8 steps, each re-sending a context that has grown to ~6,000 tokens by the end, can burn 30,000–40,000 input tokens total. Fewer steps isn't just faster — it's the single biggest lever on your bill.

Latency stacks. Each step is a sequential round-trip — the model can't take step 2 until it sees step 1's result. At ~2 seconds per call, an 8-step task is a 16-second wait. Users feel every step. "Make the agent take fewer steps" is therefore the recurring theme of this entire book: through planning (Ch. 4), memory (Ch. 5), and reflection used sparingly (Ch. 11).

The three things that make a loop an agent

  1. Tools — the actions it can take (Ch. 2). No tools, no agency; it's just a chatbot.
  2. A stopping rule — how the loop ends (Ch. 3). Get this wrong and it runs forever or quits early.
  3. State — what it remembers across turns (Ch. 5). The context is the memory until you outgrow it.

You'll build the smallest possible version of all three in the lab, then spend the rest of the book making each one production-grade.

Try it · ~20 min

Feel the loop — count the steps and the tokens

You'll run a minimal agent loop on one task and instrument it: how many model calls did it take, and how did the context (and therefore cost) grow with each step? The point is to feel the quadratic-ish growth in a number you produced, so every later "reduce steps" lesson lands.

Setup: pip install anthropic, then export ANTHROPIC_API_KEY=...or leave USE_REAL_API = False and run the built-in mock for $0.

Step 1. Run the loop below on the task and watch it call the calculator tool a few times.

Step 2. After each turn, record the running input-token count (sum of message lengths sent so far).

Step 3. Print steps taken and total input tokens; compare to what one direct call would have cost.

Your goal: two numbers — "N steps, ~T total input tokens" — and a one-line read on why T is so much bigger than N × (first-call size).

Starter code

import anthropic, re

USE_REAL_API = False        # flip to True with a key; mock runs free

TOOLS = [{
    "name": "calculator",
    "description": "Evaluate a basic arithmetic expression, e.g. '3 * (4 + 5)'.",
    "input_schema": {"type": "object",
        "properties": {"expr": {"type": "string"}}, "required": ["expr"]},
}]

TASK = ("A cart has 3 boxes of 12 apples and 5 boxes of 9 apples. "
        "Remove 7 apples, then split the rest evenly among 4 people. "
        "How many does each person get? Use the calculator for every step.")

def run_tool(name, args):
    if name == "calculator":
        return str(eval(re.sub(r"[^0-9+\-*/(). ]", "", args["expr"])))
    return "unknown tool"

def agent(task, max_steps=8):
    messages = [{"role": "user", "content": task}]
    total_in = 0
    for step in range(max_steps):
        resp = call_model(messages)                 # returns a message object
        total_in += approx_tokens(messages)         # what we PAID to send this turn
        messages.append({"role": "assistant", "content": resp["content"]})
        tool_uses = [b for b in resp["content"] if b["type"] == "tool_use"]
        if not tool_uses:                           # no tool call => final answer
            print(f"{step+1} steps, ~{total_in} total input tokens")
            return resp
        results = []
        for tu in tool_uses:
            out = run_tool(tu["name"], tu["input"])
            results.append({"type": "tool_result", "tool_use_id": tu["id"], "content": out})
        messages.append({"role": "user", "content": results})

def approx_tokens(messages):
    return sum(len(str(m["content"])) for m in messages) // 4   # ~4 chars/token

# TODO: implement call_model(): if USE_REAL_API, use
# client.messages.create(model="claude-opus-4-8", max_tokens=500, tools=TOOLS,
#   messages=messages) and adapt .content; else return the next _MOCK turn.
Worked solution

The mock plays back a realistic 4-step trajectory so the token math is real without a key:

_MOCK = [   # one entry per model turn
  {"content": [{"type":"tool_use","id":"t1","name":"calculator","input":{"expr":"3*12"}}]},
  {"content": [{"type":"tool_use","id":"t2","name":"calculator","input":{"expr":"5*9"}}]},
  {"content": [{"type":"tool_use","id":"t3","name":"calculator","input":{"expr":"(36+45-7)/4"}}]},
  {"content": [{"type":"text","text":"Each person gets 18 apples (74/4 rounds... actually 18.5)."}]},
]
_turn = {"i": 0}
def call_model(messages):
    if USE_REAL_API:
        import anthropic
        c = anthropic.Anthropic()
        r = c.messages.create(model="claude-opus-4-8", max_tokens=500,
                              tools=TOOLS, messages=messages)
        return {"content": [b.model_dump() for b in r.content]}
    m = _MOCK[_turn["i"]]; _turn["i"] += 1
    return m

agent(TASK)   # => "4 steps, ~T total input tokens"

What you should see: about 4 steps, ~180–260 total input tokens for this tiny task — and critically, the per-turn cost climbing: turn 1 sends only the task, but by turn 4 you're re-sending the task plus three tool calls plus three results. Scale the task up (more sub-steps, bigger tool outputs) and that climb is the quadratic growth from the chapter, live.

The engineering read. Notice the tool did the arithmetic — the model just orchestrated. That's the right division of labor: don't make the model compute what a deterministic tool can. And notice the final answer surfaced a real bug (74/4 isn't a whole number) — an agent will confidently return wrong math if you let it eyeball instead of tool-call. Both lessons recur.

Going further (optional): add a redundant 5th step (make the mock call the calculator once more on a value it already has) and watch total tokens jump disproportionately. That single wasted step is what planning (Ch. 4) exists to prevent.

Case study

Cadence: the "agent" that was really one call

Cadence announced an "AI agent" that booked meetings. Under the hood it was a single LLM call that emitted a booking — no loop, no feedback. It demoed fine on "book a 30-minute call with Sam Tuesday." It fell apart the moment reality pushed back: Sam was busy, the room was taken, the time zone was wrong. With one shot and no way to see the result and try again, it just produced a confident, wrong booking.

The fix wasn't a smarter model — it was making it an actual agent: call the calendar tool, observe the conflict, choose another slot, check again, and stop when booked. Task-completion on realistic requests went from 47% to 86%. The lesson that reframed their roadmap: the power isn't the model, it's the loop — act, observe, decide, repeat — and if a task never needs that loop, you don't need an agent at all.

Running case · Ridgeline

Ridgeline, a SaaS company, sets out to build an "ops agent" that resolves support tickets by calling tools — look up an order, issue a refund, update the CRM. Their first question is the right one: is this really a loop-with-tools problem, or would a fixed script do? For multi-step tickets, it's an agent. We'll follow the build to the end.

Quiz · Chapter 1 — reasoning, not recall

  1. Your team wants to build an "agent" that always does: fetch a ticket → summarize it → route it to a queue, in that exact order, every time. The right call is:
  2. An 8-step tool-using task costs far more than 8× a single call. The primary reason is:
  3. What single property most distinguishes an agent from a workflow?
  4. In the loop, whose job is the "agency"?
  5. You notice your agent takes 12 seconds to answer. The most structural lever to cut that is:

← Back Continue →

AI Agent Engineer — New-Grad Edition · AI Engineer Dojo · aiengineerdojo.com