Claude Architect WorkshopPDFEPUBlearnwithparam.comkaas.team

Foundations, lesson 2

Tool descriptions

The description decides which tool Claude picks

Your agent has four tools, and three of them say "Gets order info". A customer asks about an old order, and Claude picks the tool that only knows recent ones. The lookup finds nothing. This is the agent at Lumen Supply, an online electronics shop. You will watch the wrong tool answer, then fix it with four better descriptions.

I doWatch the failure and its fix.

Claude sees only a name and a description

The Lumen Supply agent has four tools. One looks up recent orders, one finds older orders, one lists orders by email, and one tracks a shipment. Claude never sees the code behind them.

Claude sees each tool's name, its description and its input schema, and nothing else. The input schema lists the fields a tool takes.

The harness is the code around Claude that runs each tool. Claude picks the tool, the harness runs it, and no code checks the pick. The description is the only place to say what a tool is for.

The customer talks to Claude. Claude reads each tool's description to decide which one to call.Customerwrites a ticketClaudereads the descriptionsLook up orderFind orderSearch ordersTrack shipmentthe description is all Claude sees of a toolThe customer talks to Claude. Claude reads each tool's description to decide which one to call.Customerwrites a ticketClaudereads the descriptionsLook up orderFind orderSearch ordersTrack shipmentthe description is all Claude sees of a tool

A clear description gets the right tool

