When you give Claude custom tools — each defined with a name, a description, and a JSON Schema input_schema — the API does not execute anything. Instead, when Claude wants to use a tool, the response arrives with stop_reason: "tool_use" and one or more tool_use content blocks, each carrying an id, the tool name, and an input object matching your schema. Your code executes the real function and sends the outcome back. This loop is identical on every platform — first-party, Claude Platform on AWS, Bedrock, Vertex AI, and Foundry — which makes it the most portable agent pattern in the Claude ecosystem.
The loop, step by step
1. Detect. Check stop_reason. If it's "tool_use", iterate over response.content and collect every block whose type is tool_use. Claude can request multiple tool calls in a single response — handle all of them, not just the first.
2. Dispatch. Route each block's name to the matching function in your code and call it with the block's input. Validate inputs even though they came from a schema; adding strict: true to a tool definition guarantees the call matches your schema exactly, which removes a whole class of defensive parsing.
3. Respond. Append Claude's assistant message to the conversation, then send a single user message containing one tool_result block per call — each referencing the originating block via tool_use_id. If your function failed, still return a tool_result, with "is_error": true and an informative message; Claude can often recover or retry sensibly when told what went wrong.
from anthropic import AnthropicFoundry
client = AnthropicFoundry(api_key="...", resource="...")
messages = [{"role": "user", "content": "What's the status of order 123?"}]
resp = client.messages.create(model="claude-sonnet-5", max_tokens=1024,
tools=TOOLS, messages=messages)
while resp.stop_reason == "tool_use":
results = [
{"type": "tool_result", "tool_use_id": b.id,
"content": DISPATCH[b.name](b.input)}
for b in resp.content if b.type == "tool_use"
]
messages += [{"role": "assistant", "content": resp.content},
{"role": "user", "content": results}]
resp = client.messages.create(model="claude-sonnet-5", max_tokens=1024,
tools=TOOLS, messages=messages)
Common mistakes are all bookkeeping: dropping one result when Claude made parallel calls, mismatching tool_use_ids, or forgetting to include the assistant message before the results. If the model uses extended thinking, pass its thinking blocks back exactly as received — modifying them causes a 400 error.
The same name also means something more specific
Confusingly, Anthropic's docs also use "programmatic tool calling" for a distinct, newer feature: letting Claude call your tools from Python code running in the server-side code execution sandbox. Instead of each tool round trip flowing through the model's context, Claude writes a script; your tools appear inside it as async Python functions, intermediate results stay in the running code, and only the final output reaches the model. On search-heavy benchmarks Anthropic reports about an 11% quality improvement with 24% fewer input tokens from this pattern.
You opt a tool in by adding "allowed_callers": ["code_execution_20260120"] to its definition (versus ["direct"] for model-only invocation), and it requires the code_execution_20260120 tool or later. Every tool_use block then carries a caller field telling you whether the model or its code initiated the call — your dispatch loop stays the same. Two caveats: allowed_callers guides the model but is not a hard security boundary, and availability narrows — GA on the first-party API and Claude Platform on AWS, beta on Foundry, and not available on Bedrock or Vertex AI. The classic client-side loop above, by contrast, works everywhere.
Which to use
Where to go next
See tool choice for controlling whether Claude uses tools at all, tool result errors for failure-handling patterns, and code execution for the sandbox that powers the server-side variant.