// lesson: what-is-an-agent

What Is an Agent

Strip away the hype and an "agent" is three things: a role (a system prompt that tells the model who it is and what it's responsible for), a model call, and a slot in a control loop that decides when it runs and where its output goes. That's it. LangGraph, CrewAI, AutoGen โ€” every framework in this space is a nicer way to arrange those three things. By the end of this course you'll have built the arrangement yourself: a working researcher โ†’ critic โ†’ writer team running on your machine with your API key, and you'll know exactly what the frameworks abstract because you'll have written the raw version of each piece.

First, the honest question: when do multiple agents beat one big prompt? Not as often as the hype suggests. One model call with a good prompt is cheaper, faster, and easier to debug โ€” start there. Multi-agent earns its complexity when:

  • The roles genuinely conflict. A prompt that says "write persuasively" and "ruthlessly find flaws in the writing" is asking one call to hold two opposing stances at once. Two agents, each fully committed to its role, do both jobs better โ€” the same reason a fresh code reviewer catches bugs the author can't see.
  • Each step needs different context. Your researcher may pull in pages of source material the writer never needs to see. Splitting the work keeps each call's context small and focused (and cheaper).
  • You want checkable intermediate output. A pipeline hands you every step's output โ€” you can log it, test it, and see exactly where quality fell apart. One giant call hands you a final answer and a shrug.

When none of those hold โ€” a summary, a classification, a single-document Q&A โ€” use one call. Agents multiply cost and latency; make them pay rent.

Let's build. This course reuses the conventions from the CLI chatbot course: messages are dicts with role and content, and a client is a callable client(messages) -> str that hides which vendor SDK you're using. Set up:

mkdir agents && cd agents
python -m venv .venv && source .venv/bin/activate
pip install anthropic
export ANTHROPIC_API_KEY=sk-ant-...

Create team.py with the same client adapter the chatbot used โ€” it splits a leading system message out of our portable history shape:

import anthropic

_anthropic = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY

def anthropic_client(messages):
    system = ""
    if messages and messages[0]["role"] == "system":
        system = messages[0]["content"]
        messages = messages[1:]
    response = _anthropic.messages.create(
        model="claude-opus-4-8",
        max_tokens=2048,
        system=system,
        messages=messages,
    )
    return response.content[0].text

(Prefer OpenAI? Swap in the openai_client from the chatbot course โ€” every function in this course only ever sees the client(messages) -> str shape.)

Now the first real piece of multi-agent machinery. An agent is a role prompt bound to a client โ€” which in Python is just a closure:

def make_agent(name, system_prompt, client):
    def agent(task):
        messages = [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": task},
        ]
        return client(messages)
    agent.name = name
    return agent

Three decisions worth noticing. The message list is built fresh on every call โ€” agents here are stateless workers, not chatbots; each task arrives with clean context (contrast with the chatbot course, where the whole point was accumulating history). The system prompt is prepended every time, so the agent can't drift out of its role. And the agent's name rides along as a function attribute โ€” plain Python, no classes needed โ€” so logs and transcripts can say who did what.

Make two real agents with genuinely different roles and run them:

researcher = make_agent(
    "researcher",
    "You are a meticulous researcher. Given a topic, produce a compact, "
    "factual brief: key facts, numbers, names, and caveats, as bullet "
    "points. No prose, no fluff. Flag anything you are unsure about.",
    anthropic_client,
)

writer = make_agent(
    "writer",
    "You are a clear, engaging technical writer. Turn the material you "
    "are given into a short article a busy developer would enjoy. "
    "Plain language. No bullet points in the final piece.",
    anthropic_client,
)

if __name__ == "__main__":
    task = "Why do vector databases use approximate nearest neighbor search?"
    print("--- researcher ---")
    print(researcher(task))
    print("--- writer ---")
    print(writer(task))

Run python team.py. Same task, two visibly different answers โ€” the researcher gives you terse bullets, the writer gives you prose. That difference is the entire trick: a role prompt turns one general model into a specialist, and make_agent is the factory. In the next lesson the researcher's output becomes the writer's input, and you have a pipeline.

The challenge below grades make_agent exactly as your app uses it, with the client stubbed the same way the chatbot course stubbed it โ€” a fake that records what it was called with.

โ€บ Make an Agent

10 pts

Implement make_agent(name, system_prompt, client):

  • Return a callable. Calling it with a task string must call client(messages) exactly once, where messages is a new two-item list: {"role": "system", "content": system_prompt} followed by {"role": "user", "content": task}.
  • The callable returns whatever the client returned.
  • Set a name attribute on the returned callable equal to name.
  • Every invocation builds a fresh message list โ€” agents are stateless; no history may leak between calls.

Log in to submit a solution and earn points.