Code instead of a toolset
This block compares two ways an agent can use tools: one call at a time, or a script the model writes. It shows where each costs tokens, where code loses, and which checks have to move when you let the model write code. Vendors call this code mode or programmatic tool calling.
Northwind needs every order delayed more than five days last month, out of 10,000 rows, with a follow-up task in the CRM for each one with carrier NorthPost.
Read me the whole ledger, or follow my list
On the phone, your colleague says "read me the ledger" and you read 10,000 rows aloud. They listen to all of it, pick out 312, and ask for 312 follow-ups, one per call, each time with the whole conversation repeated from the top.
Or your colleague dictates a short list: "go through the ledger, keep these rows, open a task for each, tell me how many." You carry it out in a locked back room and phone back with two numbers.
What happens, step by step
On Anthropic's API, this is programmatic tool calling. You mark which tools the script may call:
{
"name": "list_orders",
"description": "Lists orders for a month with status and carrier.",
"input_schema": {
"type": "object",
"properties": {
"month": {
"type": "string"
}
},
"required": [
"month"
]
},
"allowed_callers": [
"code_execution_20260120"
]
}- The model writes Python in which
list_ordersis an async function. It takes a single dict and returns a string. - The script runs in a sandboxed container.
- When the script calls a tool, it pauses, and the API returns a
tool_useblock to your code. Itscallerfield names the code execution run. - Your code runs the tool and sends back a
tool_result. The script resumes, and the result does not enter the model's context. - When the script ends, the model gets its output and continues.
The script itself might look like this:
rows = json.loads(await list_orders({"month": "2026-10"}))
late = [r for r in rows
if r["carrier"] == "NorthPost" and r["days_late"] > 5]
for r in late:
await create_task({"order_id": r["order_id"], "kind": "follow_up"})
print(len(rows), len(late))The model reads two numbers. Cloudflare's Code Mode follows the same idea with generated JavaScript in a V8 isolate. Details differ per product: on Anthropic's API, for instance, tools from an MCP connector cannot be called this way, and strict: true tools are not supported.
What it saves, and when it does not
A result sent back to the model is read again on every later turn, so sequential calls add up fast. The chart uses a 12,000-token prefix, no caching, and our own arithmetic.
Parallel calls close much of the gap, and they need no sandbox. With two small dependent calls, the model must see the first result before it can write the second call, so the script saves no turn.
Published numbers vary with the task, so read who chose it:
Source | Task | Result |
|---|---|---|
Anthropic post | A hypothetical Drive to Salesforce flow | 150,000 to 2,000 tokens |
Cloudflare | Whole Cloudflare API in two tools | 1.17M to about 1,000 tokens of tool definitions |
Anthropic docs | 75-tool benchmark | About 38% fewer billed input tokens, same accuracy |
Anthropic docs | τ²-bench, one or two sequential calls per turn | Same scores, about 8% more cost |
The vendor examples are favorable cases. Quality depends on the model and the chain. In one August 2026 preprint, on a 309-task subset of BFCL v4, code matched or beat JSON calls in 11 of 14 models, while three older GPT models fell behind. In its 52-task chaining test, the advantage reached 18.8 points at chains of 12 or more steps and did not appear on short chains.
Who is watching now
With tool calls the model sees each result and picks the next step. With a script it writes the plan once, and a loop can issue 500 refunds in one turn while the model reads none of the results. How much your code sees depends on the product. On Anthropic's API each call still pauses and comes to your code. Cloudflare's codeMcpServer calls upstream handlers from inside one outer code tool, and its docs say to enforce authorization and approval in each handler.
So the rules from the refund block stay in the tool, and two get added.
- Per-run limits go in the tool: how many refunds, how much money in total, and one refund per order in this run.
- Keys stay outside the sandbox. In Cloudflare's search-and-execute pattern the generated code gets a request function and never sees the credential.
Treat the code as untrusted. An injection the model read earlier can shape the script, and Anthropic says running generated code needs sandboxing, resource limits and monitoring. Hugging Face says no local Python sandbox can be completely secure, so an interpreter inside your own process does not count as a boundary. allowed_callers is not one either: Anthropic's docs say not to rely on it as a security boundary.
How to choose
| Tool calls | Code |
|---|---|---|
Good fit | Few tools, single steps | Many tools, big results, long chains, loops |
Infrastructure | None extra | Sandbox, limits, network control |
Oversight | The model reads each result | The check is in the tool |
The choice can be per tool. Northwind's list_orders goes through code. issue_refund stays a direct tool with its own checks, because someone should see each refund.
Conclusion
- Code keeps results out of the context, so it pays off with large data and long chains, and can cost more on short ones.
- Vendor savings are favorable examples. Measure on your own tasks and models.
- Model-written code is untrusted. It needs a real sandbox, keys outside, and limits inside each tool.