Learning map / Task 1

Build an agent from scratch, then make it ask the model less

One order desk, one question, four versions of the program that answers it, each cheaper to run than the last. By the end you can state, from a trace (the written record of one run), what each request to the model costs and which change to the program removes it.

Reading: about 25 minutes, no programming needed. Building the four versions yourself, for readers who write Python: an afternoon and an API key (the password your program sends with each request).

Four words, in everyday terms

You do not need to program to follow this page. Four words carry it; each points into the picture of who does what, which comes under the next heading but one:

The scaffold level is how much help this page shows, from worked code at 0 to nothing but the questions at 3.

The level fades what this page shows. The project's tests, automatic checks that run each version of the program and compare what it measures (the three costs the scenario below names) with the expected values, stay the same at every level. Move up when the previous level felt easy, not when it felt finished. Your choice is remembered in this browser only.

The scenario, toy to production

A support desk asks: "For customers C1 to C4, list open orders and the shipping status of each." The data lives in tasks/t01_fewer_tool_calls/store.py. Today it is three customers in a small lookup table in the code (a Python dictionary), and the question names a fourth. The same loop will be asked to do ten thousand customers nightly, and every extra request to the model then costs time and money, and a request that fails costs them again when it is sent again .

The data. Two tools do the work. get_orders takes a customer and returns that customer's open orders, the ones not delivered yet, each with an order number. get_status takes one order number and returns its shipping status, so no status can be asked for until the orders are known. Open orders per customer in the question:

CustomerOpen orders
C12
C21
C33
C4not found

So the question is 2 + 1 + 3 open orders, 6 statuses to look up. C4 is not a customer, so looking it up fails. The model does not know that until it asks, and in this lesson it never asks again: C4 does not exist, so a retry could not help.

Six more words, used on every line below:

