HANDS-ON · 60 minutes
Turn a single Anthropic SDK call into a real support-triage agent with a role, memory, and the judgment to answer, route, or escalate.
When I first put a support-automation assistant in front of real users — the de-identified support-automation RAG I built after the Internal Support Dashboard — the hardest part was not getting the model to write nice sentences. It was getting it to decide what to do. A customer message arrives. Should the system answer it directly from what it knows? Hand it to the right team? Or admit it is unsure and pull a human in? A single model call can produce text. An agent is the thing that wraps that call in a role, a memory, and a decision.
In this module you and I build exactly that: a de-identified customer-support triage assistant. It takes an incoming customer message and does one of three things — answers from a small knowledge base, routes the message to a team, or escalates when it is not confident. We will grow it one capability at a time, and every line of code is a real Anthropic SDK call you can run.
Think of an agent like a new teammate on their first support shift. The model is their raw ability to read and write. But to be useful they also need a job description (their role), a memory of the conversation so far, and the judgment to escalate instead of guessing. We add those three things in order.
Key insight: An agent is not a smarter model — it is a model plus a role, plus memory, plus a decision about what to do with the model's answer.
A plain script does the same thing every time you run it. An agent reads its situation and chooses its next move. The earlier modules in this path gave you the raw model call; an agent is what you build around that call so it behaves like a worker rather than a calculator.
Three ingredients turn a model call into an agent:
Common pitfall: People call any chatbot an "agent." The word earns its keep only when the program actually acts on the model's output — routing, escalating, calling a tool — rather than echoing it back to the user.
Before we write any code, predict: if you stripped the role out of our triage assistant and just sent the raw customer message to the model, what would you lose? (Hint: think about what stops it from confidently inventing a refund policy that does not exist.)
Let us pin down the job before we build it. The assistant handles one customer message at a time and must land that message in exactly one of three buckets:
That third bucket is the one beginners skip and the one that mattered most in my real system. A triage assistant that never says "I am not sure" is worse than no assistant, because it routes confidently wrong. We design escalation as a first-class outcome, not an error case.
Good pattern: Give the agent an explicit "escalate when unsure" path. Knowing its own limits is a feature, not a failure.
Everything starts with one real model call. This is the smallest possible version of our agent: it
hands a customer message to the model and prints the response. Copy it, set ANTHROPIC_API_KEY in
your environment, and run it — there is no abstraction layer hiding what happens.
We use claude-haiku-4-5 because it is the fastest and cheapest model for a learner's first real
calls — perfect for a triage loop that runs on every incoming message. The response comes back as a
list of content blocks; response.content[0].text is the model's reply. That is a model call. It is
not yet an agent, because nothing told the model what its job is and nothing decided what to do
with the answer. Let us fix the first of those.
Right now the model is improvising. To make it our triage assistant, we give it a system prompt — a standing instruction that frames every message it ever sees. This is the agent's job description.
Notice what the role does: it names the three buckets from our design and requires the model to
commit to one. The instruction to "start your reply with the action in capitals" is deliberate — it
gives our code something predictable to read later. Now we pass that role to the model with the
system parameter.
The system parameter is separate from the conversation messages on purpose: the role is constant,
the conversation changes. With this one change, the same model call now behaves like a triage worker
instead of a generic chatbot. But it still has no memory — each call forgets the last.
Common pitfall: Stuffing the role into the user message instead of the system parameter. It
often works, but the model treats system instructions as more authoritative — exactly what you want
for rules the customer should never be able to override.
A real support conversation has follow-ups. "It still is not working" only makes sense if the agent remembers what "it" was. We give the agent memory by keeping the running list of messages and passing the whole list back on every turn. Let us wrap our triage logic in a small class that holds that history.
The key line is messages=self.history. Each turn we append the customer's message, send the entire
conversation, then append the model's reply so the next turn has it too. That growing list is the
agent's memory. Here is a two-turn exchange where the second message only makes sense because of the
first:
Because the first turn is still in history, the model understands the second message refers to the
failing export — and can decide to escalate now that the customer has tried twice. That is the role,
the memory, and the beginnings of judgment, all built on the one real model call we started with.
Reflect before the quiz: our history list grows forever. On a long conversation that is both
expensive (you resend every past message) and eventually too large for the model's context. What is
one trade-off you would weigh when deciding how to trim that history — and what might you lose by
trimming the wrong end?
You now have a working triage agent: a verified Anthropic call wrapped in a role, a memory, and a clear set of decisions. What it cannot yet do is act on the world — look a customer up, file a ticket, read the real knowledge base. That is the job of tools and the MCP server from the previous module. In the capstone, we connect this agent to those tools and assemble the full support-triage system end to end.
Key insight: Everything advanced — tools, multi-step plans, multiple agents — is built on the three things you just added. Get the role, the memory, and the decision right, and the rest is extension, not reinvention.
Application-level questions on what makes a model call an agent.
1. According to this module, what turns a plain model call into an agent?
Correct Answer: B
An agent is a model call wrapped in a role (system prompt), memory (conversation history), and a decision your code makes with the output — not simply a smarter model.
2. In the first real call, what does response.content[0].text hold?
Correct Answer: C
The response is a list of content blocks; the first block's .text is the model's generated reply. The other options describe metadata or inputs, not the output text.
3. Why pass the triage role in the system parameter instead of inside the user message?
Correct Answer: C
The role is constant and authoritative, so it belongs in system. The model weights system instructions more heavily, which protects rules from being overridden by user input. Both system and user text are billed.
4. Why does the design treat ESCALATE as a first-class outcome rather than an error case?
Correct Answer: B
Knowing its limits is a feature. Without an explicit escalate path, the agent answers or routes confidently even when wrong — the failure mode that mattered most in the real support system.
5. In the TriageAgent class, which line gives the agent its memory?
Correct Answer: C
Passing the whole running history as messages is what lets the model see the prior turns. The system prompt sets the role, max_tokens caps the reply length, and model selects the model — none of those provide memory.
6. The second turn sends "It still does not work after retrying." Why can the agent understand what "it" means?
Correct Answer: B
Memory is just the resent message list. Because the first turn about the failing export is still in history, the model resolves 'it' from context. The model itself is stateless between calls.
7. What real trade-off does the unbounded history list create on a long conversation?
Correct Answer: B
Every turn resends all prior messages, so cost grows and the context can overflow. Trimming is necessary, but trimming the wrong end can drop the context a follow-up depends on.