AI Engineer Dojo Contents
Chapter 1

What an Agent Is

Take away the hype and an agent is one small idea: a model running in a loop, calling tools, reading the results and deciding when it's finished. The hard parts of the job come from that loop: what each turn costs, how it fails, and when you shouldn't have used one.

Case study

Cadence's "agent" was one call

Cadence launched an "AI agent" that booked meetings. It was a single model call that produced a booking, with no loop and no way to check the result. It handled "book 30 minutes with Sam on Tuesday" in the demo. In real use, Sam was busy, the room was taken or the time zone was wrong, and it produced a confident, wrong booking.

They turned it into a real agent: call the calendar tool, see the conflict, pick another slot, check again, stop when booked. Completion on realistic requests went from 47% to 86%, with the same model.

The loop

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 all it is. The model either gives a final answer or asks to call a tool. Your code runs the tool, adds the result to the conversation and asks again. The model provides the reasoning; the loop, the tools, the stopping rule and the safeguards are yours to build, and that's where most production failures happen.

Agent or workflow?

A workflow is a fixed path you wrote: retrieve, then summarize, then classify. The model fills in steps, but your code decides the order. It's predictable, cheap and easy to test.

An agent lets the model choose the path at runtime: which tool, in what order, how many times, when to stop. That flexibility is the point, and it's also the risk. You give up predictability in exchange for handling tasks whose steps you couldn't list in advance.

Rule of thumb

If you can write down the steps, build a workflow. Use an agent only when the steps depend on what you find along the way. Many failed agent projects should have been workflows that paid the agent's costs (latency, money, unpredictability) for no benefit.

Why each step costs more than the last

Every turn is a full model call that rereads the whole conversation so far. Turn 1 sends 1,000 tokens. Turn 2 resends turn 1 plus the tool result, turn 3 resends all of that, and so on. An eight-step task doesn't cost eight times a single call; because the context keeps growing, it's closer to quadratic. By step 8 the context might be 6,000 tokens, and the task as a whole can use 30,000 to 40,000 input tokens. Cutting steps is the biggest lever you have on cost.

Latency adds up the same way. Each step waits for the previous one, so at about two seconds per call, an eight-step task takes 16 seconds. Much of this book is about reaching the goal in fewer steps.

What turns a loop into an agent

Three things. Tools, the actions it can take (Chapter 2); without them it's a chatbot. A stopping rule (Chapter 3); get it wrong and it runs forever or quits early. And state, what it remembers across turns (Chapter 5).

Try it · ~20 minFree · mock model

Count the steps and the tokens

Run a minimal agent loop on one task and measure it: how many model calls it made, and how the context, and so the cost, grew each step. Leave USE_REAL_API = False to use the mock. Aim for two numbers, steps and total input tokens, and an explanation of why the token total is so much more than steps times the first call.

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 replays a realistic four-step run so the token math is real:

_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"

You should see about four steps and 180 to 260 input tokens for this small task, with each turn costing more than the last. Turn 1 sends only the task; turn 4 resends the task plus three tool calls and three results. Scale up the task and that climb is the growth described above. Notice too that the calculator did the math while the model only decided what to calculate. That's the right split: don't ask a model to compute what a tool can. The final answer also exposes a real problem, 74 divided by 4 isn't a whole number, which a model eyeballing the math might have glossed over.

Running case · Ridgeline

Ridgeline, a software company, wants an agent that resolves support tickets by looking up orders, issuing refunds and updating its CRM. Its first question is whether this needs an agent or a fixed script. Multi-step tickets vary too much to script, so it's an agent. We'll follow the build to the end of the book.

If you're starting out

Write the loop yourself before using any framework. It's about thirty lines, and once you've done it, frameworks stop being mysterious. Interviewers often ask candidates to sketch it.

"When would you not use an agent?" is a common question, and the workflow distinction is the answer they want. Being able to say why agents cost more per task, because the context is resent every turn, makes the answer stronger.

If you run the team

When someone proposes an agent, ask whether the steps could be written down in advance. If they can, a workflow will be cheaper, faster and easier to test. Agents earn their cost on tasks that branch depending on what the system finds.

Ask about cost and response time per task, too. Agents take several steps, each one a model call, so a task that looks like "one question" can be eight calls and fifteen seconds. Knowing that early avoids surprises in the budget and in user reviews.

Quiz · Chapter 1

  1. A task always runs fetch ticket, summarize, route, in that order. What should you build?
  2. An eight-step task costs far more than eight single calls. Why?
  3. What most distinguishes an agent from a workflow?
  4. Whose job is the loop, tools and stopping rule?
  5. Your agent takes 12 seconds to answer. What's the most effective fix?
Back Continue

The AI Agent Engineer · aiengineerdojo.com