Three costs move separately: round trips (waiting, and the whole conversation so far, the transcript, re-read each time), tool calls (your code's work), and tokens .

One round trip, and why each one costs more than the last

A picture to hold on to. Think of the model as an expert you can reach only by phone. They cannot see your systems, and they forget each call the moment it ends. So every time you call, you read back the whole conversation from the start. Then they either give the answer or ask you to look things up: "check C1's orders, and C2's". You hang up, look them up, and call back with everything said so far plus what you found. Each call is a round trip. Each lookup they ask for is a tool call. The read-back is literal: the API keeps no memory between requests, so your code resends the whole conversation every time .

The conversation is a list of messages, each labeled with who sent it: assistant for the model's replies, and user for everything your code sends, tool results included . So a "user message" on this page is your code speaking, not the support-desk person.

Who does what: you, the server code, the modelYou ask the server code once and wait. The server code holds the key and the tools; the model, on Anthropic's side, reads and writes text and cannot run a tool. The server code sends the model 3 requests, each resending everything so far, so they grow from 170 to 430 to 808 tokens. The model replies by asking for lookups (4, then 6), which the server code runs between requests, and the last time with the answer. Each request and its reply is one round trip.Anthropic's side (Claude's maker)APIYou, at the deskask once, then waitServer codethe app, the developer's codeThe modelreads, writes; can't look upcannot look anything upwaitinground trips123total sent1,408 tokensthe questionrequest 1170 tokensrun get_orders × 4runs 4 lookupsC4: not foundrequest 2430 tokensrun get_status × 6runs 6 lookupsall foundrequest 3808 tokensthe answerthe list
Who does what: you, the server code, the modelYou ask the server code once and wait. The server code holds the key and the tools; the model, on Anthropic's side, reads and writes text and cannot run a tool. The server code sends the model 3 requests, each resending everything so far, so they grow from 170 to 430 to 808 tokens. The model replies by asking for lookups (4, then 6), which the server code runs between requests, and the last time with the answer. Each request and its reply is one round trip.Anthropic's side(Claude's maker)APIYouat the deskask, then waitServer codethe app,developer's codeThe modelreads, writes;can't look upcannot look anything upwaitingroundtrips123the questionrequest 1170 tokensrun get_orders × 4runs 4 lookupsC4: not foundrequest 2430 tokensrun get_status × 6runs 6 lookupsall foundrequest 3808 tokensthe answerthe list
  1. You ask the server code your question once, then wait.
  2. The server code sends request 1 to the model: your question and the list of tools it may ask for.The server code sends request 1 to the model: your question and the list of tools it may ask for: 170 tokens for the question, the tool list and the request's wrapping, the field names around them. Tokens are counted as the scenario says, characters divided by four, an estimate of the shape, not a bill. These are input tokens, what is sent; the replies' own tokens are billed too and are not counted here. Every request is paid in full: the API can charge less for an opening it has seen before, and this run does not use that.
  3. The model cannot run a tool, so reply 1 asks the server code to run 4 lookups with get_orders. A reply that asks for tools is labeled tool_use ("run these for me"); the answer is labeled end_turn ("I am done") .
  4. The server code runs the 4 lookups and adds one result per call, all together in one message, to the message history it sends the model . C4 is not found, and that result goes back too. None of this is a round trip: nothing goes to the model.
  5. Request 2 sends everything again, plus reply 1 and its results.Request 2 sends everything again, plus reply 1 and its results: 170 (the question and the tool list) + 260 (reply 1 and its results) = 430 tokens.
  6. Reply 2 asks the server code to run one status lookup per open order, C1's 2 + C2's 1 + C3's 3 = 6, with get_status.
  7. The server code runs the 6 lookups; all are found.
  8. Request 3 sends everything again, plus reply 2 and its results.Request 3 sends everything again, plus reply 2 and its results: 170 (the question and the tool list) + 260 (reply 1 and its results) + 378 (reply 2 and its results) = 808 tokens.
  9. Reply 3 asks for no tool. It is the answer, and the loop ends.
  10. The server code shows you the list, one line per customer, as the model wrote it. Behind it: 3 round trips and 10 tool calls.The server code shows you the list, one line per customer, as the model wrote it. Behind it: 3 round trips and 10 tool calls, and 1,408 tokens sent: 170 (request 1) + 430 (request 2) + 808 (request 3), every request paid in full.

The exact messages, from the same run the tests measure: what each request carried, what each reply said, and how each result went back. To read them: a toolu_ label is the ticket that pairs a result with the call it answers; is_error marks a lookup that failed; the input schema is the form a tool's inputs must take, sent with its name and description; the limits are the most tokens a reply may use. The braces, brackets and quoted names are JSON, the notation the API speaks, and every character of it is counted. stop_reason is the label the loop reads: tool_use means run these, end_turn means done; each call block is typed tool_use as well.

What each request carried, in characters as sent; a part keeps its row, so what repeats reads down a column
partrequest 1request 2request 3
the question173173173
reply 1462462
the results of reply 1576576
reply 2688
the results of reply 2819
the tool list432432432
the rest of the request: the model name, the limits, the field names, the commas between messages757983
characters6801,7223,233
input tokens (divided by four, rounded down)170430808
new in this request: its tokens minus the request before, so the commas and rounding land here170260378
Step 1, exactly: the question, before any request
Not a message to the API. You type the question; the server code builds request 1 from it.
the question: "For customers C1, C2, C3 and C4: list each customer's open orders, then the shipping status of every order. Finish with one line per customer."
Step 2, exactly: request 1
user message (173 characters as sent, its labels and quotes included):
  text: "For customers C1, C2, C3 and C4: list each customer's open orders, then the shipping status of every order. Finish with one line per customer."
tools offered (432 characters, the input schema of each included):
  get_orders: "Open orders for one customer."
  get_status: "Shipping status of one order."
the rest of the request (75 characters): the model name, the limits, the field names and the commas between messages
characters: 173 + 432 + 75 = 680; divided by four, rounded down: 170 input tokens
Step 3, exactly: reply 1
stop_reason: tool_use
  tool_use toolu_0001_get_orders: get_orders({"customer_id":"C1"})
  tool_use toolu_0002_get_orders: get_orders({"customer_id":"C2"})
  tool_use toolu_0003_get_orders: get_orders({"customer_id":"C3"})
  tool_use toolu_0004_get_orders: get_orders({"customer_id":"C4"})
Step 4, exactly: the results of reply 1, as request 2 carries them
sent back in request 2, one tool_result per call:
  tool_result for toolu_0001_get_orders: "{"customer_id": "C1", "orders": ["ord-101", "ord-102"]}"
  tool_result for toolu_0002_get_orders: "{"customer_id": "C2", "orders": ["ord-201"]}"
  tool_result for toolu_0003_get_orders: "{"customer_id": "C3", "orders": ["ord-301", "ord-302", "ord-303"]}"
  tool_result for toolu_0004_get_orders is_error=true: "'C4' not found"
Step 5, exactly: request 2
user message (173 characters as sent, its labels and quotes included):
  text: "For customers C1, C2, C3 and C4: list each customer's open orders, then the shipping status of every order. Finish with one line per customer."
assistant message (462 characters as sent, its labels and quotes included):
  tool_use toolu_0001_get_orders: get_orders({"customer_id":"C1"})
  tool_use toolu_0002_get_orders: get_orders({"customer_id":"C2"})
  tool_use toolu_0003_get_orders: get_orders({"customer_id":"C3"})
  tool_use toolu_0004_get_orders: get_orders({"customer_id":"C4"})
user message (576 characters as sent, its labels and quotes included):
  tool_result for toolu_0001_get_orders: "{"customer_id": "C1", "orders": ["ord-101", "ord-102"]}"
  tool_result for toolu_0002_get_orders: "{"customer_id": "C2", "orders": ["ord-201"]}"
  tool_result for toolu_0003_get_orders: "{"customer_id": "C3", "orders": ["ord-301", "ord-302", "ord-303"]}"
  tool_result for toolu_0004_get_orders is_error=true: "'C4' not found"
tools offered (432 characters, the input schema of each included):
  get_orders: "Open orders for one customer."
  get_status: "Shipping status of one order."
the rest of the request (79 characters): the model name, the limits, the field names and the commas between messages
characters: 173 + 462 + 576 + 432 + 79 = 1,722; divided by four, rounded down: 430 input tokens
the figure splits the same 430 by the request that first sent each part: 170 + 260, where each new part is this request's count minus the one before
Step 6, exactly: reply 2
stop_reason: tool_use
  tool_use toolu_0005_get_status: get_status({"order_id":"ord-101"})
  tool_use toolu_0006_get_status: get_status({"order_id":"ord-102"})
  tool_use toolu_0007_get_status: get_status({"order_id":"ord-201"})
  tool_use toolu_0008_get_status: get_status({"order_id":"ord-301"})
  tool_use toolu_0009_get_status: get_status({"order_id":"ord-302"})
  tool_use toolu_0010_get_status: get_status({"order_id":"ord-303"})
Step 7, exactly: the results of reply 2, as request 3 carries them
sent back in request 3, one tool_result per call:
  tool_result for toolu_0005_get_status: "{"order_id": "ord-101", "status": "shipped"}"
  tool_result for toolu_0006_get_status: "{"order_id": "ord-102", "status": "packed"}"
  tool_result for toolu_0007_get_status: "{"order_id": "ord-201", "status": "shipped"}"
  tool_result for toolu_0008_get_status: "{"order_id": "ord-301", "status": "delayed"}"
  tool_result for toolu_0009_get_status: "{"order_id": "ord-302", "status": "shipped"}"
  tool_result for toolu_0010_get_status: "{"order_id": "ord-303", "status": "packed"}"
Step 8, exactly: request 3
user message (173 characters as sent, its labels and quotes included):
  text: "For customers C1, C2, C3 and C4: list each customer's open orders, then the shipping status of every order. Finish with one line per customer."
assistant message (462 characters as sent, its labels and quotes included):
  tool_use toolu_0001_get_orders: get_orders({"customer_id":"C1"})
  tool_use toolu_0002_get_orders: get_orders({"customer_id":"C2"})
  tool_use toolu_0003_get_orders: get_orders({"customer_id":"C3"})
  tool_use toolu_0004_get_orders: get_orders({"customer_id":"C4"})
user message (576 characters as sent, its labels and quotes included):
  tool_result for toolu_0001_get_orders: "{"customer_id": "C1", "orders": ["ord-101", "ord-102"]}"
  tool_result for toolu_0002_get_orders: "{"customer_id": "C2", "orders": ["ord-201"]}"
  tool_result for toolu_0003_get_orders: "{"customer_id": "C3", "orders": ["ord-301", "ord-302", "ord-303"]}"
  tool_result for toolu_0004_get_orders is_error=true: "'C4' not found"
assistant message (688 characters as sent, its labels and quotes included):
  tool_use toolu_0005_get_status: get_status({"order_id":"ord-101"})
  tool_use toolu_0006_get_status: get_status({"order_id":"ord-102"})
  tool_use toolu_0007_get_status: get_status({"order_id":"ord-201"})
  tool_use toolu_0008_get_status: get_status({"order_id":"ord-301"})
  tool_use toolu_0009_get_status: get_status({"order_id":"ord-302"})
  tool_use toolu_0010_get_status: get_status({"order_id":"ord-303"})
user message (819 characters as sent, its labels and quotes included):
  tool_result for toolu_0005_get_status: "{"order_id": "ord-101", "status": "shipped"}"
  tool_result for toolu_0006_get_status: "{"order_id": "ord-102", "status": "packed"}"
  tool_result for toolu_0007_get_status: "{"order_id": "ord-201", "status": "shipped"}"
  tool_result for toolu_0008_get_status: "{"order_id": "ord-301", "status": "delayed"}"
  tool_result for toolu_0009_get_status: "{"order_id": "ord-302", "status": "shipped"}"
  tool_result for toolu_0010_get_status: "{"order_id": "ord-303", "status": "packed"}"
tools offered (432 characters, the input schema of each included):
  get_orders: "Open orders for one customer."
  get_status: "Shipping status of one order."
the rest of the request (83 characters): the model name, the limits, the field names and the commas between messages
characters: 173 + 462 + 576 + 688 + 819 + 432 + 83 = 3,233; divided by four, rounded down: 808 input tokens
the figure splits the same 808 by the request that first sent each part: 170 + 260 + 378, where each new part is this request's count minus the one before
Step 9, exactly: the final reply, the answer
stop_reason: end_turn
  text: "C1: 2 open orders (ord-101 shipped, ord-102 packed) C2: 1 open orders (ord-201 shipped) C3: 3 open orders (ord-301 delayed, ord-302 shipped, ord-303 packed) C4: could not be found"
Step 10, exactly: the list, after the last reply
Not a message to the API. The server code prints the final reply's text; that is the list you see.
the answer: "C1: 2 open orders (ord-101 shipped, ord-102 packed) C2: 1 open orders (ord-201 shipped) C3: 3 open orders (ord-301 delayed, ord-302 shipped, ord-303 packed) C4: could not be found"
Time runs down the page. You talk only to the server code; only the server code talks to the model. A request to the model is one arrow: the last request's segments again as its tail and one new segment at the head, so each is longer and costs more than the last; Now the cost puts the counts on it, and The exact messages opens what each one carried. The server code is the developer's program, the one this page also calls your code.
Go deeper: what a reply and the next request actually contain

The model's reply is a list of blocks. Here is a reply asking for two lookups at once, which is parallel tool use:

"stop_reason": "tool_use",
"content": [
  {"type": "tool_use", "id": "toolu_1", "name": "get_orders", "input": {"customer_id": "C1"}},
  {"type": "tool_use", "id": "toolu_2", "name": "get_orders", "input": {"customer_id": "C2"}}
]

Your code's next request repeats everything so far, then adds one user message holding a result for each call, matched by id. The results must come straight after the reply that asked for them, and before any other text, or the API rejects the request .

{"role": "user", "content": [
  {"type": "tool_result", "tool_use_id": "toolu_1", "content": "[\"ord-101\", \"ord-102\"]"},
  {"type": "tool_result", "tool_use_id": "toolu_2", "content": "[\"ord-201\"]"}
]}

If a lookup fails, its result is still sent, marked "is_error": true, so the model knows the tool failed rather than returned data. Claude then works the error into its answer, or retries with a corrected request when its input was wrong .

Go deeper: what each of the three costs actually is
CostPaid inGrows withLowered by
Round tripsWaiting: roughly a second before each reply starts, plus the time to write it How many times the model must wait for results before it can ask againAsking in parallel; tools shaped like the task
TokensMoney: every token sent in each request, the resent conversation and every tool result included The length of the conversation, paid again on every round tripFewer, smaller results; prompt caching, which bills a repeated opening of the request at about a tenth of the normal input price on most models
Tool callsYour own systems' time and load, plus their text in the conversationHow many lookups the model asks forTools that take a whole list; tools that do a whole job

The one-second figure is an independent measurement and an order of magnitude, not a constant; it moves with the model and the load. Across ten thousand customers a night, run one after another, every extra round trip per customer adds hours.

What you need to predict

Each order then needs its status. The statuses cannot be asked for before the orders are known, so the work has two stages: orders first, statuses second. The list-taking tools some versions use (listed below) report C4 as "not found" inside a result that still answers the other customers.

CustomerStage 1: its ordersStage 2: a status per order C1→2 orders→2 statuses C2→1 order→1 status C3→3 orders→3 statuses C4→not foundstops here
Each arrow is a dependency: no status can be asked for until the lookup to its left has returned the order. C4 never reaches stage 2.

The tools each version offers the model. A rung is one version of the agent; each rung changes one thing.

RungTools it offers the modelHow the results go backHow the tool reports the failure
v0get_orders (one customer per call) and get_status (one order per call)Each result in its own user messageAs plain text, "Error: ..."
v1The same two toolsAll of a reply's results together in one user messageMarked as an error, is_error
v2get_orders_batch and get_statuses_batch, batch tools: each takes a whole list in one callTogether"not found" inside a result that still answers the other customers
v3open_orders_with_status, one tool that does both stages for a list of customersTogether"not found" inside the result

v0's separate messages still travel together in the next request, and the API accepts them: it combines back-to-back user messages into a single turn . So the split adds no round trip by itself. What it changes, according to the parallel tool use docs, is the model's later replies .

How to count. Count every tool call, including the one that fails. Count every round trip, including the last one, in which the model writes the answer and calls no tool. The tests do not use a real model: a scripted model stands in for it and follows the documented rules of tool use, so the counts are the same on every run. It starts out parallel: each reply asks for every call whose inputs it already knows.

The worked rung: v1, drawn from its run

Time runs down the page. The brackets under each request say what it carries and what that costs.

v1: 3 round trips, 10 tool calls, 1,408 tokens sent (every request added up)Round trip 1: request of 1 message, 170 tokens; the model asks for 4 tool calls; your code runs them. C4 failed, marked is_error. Round trip 2: request of 3 messages (1 sent again), 430 tokens; the model asks for 6 tool calls; your code runs them. Round trip 3: request of 5 messages (3 sent again), 808 tokens; the model answers; C4 reported as not found.v13 round trips, 10 tool calls, 1,408 tokens sent (every request added up)Your codeThe modelRound trip 1from your codethe questionsent: the question and the tool list (170 tokens)request 1: 170 tokens sent#1 get_ordersC1#2 get_ordersC2#3 get_ordersC3#4 get_ordersC4reply 1: asks for 4 tool calls, stage 1, orders (get_orders), and waitsyour code runs 4 toolsnot a request to the model✓ C12 orders✓ C21 order✓ C33 orders✗ C4is_errorRound trip 2the questionsent again: the question and the tool list (170 tokens)from the model4 tool callsfrom your code4 results, 1 failedreply 1 and its results (260 tokens, new)request 2: 430 tokens sent = 170 sent before + 260 new#5 get_statusord-101#6 get_statusord-102#7 get_statusord-201#8 get_statusord-301#9 get_statusord-302#10 get_statusord-303reply 2: asks for 6 tool calls, stage 2, statuses (get_status), and waitsyour code runs 6 toolsnot a request to the model✓ ord-101shipped✓ ord-102packed✓ ord-201shipped✓ ord-301delayed✓ ord-302shipped✓ ord-303packedRound trip 3the questionsent again: the question and the tool list (170 tokens)4 tool calls4 results, 1 failedsent again: reply 1 and its results (260 tokens)from the model6 tool callsfrom your code6 resultsreply 2 and its results (378 tokens, new)request 3: 808 tokens sent = 430 sent before + 378 newthe answer ✓ right answer for C4C4: could not be foundreply 3: the answer, no tool calls; the loop ends
What each mark means

Messages, on the request arrow

  • square corners: a message from your code (the API calls it a user message)
  • rounded corners: a message from the model (the API calls it an assistant message)
  • the same cards, narrower, under a bracket: messages sent again in a later request, paid for again; the bracket says what they are and what they cost

Tool calls, chips on the reply arrow, numbered #1, #2, ... in order. A reply that asks for tool calls has the stop reason tool_use ("run these and come back"); the answer has end_turn ("done")

  • solid outline: a stage 1 call, orders
  • dashed outline: a stage 2 call, statuses
  • thick outline: one call that does both stages

Results, cards between the arrows, from your code

  • tick: a result with data; on the answer card, a right answer
  • cross and a hatched band: a failure the model can see, marked is_error, or named inside a list tool's data (drawn inside the tick card of the result that names it); on the answer card, an answer that is wrong
  • question mark and a dotted outline: a failure sent as plain text, not marked is_error, which this lesson's scripted model cannot read
The last chip is #10, so 10 tool calls; the last row is round trip 3. Call #4, get_orders(C4), fails when your code runs it, goes back at the top of round trip 2 marked ✗ is_error, and counts all the same.

To predict the other three rungs, start from this picture and ask what the one thing each rung changes (the rung table above) does to the chips in each reply, and to the number of replies.

Hint for v0. The model's first reply is parallel, as always. Claude's docs say results split across several messages teach the model to avoid parallel calls . This lesson's scripted model takes that to the limit: once the results of a parallel reply come back split, it asks for one thing per reply from its next reply on. Predict v1 first, then ask what that rule changes for v0 after its first reply.

Predict before you run

Rungs 0 to 3 have worked versions under worked/; rung 4 has none. Work each one out from what the page states, write your numbers, then reveal. A wrong prediction is still the useful outcome: it names the rule you did not yet hold, and the reveal draws every rung the way v1 is drawn above, so you can see where your count and the measured one part ways.

RungYour round tripsYour tool callsMeasured

The ladder

The ladder is the hands-on part, for readers who write Python. If you do not program, everything it builds is explained in the answers after the reveal; skip ahead to the interview questions.

You build in tasks/t01_fewer_tool_calls/learner/agent.py, a copy of rung 0. Run make install once. make practice runs your file against rung 3's target and stays red until it meets it; make test prints every worked rung's trace to compare with.

Rung 0: the toy that works

worked/v0_naive.py. Read it until you can say, line by line, what the model receives. Then run it live once with python -m learnweb.live tasks.t01_fewer_tool_calls.worked.v0_naive (make live runs your own agent instead) and compare with the scripted trace. A live run calls the real API, so it needs ANTHROPIC_API_KEY in .env.

Watch for: the for block in response.content loop appends a user message per result; call_tool returns "Error: ..." as content.

Rung 1: the loop the docs describe

Same tools, correct contract: every result of a turn in one user message; failures flagged with is_error so the model can act on them .

Hint. Collect results in a list inside the turn; append once after the loop. Wrap the tool call in try/except KeyError and set is_error.

Worked: worked/v1_parallel.py and the shared worked/loop.py.

Rung 2: tools that take lists

Consolidate: one call per stage. Fewer tool calls, a shorter transcript, and a description that says when to use it .

Hint. v2 offers only the two list tools. If you keep get_orders alongside them while you build, the scripted model still picks the one whose schema has customer_ids, as a real model told "prefer this when more than one customer is involved" would.

Worked: worked/v2_batch.py.

Rung 3: one tool shaped like the question

The two stages happen in your code. The model makes one call and writes the answer . This is where the target lives: at most two round trips, C4 reported as not found.

Hint. A schema with customer_ids and include_status; store.open_orders_with_status already exists.

Worked: worked/v3_composite.py.

The question you must answer yourself: what did rung 3 give up? Name one situation in which you would keep rung 2's two tools on purpose.

Rung 4: production. Not built for you.

Four words the exit criteria use. A contract test pins each worked rung's measured round trips and tool calls (rungs.json), so a change that costs more fails. The system prompt is the standing instruction sent at the top of every request. A cache breakpoint marks the end of a prefix of the request that the API can reuse from its cache instead of processing again, which is cheaper and faster as long as that prefix does not change . The effort setting tells the model how many tokens to spend on a reply, trading thoroughness for cost and speed .

Exit criteria, each with its source. You add the rung; the contract tests stay green.

Five-minute lessons

Where the round trips come from

Move the sliders, watch the turns. Find the setting where batch and composite cost the same round trips but different tokens.

Spot the bug

An agent loop with five documented mistakes. Click the lines. Wrong clicks stay on the board.

Teach it back

Write tasks/t01_fewer_tool_calls/learner/explain.md: what you changed, why each change moved which cost, and when you would not make it. Then make feedback (it needs ANTHROPIC_API_KEY, like make live). Every finding you get back cites the catalog (web/resources.json, the list of sources this page draws on) or is dropped and counted, so you can check each one yourself.

Interview questions this task prepares

  1. Walk me through one turn of a tool-use loop. What must the next request contain?
    A model answer

    Your code sends the whole conversation and the tool list, because the API keeps no memory between requests . If the reply's stop reason is tool_use, it holds one or more tool calls . Your code runs each one. The next request is everything so far, the model's reply, then one user message with a tool result per call, matched by id, placed before any text, failures marked is_error . Repeat until the stop reason is end_turn.

  2. Your agent makes forty calls for a task that needs four. Where do you look first?
    A model answer

    At a trace, counting round trips and tool calls separately, since they have different causes. Then four suspects, cheapest first. Results split across user messages, which teaches the model to stop calling in parallel . Tools that take one item when a list tool fits, as v1 against v2 shows. Failures not marked is_error, the documented way to say a tool failed, or error messages that do not say what to try next . Tool descriptions that leave the model unsure which tool fits .

  3. When is a coarse tool the wrong design?
    A model answer

    When the questions vary, because a tool shaped for one question needs a sibling for every variant. When the model should judge the in-between results, which a coarse tool hides. When its result would be too large to send whole . And when other tasks reuse the small tools. The guidance favors consolidating ; these exceptions are reasoning, so say so in the interview.

  4. What is the difference between fewer round trips and fewer tokens? Give a case where they move apart.
    A model answer

    Round trips count waits; tokens count text, which is what you pay for. v1 to v2 kept 3 round trips and cut tokens, because 2 calls and results replaced 10. The other way round: v3 has fewer round trips than v2, but if its single result carried every order's full history, tokens could rise. Prompt caching is a third case: it cuts token cost and leaves round trips alone .

  5. How would you measure whether a tool change helped?
    A model answer

    Run the same set of scenarios before and after, and record round trips, tool calls, tokens and whether the answer was right, per completed task rather than per request . This task's own tests do that for one scenario; a real check uses many more, twenty in the production rung, because one scenario can flatter a change.

  6. What does is_error change about what the model does next?
    A model answer

    It tells the model the tool failed, rather than returned data. The docs say Claude works the error into its answer, and retries with corrections when its own input was invalid; write the error so it says what went wrong and what to try next . The docs do not say what happens to an unflagged error, so a candid answer says that too.

Strong answers name a mechanism and a cost, cite the contract rather than a feeling, and include one condition under which the opposite choice is right. Sources: .