Learning / Agentic Operations / Agent Loop & Tool-Call
Agentic Operations · Reference · Level 1 · Foundations · Code
The Agent Loop & Tool-Call Round-Trip
The skeleton every tool-using agent shares. Distilled from the Lesson 0005 build; full working script at ../exercises/m1_agent.py.
The loop in one line: call → observe → decide → repeat —
and every iteration rebuilds the model's entire world from the
messages list.The round-trip, step by step
- 1. You send: system prompt + tool definitions + full message history.
- 2. Model proposes: the response's stop-reason says
tool_useand its content includes a block naming a tool + arguments. This is just structured text. - 3. Your code executes: validate the input, run the real function, capture the result (or the error).
- 4. You append: the assistant's content (verbatim), then a user message containing a
tool_resultblock whosetool_use_idmatches the proposal's id. - 5. Repeat until the stop-reason is no longer
tool_use.
Minimal loop (Python, Anthropic SDK)
messages = [{"role": "user", "content": question}]
for turn in range(1, 11): # always bound the loop
response = client.messages.create(
model=MODEL, max_tokens=16000,
system=SYSTEM, tools=TOOLS, messages=messages,
)
if response.stop_reason != "tool_use":
break # model is done
messages.append({"role": "assistant", "content": response.content})
results = []
for block in response.content:
if block.type == "tool_use":
try:
out, err = run_tool(block.name, block.input), False
except Exception as e:
out, err = f"Error: {e}", True # tell the model, don't crash
results.append({"type": "tool_result", "tool_use_id": block.id,
"content": out, "is_error": err})
messages.append({"role": "user", "content": results})
Reliability rules (the "reliable" in the M1 gate)
- Bound the loop. A fixed iteration cap converts an infinite loop into a visible failure.
- Return errors as tool results (
is_error: true) — the model recovers; a crash doesn't. - All parallel tool results go back in ONE user message, one result block per proposal, ids matched.
- Append the assistant content verbatim — don't extract just the text; the proposal blocks are part of the conversation.
- Validate tool inputs in your code. The model's arguments are untrusted text (see the 0005 calculator: AST whitelist, never
eval()).
Reading cost off a response
u = response.usage
u.input_tokens, u.output_tokens # tokens this call
cost = u.input_tokens/1e6 * PRICE_IN + u.output_tokens/1e6 * PRICE_OUT
Note what grows: every loop iteration re-sends the whole history, so input tokens climb each turn — the loop's cost curve is the statelessness lesson made visible.
Tool definitions are prompts
{"name": "calculator",
"description": "Evaluate one arithmetic expression (...). Call this for
every arithmetic step; do not do mental math.",
"input_schema": {"type": "object",
"properties": {"expression": {"type": "string", "description": "..."}},
"required": ["expression"]}}
The model chooses tools by reading the description — state when to call it, not just what it does. Official reference: tool use docs.