
Build an Autonomous Agent with Claude Sonnet 5: Tool Use, the Agentic Loop, and Self-Verification
Claude Sonnet 5 (model id claude-sonnet-5) is the most agentic Sonnet yet — it plans, uses tools, and checks its own output. Define tools as JSON Schema, run an agentic loop (tool_use to tool_result until stop_reason is end_turn), use adaptive thinking with effort, add a self-verification step, and cache the system+tools prefix. At $2/$10 intro pricing through Aug 31 2026, running real agents is cheap.
Claude Sonnet 5 just landed — and it’s built for agents
Claude Sonnet 5 (claude-sonnet-5) shipped June 30, 2026 — Anthropic’s most agentic Sonnet yet, at $3/$15 per MTok with $2/$10 introductory pricing through August 31, 2026 (confirm current before you ship). This tutorial builds a working autonomous, tool-using, self-checking agent on the Messages API: the tools[] array, the tool_use/tool_result loop, self-verification, and prompt caching to cut the bill.
The launch framing, briefly. It’s live across every Claude app and on the Claude Platform/API right now, and it’s already the default model on Free and Pro — Max, Team and Enterprise get it too. The model id claude-sonnet-5 is a dateless pinned-snapshot id, used as both the API id and the alias, so do not append a date suffix to it. On Bedrock it’s anthropic.claude-sonnet-5; on Google Cloud it’s claude-sonnet-5.
Anthropic calls it “the best combination of speed and intelligence” and “the most agentic Sonnet yet” — it plans, drives tools like browsers and terminals, and runs autonomously at a level that a few months ago needed a bigger, pricier model. You can read the details in Anthropic’s announcement and the models overview.
But this isn’t launch-day news. This is a build — the actual code, the loop, the gotchas. If you’re new to the concept, start with what is an AI agent and the Python agent foundation; this post goes deeper into the loop itself.
Why Sonnet 5 changes the math on agents
Here’s my honest take, labeled as opinion: the headline for builders isn’t the benchmark — it’s COST times AGENCY. You can now run autonomous, tool-using, self-checking agents at Sonnet prices, and that changes which projects are worth building.
The numbers, as of launch 2026 (confirm current pricing before you ship): standard is $3 per input MTok and $15 per output MTok. There’s introductory pricing of $2 / $10 per MTok through August 31, 2026, after which it reverts to $3/$15. At standard pricing that’s roughly 60% cheaper per token than Opus 4.8 ($5/$25). An agentic loop burns tokens every iteration — plan, call a tool, read the result, re-plan — so a per-token discount compounds across a multi-step run.
Now the part launch posts skip. Sonnet 5 is not the absolute frontier. On an agentic-coding evaluation, Anthropic’s reported figures put Sonnet 5 at about 63.2% versus Opus 4.8 at about 69.2% and Sonnet 4.6 at about 58.1%. It was also evaluated on agentic search and computer-use suites at different effort levels. Confirm the exact eval names in the announcement — I’m not going to pretend I memorized them. The takeaway: Sonnet 5 closes most of the gap to Opus 4.8 at a fraction of the cost, but Opus 4.8 still leads on the hardest agentic coding.
So my verdict: pick Sonnet 5 for agentic workloads at scale, and use Haiku 4.5 ($1/$5) for the cheapest, fastest simple calls. Before you escalate to Opus 4.8 for hard agentic coding, try Sonnet 5 at xhigh effort first — it closes more of the gap at Sonnet pricing, and it’s the cheaper lever to reach for. Reach for Opus 4.8 only when you genuinely need the top. Sonnet 5 is the value-tier sweet spot, not the frontier.
The specs that matter for agents: a 1M-token context window, 128k max output (up to 300k on the Batch API with the output-300k-2026-03-24 beta header), a reliable knowledge cutoff of January 2026, and “Fast” comparative latency.
One-shot prompt
- Single request, single answer
- No tools, no real-world actions
- Cheap per call but can't do multi-step work
- Fine for summaries, extraction, rewriting
Agentic loop
- Plan, use tools, verify, finish
- Calls real tools across many turns
- Burns tokens each iteration — so per-token price matters
- Sonnet 5 intro $2/$10 makes repeated runs affordable
How the agentic loop actually works
Before any code, let’s kill the magic. The loop is a contract between your code and the model:
- You send the user’s task plus a
tools[]array describing what the agent can do. - Sonnet 5 plans and emits a
tool_useblock — a tool name, an input object, and a uniquetool_useid. - Your code runs the real tool. The model never runs anything; it only asks.
- You append a
tool_resultblock that references that exacttool_use_id, with the tool’s output as content. - You send everything back and loop.
The signal that drives the loop is stop_reason. While it’s tool_use, you keep going. When it flips to end_turn, the model has its final answer and you stop. (Also handle pause_turn, which can appear on long autonomous runs — just send the response back to continue.)
That separation — the model asks, you execute — is the entire security and control story. You decide what actually runs. Adaptive thinking lives on this surface too, so the model interleaves planning between tool calls without you managing a budget. And because Sonnet 5 is cheap, running that multi-step loop repeatedly is finally affordable.
Define your tools (JSON Schema with prescriptive descriptions)
Each tool needs three things: a name, a description, and an input_schema in JSON Schema with typed properties and a required list. The description is your steering wheel — lead with “Call this tool when…” and say what NOT to use it for. Description quality matters more than the schema: tell the model the exact situation that should trigger the call.
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "get_exchange_rate",
"description": (
"Call this tool when the user asks for the current exchange rate "
"between two currencies (e.g. USD to MXN). Returns the latest rate "
"as a float. Do NOT use it for historical rates or for converting a "
"specific amount — for that, first get the rate, then compute."
),
"input_schema": {
"type": "object",
"properties": {
"base": {"type": "string", "description": "ISO code, e.g. USD"},
"quote": {"type": "string", "description": "ISO code, e.g. MXN"},
},
"required": ["base", "quote"],
},
},
{
"name": "lookup_invoice",
"description": (
"Call this tool when you need details for a specific invoice by id. "
"Returns amount, currency and status. Use ONLY when you already have "
"an invoice id — never guess an id."
),
"input_schema": {
"type": "object",
"properties": {
"invoice_id": {"type": "string", "description": "Invoice id, e.g. inv_123"},
},
"required": ["invoice_id"],
},
},
]
Keep this tools[] array and your system prompt stable across turns — they repeat on every iteration, and we’ll cache that prefix later to cut the bill. Pin the model explicitly: model="claude-sonnet-5". If you want the tools to reach real data, wire them up through MCP — see connect an AI agent to your data with MCP.
Write the loop: tool_use in, tool_result out
Here’s the full manual loop. The non-negotiable rule: every tool_result must carry the tool_use_id of the block it answers, and you must append both the assistant turn (the model’s tool_use) and your user turn (the tool_result blocks) in order. Mismatch the ids or the order and the API rejects the request.
def run_tool(name, tool_input):
if name == "get_exchange_rate":
# Real implementation goes here.
return {"rate": 17.12}
if name == "lookup_invoice":
return {"amount": 4200, "currency": "MXN", "status": "paid"}
return {"error": f"unknown tool: {name}"}
messages = [{"role": "user", "content": "What's the USD to MXN rate right now?"}]
MAX_ITERS = 10 # guard so a misbehaving agent can't loop forever
for _ in range(MAX_ITERS):
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=2048,
# Adaptive thinking is ON BY DEFAULT on Sonnet 5; this line is
# explicit for clarity. Pass {"type": "disabled"} to turn it off.
thinking={"type": "adaptive"},
output_config={"effort": "medium"}, # defaults to "high" on the API
tools=tools,
messages=messages,
)
if response.stop_reason == "end_turn":
break
if response.stop_reason in ("tool_use", "pause_turn"):
# Append the assistant turn exactly as returned.
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = run_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id, # MUST match the tool_use block
"content": str(result),
})
if tool_results:
messages.append({"role": "user", "content": tool_results})
# pause_turn with no tool_use: loop again to let it continue.
final = next((b.text for b in response.content if b.type == "text"), "")
print(final)
A few things to internalize. Set effort explicitly via output_config — the levels are low/medium/high/xhigh/max, and it defaults to high on the Claude API and Claude Code. Drop to medium or low for cheaper, faster dispatch when the task is simple; step UP to xhigh for the hardest coding and agentic tasks (that’s the cheaper lever to pull before reaching for Opus 4.8). Adaptive thinking is on by default on Sonnet 5, so you don’t need a thinking block at all — set it explicitly only for clarity, and pass thinking={"type": "disabled"} to turn thinking off. There is no budget_tokens on this surface — manual thinking returns a 400 — and sampling params (temperature, top_p, top_k) are not used either, so don’t pass them.
And the honest operational gotcha: your tool handlers may get called more often than you expect — retries, re-plans, the model second-guessing itself. Make side-effecting tools idempotent (dedup keys, check-before-write) so a repeated call is safe. The MAX_ITERS guard is there for the same reason.
- Define your toolsname + prescriptive description + input_schema (JSON Schema)
- Write the loopCall claude-sonnet-5 with tools[] and adaptive thinking
- Execute tool_use, return tool_resultMatch tool_use_id exactly; append assistant + user turns in order
- Let it self-checkVerify the result against success criteria before accepting
- Stop on end_turnWith a max-iterations guard so it can't loop forever
Let it check its own work (self-verification)
Early-access partners report that Sonnet 5 finishes tasks where previous Sonnets stopped short — and that it checks its own output without being asked. Don’t leave that to luck. Make it a feature.
Two patterns work. The first is a system-prompt instruction: tell the model to verify its result against the task’s success criteria before finishing, and to keep working if it finds a gap. The second is a verify_result tool the agent must call, returning pass/fail with reasons — this forces the check into the loop where you can log it.
verify_tool = {
"name": "verify_result",
"description": (
"Call this tool BEFORE giving your final answer. Re-check your result "
"against the task's success criteria. Return passed=false with a reason "
"if anything is missing, then fix it and verify again."
),
"input_schema": {
"type": "object",
"properties": {
"passed": {"type": "boolean"},
"reason": {"type": "string"},
},
"required": ["passed", "reason"],
},
}
On top of the self-check, use structured outputs so the final answer comes back in a schema you validate in code — belt and suspenders. This output_config.format goes on the model call that produces the final answer (the last loop iteration), not as a separate extra request — wire it into the existing loop:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=2048,
thinking={"type": "adaptive"},
output_config={
"effort": "medium",
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"answer": {"type": "string"},
"confidence": {"type": "number"},
},
"required": ["answer", "confidence"],
"additionalProperties": False,
},
},
},
tools=tools,
messages=messages,
)
Honest caveat: self-verification reduces errors, it doesn’t eliminate them. Keep your own assertions on any side-effecting action. The model checking itself is a safety net, not a guarantee.
The SDK tool runner vs the manual loop — which to use
You don’t always have to hand-roll the loop. The Python SDK ships a built-in tool runner — client.beta.messages.tool_runner, with tools registered via the @beta_tool decorator — that drives the tool_use to tool_result cycle for you. (In the TypeScript SDK the equivalent is toolRunner.) Register your tool functions, hand it the task, and it loops. That’s the least code and the fastest way to a working prototype.
The manual loop earns its keep when you need control: custom retry and idempotency logic, observability and logging on every tool call, human-in-the-loop approval gates before a side-effecting tool fires, or non-standard dispatch. My take — start with the SDK runner to prototype, then drop to the manual loop the moment you need to instrument or gate the loop.
Either way, the underlying contract is identical: stop_reason, a matching tool_use_id, end_turn. Everything you learned above transfers.
And one cost lever applies to both: prompt caching. Your tools[] array and system prompt repeat on every single turn of the loop, so cache that prefix with cache_control and stop paying full input price each iteration. On a long multi-step run that’s real money saved — on top of the $2/$10 intro pricing. I wrote up the mechanics in cut token costs with Claude prompt caching.
Migrating an existing Sonnet 4.6 agent
If you already have an agent on Sonnet 4.6, the move is mostly mechanical:
- Change the model string to
claude-sonnet-5. - Drop
budget_tokens— Sonnet 5 uses adaptive thinking (on by default, nothinkingblock required) and manualbudget_tokensreturns a 400. Control depth witheffortinstead. - Re-tune
effort. Sonnet 5 defaults tohigh, which may cost more and run slower than you want for simple tool dispatch — set it explicitly (the levels are low/medium/high/xhigh/max). - Drop any
temperature/top_p/top_ksampling params; they aren’t used on this surface. - Confirm the exact breaking changes against Anthropic’s migration guide before you ship. Don’t trust this list blind — the model is brand new.
FAQ
What’s the exact model id? claude-sonnet-5 — a dateless pinned snapshot used as both the API id and the alias. No date suffix.
How much does it cost? Standard $3/$15 per MTok; introductory $2/$10 through August 31, 2026, then back to $3/$15. Confirm current pricing at the pricing page before you commit a budget.
Can I set a thinking budget? No. Adaptive thinking only, on by default — manual budget_tokens returns a 400. Control depth with output_config.effort (low/medium/high/xhigh/max), not budget_tokens.
Is it as good as Opus 4.8? Close on agentic tasks — about 63.2% vs about 69.2% on an agentic-coding eval (Anthropic’s reported figures; check the announcement for the exact eval names) — but Opus 4.8 still leads on the hardest agentic coding. Try Sonnet 5 at xhigh effort before escalating to Opus. Sonnet 5 is the value tier.
How do I keep the loop from running forever? Add a max-iterations guard and stop on stop_reason == "end_turn".
How big is the context and output? 1M-token context window, 128k max output — up to 300k on the Batch API with the output-300k-2026-03-24 beta header.
Ship it
The whole recipe in one breath: model claude-sonnet-5, a tools[] array with JSON Schema and “call this when…” descriptions, a loop on stop_reason == "tool_use", adaptive thinking plus effort, a self-verification step, and prompt caching on the system+tools prefix. That’s an autonomous, self-checking agent.
Sonnet 5 makes that kind of agent genuinely affordable to run — especially inside the $2/$10 intro window through August 31, 2026. If you want more patterns, I keep a running set of AI-agent guides.
One last thing, because this stuff is brand new and moves fast: always confirm model ids, pricing, and API fields against Anthropic’s live docs before you ship. Then go build something that does the work for you.