Learning / Agentic Operations / Lesson 0005
Build the Single-Tool Agent
The second half of the M1 gate: ship a reliable agent — and read what the model actually sees.
This is a build lesson, not a reading lesson. Work in a terminal at the repo root. A
complete, self-tested reference implementation lives at
learning/agentic-operations/exercises/m1_agent.py — type the stages yourself
first and peek at it when stuck; the typing is where the learning is. (Its offline logic —
calculator safety, context-log serialization — passed --selftest; the live API
path needs your key, which is checkpoint 0.)
Checkpoint 0 · Bring your own key
This course runs on your own Anthropic API key — it stays on your machine,
is never committed, and never reaches anyone else. Copy the template, then paste your key into
the new .env:
cp .env.example .env # then edit .env: ANTHROPIC_API_KEY=sk-ant-...
Create a key at the Anthropic Console if you
don't have one. .env is gitignored, so the key can't be committed by accident.
Verify it's set without printing the secret:
uv run python -c "from dotenv import dotenv_values; print('ANTHROPIC_API_KEY' in dotenv_values('.env'))"
True. The whole hands-on runs on
well under a dollar of your own credit — this agent's model is priced at $5 / $25 per million
input / output tokens, and the calculator examples spend a few thousand tokens each.Stage 1 · One call, no tools
Start a script that makes a single API call and prints the response blocks and token usage.
Model: claude-opus-4-8. Note two things when it runs: the response is a list of
typed content blocks, not a string; and the usage object prices the call in tokens —
the unit everything in this field is denominated in.
response = client.messages.create(
model="claude-opus-4-8", max_tokens=16000,
system=SYSTEM, messages=[{"role": "user", "content": "What is 137 * 41?"}],
)
for block in response.content:
if block.type == "text": print(block.text)
print(response.stop_reason, response.usage.input_tokens, response.usage.output_tokens)
end_turn, and real token counts. The model probably did the arithmetic in its
head — plausibly. That's the failure mode tools exist to close.Stage 2 · Define the tool
Add the calculator tool definition (see the reference sheet for the exact schema) and a system prompt that says arithmetic must go through it. Remember from Lesson 0002: the description is a prompt — say when to call it ("for every arithmetic step; do not do mental math"), not just what it does.
stop_reason == "tool_use" and a content block carrying
{"expression": "137 * 41"}. Stop and look at that block: it is text the model
proposed. Nothing has been computed yet. Your code makes it real.Stage 3 · Execute and loop
Implement the tool body and the loop: execute the proposal, append the assistant content
verbatim, append a tool_result (id matched to the proposal), call again; repeat
while the stop-reason is tool_use. Two non-negotiables from the reference sheet:
the tool body treats the model's input as untrusted (the reference script uses an AST
whitelist — never eval()), and the loop is bounded (10 iterations).
uv run learning/agentic-operations/exercises/m1_agent.py
# or your own: "What is (137 * 41) + 2**10 / 7? Work step by step."
Stage 4 · Log the world — the M1 exercise
Before every API call, dump the complete context — system prompt, tool
definitions, full message history — to context_log.jsonl. Then run once and
read the log. This is the rubric's exercise #1 and the whole point:
- Turn 1's record is small: system + tools + your question.
- Turn 2's record contains everything turn 1 had plus the model's proposal plus your tool result — because the model retained nothing; you rebuilt its world.
- Watch
input_tokensclimb every turn. That's statelessness, priced.
Stage 5 · Make it reliable
Break it on purpose (gently — the real breaking is Lesson 0006): ask something that makes
the model send a bad expression, or temporarily make the tool raise. The agent must return the
error to the model as a tool_result with is_error: true — and the
model should recover — rather than crashing.
Self-grade — the gate check
- It works — multi-step arithmetic question answered correctly via the tool
- The world is visible — every turn's full context logged, and you read it (can narrate turn 2's contents from memory)
- Failure is handled — tool errors go back to the model as error results; loop is bounded
- You can defend the design — why the tool description says "do not do mental math", why the input is validated, why input tokens grow per turn
Next
Lesson 0006 — Break It On Purpose: overflow the window with junk and measure the degradation; rewrite one vague prompt three ways. Then the M1 readiness check.