A customer asks about order 1042. Claude stops with stop_reason set to tool_use, and the reply holds one tool_use block: an id, the name Look up order and the input it chose. Open the card to see that input and what the tool sent back.

  1. CustomerWhat is the status of order 1042? I placed it on 12 September.
    Show the result
    What is the status of order 1042? I placed it on 12 September.
  2. Claudepicks Look up order: 1042
    Show the result
    {
      "input": {
        "query": "1042"
      },
      "output": {
        "order_id": "1042",
        "email": "dana@example.com",
        "item": "USB keyboard",
        "amount": 89.0,
        "days_ago": 18
      }
    }
  3. Result{'order_id': '1042', 'email': 'dana@example.com', 'item': 'USB keyboard', 'amount': 89.0…

Claude picked Look up order, the right tool.

Three tools that say the same thing

Three of the four descriptions read "Gets order info". Nothing says which orders each tool covers or what to pass in. A description that reads like a label gives Claude nothing to choose with.

Every tool is sent on every request, so each one costs input tokens and adds a choice. A long list of tools lowers accuracy. The fix here keeps four tools and makes them distinct.

The same idea holds for bigger agents. One agent with 18 tools, where each role needs about 4, picks wrong more often, so give each role only the tools it needs. A subagent is a second agent started for one job. If it inherits every tool, it can call process_refund, and a prompt line against it is only a request.

starter/tools.py
1"""EDIT THIS FILE. The four descriptions are the only thing Claude reads.2Run `./workshop check tool-descriptions` until it is green, then3`./workshop watch tool-descriptions`.4"""56from kit.api import Json789def tool(name: str, description: str, field: str) -> Json:10 """One tool definition with a single string input."""11 return {12 "name": name,13 "description": description,14 "input_schema": {15 "type": "object",16 "properties": {field: {"type": "string"}},17 "required": [field],18 },19 }202122# TODO(workshop): say what goes in, what comes back, which orders each tool23# covers and when to use its neighbour instead. The checks look for "use when",24# the number of days ("90 days") on the two order tools, and "email" on search.25TOOLS: list[Json] = [26 tool("lookup_order", "Gets order info.", "query"),27 tool("find_order", "Gets order info.", "query"),28 tool("search_orders", "Gets order info.", "query"),29 tool("track_shipment", "Gets shipping info.", "order_id"),30]

Which tool will Claude pick for an old order?

Take the ticket about order 0871 from February. Look up order, Find order and Search orders all say "Gets order info".

Predict: Which tool will Claude pick?

  1. ALook up order, the first one that fits. Nothing says it cannot.
  2. BFind order, because the ticket mentions an old date.
Show the answer

A. Claude picked Look up order and got no match. With equal descriptions, the choice is a guess.

The wrong tool answers

This is the ticket about order 0871 with the vague descriptions. The result card is red: Look up order only holds orders from the last 90 days, so it found nothing and the reply says the order is missing.

This is a real Claude Haiku 5.5 run, recorded once and replayed here. The code behind the tools did not change. The wording did.

  1. CustomerWhat was in order 0871? I placed it on 3 February.
    Show the result
    What was in order 0871? I placed it on 3 February.
  2. Claudepicks Look up order: order 0871 placed 3 February
    Show the result
    {
      "input": {
        "query": "order 0871 placed 3 February"
      },
      "output": {
        "error": "No order with that id."
      }
    }
  3. Wrong toolFind order was needed. No order with that id.
    Show the result
    {
      "error": "No order with that id."
    }

Claude picked Look up order. The ticket needed Find order.

Descriptions say what goes in and when to use them

Each precise description gives the input, what comes back, when to use the tool and which tool to use instead. The boundary is a number: 90 days.

A catch-all tool is better split into narrow ones with clear contracts. Rename a tool only after its description is fixed, because the description is what Claude reads.

Descriptions steer the choice. The tool_choice setting forces it. auto is the default and lets Claude answer with no tool. any forces some call, and none forbids calls. A named tool, such as get_customer, forces that one, which a step that must run first needs. This lab sends any.

Messages between the customer, Claude and the tools, in order.CustomerClaudeToolstickettool_use: Find orderresult
tools.pygreen lines are what the fix adds
1"""The four tool descriptions: what goes in, what comes back, when to use.2Each one also names the tool to use instead, so the boundary is clear.3"""45from kit.api import Json678def tool(name: str, description: str, field: str) -> Json:9 """One tool definition with a single string input."""10 return {11 "name": name,12 "description": description,13 "input_schema": {14 "type": "object",15 "properties": {field: {"type": "string"}},16 "required": [field],17 },18 }192021TOOLS: list[Json] = [22 tool(23 "lookup_order",24 "Fetch one order placed in the last 90 days, by order id. "25 "The input query is the order id, such as 1042. Returns the item, "26 "amount and age. Use when the order is under 90 days old. "27 "When the customer gives a date, count the days back from "28 "today first. For older orders use find_order instead.",29 "query",30 ),31 tool(32 "find_order",33 "Fetch one order placed more than 90 days ago, by order id. "34 "The input query is the order id, such as 0871. Returns the item, "35 "amount and age. Use when the order is over 90 days old, "36 "such as a date more than three months before today. "37 "For newer orders use lookup_order instead.",38 "query",39 ),40 tool(41 "search_orders",42 "List every order for a customer when you have an email address "43 "and no order id. The input query is the email. Returns a list. "44 "Use when the customer has no order number. "45 "With an order id, use lookup_order or find_order instead.",46 "query",47 ),48 tool(49 "track_shipment",50 "Get the carrier and delivery date for a shipped order. "51 "The input order_id is the order number. Use when the customer "52 "asks where a package is. For the item or price, use lookup_order.",53 "order_id",54 ),55]
We doChange one setting at a time and predict.

The same ticket now finds the order

Use the switch to run the ticket with the vague descriptions, then the precise ones. On the second button, the card marked "This is where the runs split" is the pick that changed.

The tools did not change. Only the words did.

This part is interactive in the lab: run ./workshop watch tool-descriptions.

Try it: descriptions

Change one setting at a time. Pick the status ticket with vague descriptions. Claude picks the right tool but sends "order 1042" as the query, and the lookup finds nothing. Nothing said what to pass in.

Pick the April ticket and Claude fails again. Pick the email ticket and the vague set still works, because only one tool takes an email. Better names on the vague set still send the February ticket to the recent tool.

On the precise set, a prompt that leans on Look up order moves nothing. On the vague set, that prompt sends even the email and parcel tickets there. Every run is recorded, so this works with no internet.

This part is interactive in the lab: run ./workshop watch tool-descriptions.

The Agent SDK reads the same descriptions

The lab above sent the tool list itself, so you could see what Claude reads. The Claude Agent SDK serves the same four tools from an in-process MCP server, a tool host inside your program. Each name gains an mcp__ prefix. The same wording drives the same choice.

This is a real Claude Haiku 5.5 run through the SDK. With the vague descriptions, the same ticket asks the wrong tool first.

app/sdk_agent.pyfile: 113 lines
1"""The same four tools served by an in-process MCP server on the Agent SDK.23The SDK shows each tool's description to Claude exactly as written, so the4descriptions in `tools.py` decide the pick here too.5"""67import json8from types import ModuleType910from claude_agent_sdk import (11 McpSdkServerConfig,12 SdkMcpTool,13 create_sdk_mcp_server,14 tool,15)1617from kit import sdk18from kit.api import Json19from .prompt import SYSTEM20from .tickets import Ticket21from .tools import run_tool2223SERVER = "support"24PREFIX = f"mcp__{SERVER}__"25MAX_TURNS = 3···5 lines not shown31 def make(spec: Json) -> SdkMcpTool[Json]:32 @tool(spec["name"], spec["description"], spec["input_schema"])33 async def handler(args: Json) -> Json:34 output = run_tool(spec["name"], args)3536 return {37 "content": [{"type": "text", "text": json.dumps(output)}],38 "is_error": "error" in output,39 }4041 return handler4243 return create_sdk_mcp_server(SERVER, tools=[make(s) for s in tools.TOOLS])444546def request_for(ticket: Ticket, tools: ModuleType, tag: str) -> Json:47 """The plain request a recording is keyed by."""48 return sdk.build_request(49 ticket.text,50 SYSTEM,51 tools=[PREFIX + spec["name"] for spec in tools.TOOLS],52 max_turns=MAX_TURNS,53 tag=tag,54 )55···58 lines not shown
  1. CustomerWhat was in order 0871? I placed it on 3 February.
    Show the result
    What was in order 0871? I placed it on 3 February.
  2. Claudepicks Find order: 0871
    Show the result
    {
      "input": {
        "query": "0871"
      },
      "output": {
        "order_id": "0871",
        "email": "omar@example.com",
        "item": "Laptop stand",
        "amount": 45.0,
        "days_ago": 239
      }
    }
  3. Result{'order_id': '0871', 'email': 'omar@example.com', 'item': 'Laptop stand', 'amount': 45.0…

Claude picked Find order, the right tool.

You doFix the starting code, then test yourself.

You do: rewrite the descriptions

Copy the starting code and open work/tool-descriptions/tools.py. All four tools say "Gets order info" or close to it, so Claude guesses. Rewrite each description: what goes in, what comes back, when to use it and which neighbour to use instead.

Terminal
./workshop start tool-descriptions
./workshop check tool-descriptions
./workshop watch tool-descriptions

The checks want four different descriptions, the 90-day boundary on both order tools, an email on search and a "use when" line on each. Then every recorded ticket must pick the right first tool.

Check your understanding

Answer each question, then read why every option is right or wrong. The wrong options are the mistakes engineers make on the job.

1. Two order tools both say "Gets order info". On 31 of 100 tickets about orders older than 90 days the agent asks the recent-orders tool and answers "not found". What fixes it?

  1. ARaise max_tokens so the model has room to think about which tool to call.
  2. BCut each description to two words so the model has less text to compare.
  3. CSay in each what goes in, which orders it covers and the tool to use instead.
  4. DRetry every "not found" with the other order tool, and keep the descriptions.
Show the answer

Answer: C

  • A The pick is one short block of output. The limit was not the cause of the wrong tool.
  • B Shorter text gives the model less to choose with. Two tools with the same two words are still a coin flip.
  • C Correct. The model reads only the description, so the boundary of 90 days has to be written there.
  • D A retry hides the miss and doubles the calls. The next ticket still starts with the wrong tool.

2. After a rename from lookup_order and find_order to get_order and fetch_order, the same 31 of 100 tickets still go to the wrong tool. The one-line descriptions did not change. What is the cause?

  1. AThe descriptions still say nothing about which orders each tool covers.
  2. BThe new names are still not clear enough, so a third rename with better words should fix it.
  3. CTwo tools are too few, so a third order tool should be added to split the traffic between them.
  4. DThe sampling settings are too loose, so the model picks a different tool on each ticket.
Show the answer

Answer: A

  • A Correct. The fix is in the description. Rename after the boundary is written, not instead of it.
  • B Names help a little, but two names with no coverage still leave the choice open. Renaming again repeats the miss.
  • C A third tool with the same kind of description adds one more choice and no more information.
  • D Settings change how varied the pick is, not what the descriptions say.

3. A system prompt says "call lookup_order for any question about an order". The descriptions still all say "Gets order info". Every order ticket now goes to lookup_order, and 38 of 100 are old orders that fail. What is the right fix?

  1. AAdd a routing line for each ticket type that has failed so far, one by one.
  2. BAdd a prompt line that names find_order for old orders, so the descriptions can stay vague.
  3. CSwitch to a larger model, which reads the system prompt more carefully.
  4. DWrite the coverage and the neighbour into each description, and drop the routing line.
Show the answer

Answer: D

  • A A list of ticket types grows with every miss, and the next new ticket type fails again.
  • B This ties the tool boundary to one prompt. Any other prompt or subagent that uses these tools gets no routing and misses again.
  • C A larger model still cannot read coverage that nobody wrote into the descriptions.
  • D Correct. A lean in the prompt only matters because the descriptions do not separate the tools. Fix them first.

4. Select two things a good tool description states.

  1. AThe code that the tool runs, so the model can read how it works.
  2. BWhat the tool takes as input and what it returns.
  3. CThe pass rate the tool had in last month's evaluation.
  4. DWhen to use the tool, and which neighbouring tool to use instead.
Show the answer

Answer: B, D

  • A The model never sees the code. It sees the name, the description and the input schema.
  • B Correct. Inputs and outputs tell the model what to send and what to expect back.
  • C A pass rate is for the team that runs the evaluation. It does not help the model pick a tool.
  • D Correct. The boundary with the neighbour is what keeps two similar tools apart.

5. One agent holds 18 tools and picks the wrong one on 1 in 5 tickets. Each role uses about 4 of them. What is the fix?

  1. AAdd six more tools, so every ticket type has a tool made for it.
  2. BGive each agent only the few tools its role needs.
  3. CKeep one list for everyone, because one list is simpler to maintain.
  4. DPut a paragraph in the prompt that says which tool suits which ticket.
Show the answer

Answer: B

  • A More tools mean more choices and more tokens on every request, and accuracy falls as the list grows.
  • B Correct. A short list per role leaves few tools to confuse, and each tool reads as distinct.
  • C Maintenance is easier, but the miss rate is the cost, and it lands on customers.
  • D A paragraph of routing is a patch for a list that is too long. It also costs tokens on every call.

6. A research subagent called process_refund twice in 50 runs. The tool was in its list because every subagent gets the parent's tools. What should change?

  1. AKeep the shared list, because two calls in 50 runs is a rate the team can accept.
  2. BMove the subagent to a larger model that follows the prompt better.
  3. CGive the subagent only the read tools its research needs.
  4. DAdd a line to its prompt that says never to call process_refund.
Show the answer

Answer: C

  • A Two refunds moved by a research agent is not an acceptable rate. Removing the tool makes it zero.
  • B A larger model may miss less often, but the tool is still there to be called.
  • C Correct. A tool the subagent cannot see cannot be misused.
  • D A prompt rule is a request. It lowers the rate, and two misses in 50 runs are still money moved.

7. A step in the agent must run get_customer first on every ticket. Which tool_choice setting does that?

  1. A{"type": "auto"}, because the model always calls a lookup first.
  2. B{"type": "tool", "name": "get_customer"}, which forces that tool.
  3. C{"type": "any"}, which forces the model to call get_customer.
  4. DA prompt line that says to call get_customer first, with auto left on.
Show the answer

Answer: B

  • A With auto the model may answer without a tool, and it may pick a different one.
  • B Correct. A named tool forces that tool on this request.
  • C any forces some tool call, but the model chooses which. It may call another tool first.
  • D A prompt line makes the call likely, not certain. A step that must run needs the setting.

8. Select two valid values for the type of tool_choice.

  1. Aauto, where the model chooses on its own whether a tool is needed.
  2. Ball, where the model calls every tool once.
  3. Csequential, where the tools run in the order they are listed.
  4. Dany, where the model must call some tool.
Show the answer

Answer: A, D

  • A Correct. auto is the default, and the model may answer without a tool.
  • B There is no such setting. The model calls the tools it needs, not every tool.
  • C There is no such setting. Order comes from the model's turns, not from the list.
  • D Correct. any forces a call, and the model picks which tool.

Play: predict the output

This search tool says what it does, but not what goes in.

python
tool("search_orders", "List the orders for a customer.", "query")

Predict: A ticket reads "Hi, it's Dana Reyes. Where are my orders?" What does Claude send as the query?

  1. AIt asks Dana for her email first, since search needs one.
  2. B"Dana Reyes", since nothing says the query must be an email.
Show the answer

B. The search matches on email, so a name returns nothing and the agent reports no orders. Say what goes in, the email, and say to ask for it when the ticket has none.

References

Conclusions

  • The description is how Claude chooses a tool. It is the interface, not a comment.
  • A good description gives the input, the output, when to use it and which neighbour to use instead.
  • Tool choice is testable: run tickets, count the right picks, fix the words.

You can now write tool descriptions that keep similar tools apart.

Tool description

The text Claude reads to decide when to call a tool

Boundary sentence

A line that says which tool to use instead

Tool choice

The setting that says whether Claude may, must or cannot call a tool

Misrouting

Claude calls a tool that cannot answer the ticket