Code Execution as a Tool-Invocation Pattern
Code Execution as a Tool-Invocation Pattern#
TL;DR#
Instead of the model emitting one tool_use block per turn and reading every intermediate result back into context, it can write a short program that calls several tools itself inside a sandboxed execution environment, and only the final result (or a distilled summary the code chooses to log) returns to the model. This collapses N round trips and N full results into one round trip and one small result - documented cases report token reductions in the 98-99.9% range for the same underlying task - at the cost of needing a real code sandbox and accepting that failures now live inside model-written glue code instead of a single well-defined tool call.
The idea#
tool-use-agentic-loop/01 through 04 all assume a particular shape for the agentic loop: the model decides on a tool, emits a tool_use block, the harness executes it, the full result comes back as a tool_result, and the model reasons about that result before deciding its next move (or, per lesson 04, several such calls happen in one batch and their results all land in context together). That shape has a hidden cost that scales with two things independently of each other: how many tools exist, and how big each tool's result is. Every tool definition offered to the model costs tokens whether or not it gets called (tool-use-agentic-loop/02), and every result a tool call actually produces gets read into context whether or not the model needs all of it (model-context-protocol/04's result-shaping discussion). Neither problem is solvable by writing better individual tool schemas - it is a problem with the invocation shape itself: direct tool calling forces every intermediate value, on the way to a final answer, through the one channel (the model's context window) that is the most expensive place to put it.
Code execution as a tool-invocation pattern changes the shape, not the underlying capabilities. The same functions - search a calendar, fetch a transcript, write a Salesforce record - are still there, but instead of being offered to the model as individually-schema'd tool_use targets the model calls one at a time, they are exposed as callable functions in a real programming-language API (frequently generated from the existing tool/MCP schemas), and the model's job for a multi-step task becomes: write a small program, in a sandboxed execution environment, that calls those functions, does whatever filtering/looping/branching the task needs entirely in code, and returns (or logs) only what actually needs to come back to the model. A loop that would have been ten tool_use/tool_result round trips becomes one round trip: the model writes the program once, the sandbox runs it and executes all ten underlying calls internally, and the model sees one final result.
This is a genuinely different move from parallel tool calling (tool-use-agentic-loop/04). Parallel calling still puts every individual result back into the model's context - it just does so concurrently instead of one after another, so it saves latency, not context tokens; all N results still arrive, just faster. Code execution instead removes results from the model's context entirely unless the code explicitly decides to surface them - it saves tokens, and its latency story is genuinely different (a sandboxed program can loop, filter, and branch without waiting on a model turn between every step, but you also pay real sandbox cold-start and execution overhead that a direct tool call does not). The two patterns solve adjacent but distinct problems and are not substitutes for each other.
How it works#
The mechanism: tools as an API surface, not a menu#
In direct tool calling, every tool the model might use has to be listed, with its full schema, in every relevant turn - the model chooses among a menu it re-reads each time. In the code-execution pattern, tool definitions are instead exposed as a programming-language API (commonly auto-generated from existing JSON-Schema/MCP tool definitions into TypeScript or Python function signatures with doc comments), organized as something closer to a filesystem or module the model can explore on demand rather than a flat list it must ingest up front. The model writes code against that API, the harness runs the code in an isolated sandbox with network/filesystem access scoped to just the tool backends it needs, the sandbox executes every underlying tool call the program makes, and only the program's return value (or explicit log output) is read back into the model's context as the next turn's input.
Worked example - Cloudflare's "Code Mode" (2026). Cloudflare's Code Mode MCP server exposes an entire API surface - more than 2,500 endpoints across Cloudflare's own API - as a type-aware TypeScript SDK the model can call from generated code, instead of listing 2,500 individual tool schemas. Reported result: interacting with that API surface drops from roughly 1.17 million tokens (loading and using the tools directly) to roughly 1,000 tokens using Code Mode - about a 99.9% reduction for the same task. The mechanism behind that number is exactly the "menu vs. API" distinction above: the model never reads 2,500 tool descriptions into context, and only reads back the few values its own generated code decided to return.
Worked example - Anthropic's meeting-transcript case (Nov 2025). Anthropic's engineering write-up describes an agent whose task is to download a meeting transcript from one system and attach it to a record in another (Salesforce). Under direct tool calling, the full transcript content passes through the model's context twice - once as the result of the "fetch transcript" call, once as the argument the model has to reproduce for the "attach to Salesforce" call - adding on the order of 50,000 tokens for a two-hour meeting, for content the model never needed to read or reason about, only to relay byte-for-byte from one tool to another. Under code execution, the model instead writes a short program that calls "fetch transcript" and "attach to Salesforce" back to back inside the sandbox, passing the transcript directly from one function's return value to the other function's argument in code - the content never enters the model's context at all. Applied across a broader case spanning five MCP servers' worth of typical tool use, the same write-up reports total token usage dropping from roughly 150,000 tokens to roughly 2,000 tokens - a 98.7% reduction - for the same underlying task.
Worked example - filtering before it reaches the model. Consider an agent asked to "find rows in this 10,000-row spreadsheet where status is 'failed' and summarize the top 5 by amount." Direct tool calling has no way to avoid pulling all 10,000 rows into context if the filtering has to happen in the model's own reasoning - that is 10,000 rows of mostly-irrelevant data occupying the context window before the model can even start summarizing. A code-execution agent instead writes a short program: read the spreadsheet, filter for status == "failed", sort by amount descending, take the top 5, return those 5 rows. The filtering, sorting, and slicing all happen inside the sandbox; the model's context receives 5 rows, not 10,000 - the same qualitative pattern as the transcript example, generalized to any data-heavy intermediate step.
What actually changes in the agentic loop#
Compare the two shapes directly for a task requiring three dependent lookups (get user -> get their account -> get that account's recent orders, the same kind of data-dependency chain tool-use-agentic-loop/04 flags as unsafe to parallelize):
Direct tool calling (3 round trips, all results in context):
turn 1: tool_use(get_user) -> tool_result(full user object)
turn 2: tool_use(get_account, user.id) -> tool_result(full account object)
turn 3: tool_use(get_orders, account.id) -> tool_result(full orders array)
model now reasons with: user object + account object + orders array, all in context
Code execution (1 round trip, 1 result in context):
turn 1: model writes:
user = get_user(email)
account = get_account(user.id)
orders = get_orders(account.id)
return summarize(orders) # only this return value enters context
sandbox executes all three calls internally; model sees only the summary
The data-dependency chain that lesson 04 says must stay sequential under direct tool calling is still sequential here - code execution doesn't change dependency structure, it changes how much of each intermediate step's output the model has to read. A three-step dependent chain that would cost three full results in context under direct calling costs one distilled result under code execution, for the identical sequence of underlying calls.
Isolation is not optional: the sandbox is the security boundary#
The model is now writing and executing arbitrary code, not filling in a validated JSON schema - this is a materially larger attack surface than direct tool calling, and it needs the same isolation discipline any untrusted-code-execution problem needs. Current production approaches (2026) converge on the same short list of techniques already covered as background in this domain: microVMs (Firecracker-style, a dedicated lightweight kernel per sandbox, ~125ms boot, low per-VM overhead), gVisor-style user-space kernels (intercept syscalls before they reach the host kernel, trading some I/O overhead for a much smaller attack surface), and hardened containers as a lighter-weight middle ground. Whichever technology is used, the sandbox needs the same least-privilege scoping principle already established for MCP servers in model-context-protocol/04: network egress limited to only the tool backends the task needs, no access to the host filesystem or other tenants' sandboxes, and execution time/resource limits so a runaway or malicious generated program cannot consume unbounded resources. The named vendors and specific technologies here (E2B, Modal, specific microVM/gVisor implementations) are a fast-moving, swappable landscape, not part of the concept itself - what's durable is that "the model's code runs somewhere isolated, with scoped credentials and bounded blast radius," not which vendor currently provides that somewhere.
Debugging shifts from "which call failed" to "where in the program did it fail"#
A failed direct tool call has a small, well-defined failure surface: one call, one set of arguments, one error message, and model-context-protocol/04's actionable-error guidance applies cleanly - the error becomes the model's next input and it can retry or adjust. A failed code-execution turn can fail for reasons a single tool call never could: a syntax error in generated code, an unhandled exception three calls deep into a loop, a logic bug that produces a wrong-but-successful result (no error at all, just silently incorrect filtering or aggregation), or a partial failure where the first four of ten calls inside the program succeeded and the fifth threw, leaving unclear how much of the side-effecting work already happened. None of these map cleanly onto "retry this one call" - the harness and the model both need visibility into where inside the generated program things went wrong, not just that something did, which is a meaningfully harder observability problem than the single-call case.
Pros#
- Order-of-magnitude token reductions on multi-step or data-heavy tasks - documented real cases range from roughly 98.7% (Anthropic, five-server case) to roughly 99.9% (Cloudflare, 2,500-endpoint API surface), because intermediate results never occupy the model's context unless the generated code explicitly returns or logs them.
- Scales to far larger tool libraries than direct calling can practically support - an API surface with thousands of endpoints can be explored and called from code the way any large SDK is used, whereas offering thousands of individual tool schemas to the model directly is prohibitively expensive in context tokens before a single call happens.
- Familiar territory for the model - generated code (loops, conditionals, data transforms) draws on a vastly larger and more natural training distribution than the comparatively narrow, contrived space of direct tool-call examples, which can improve reliability on tasks with real control-flow complexity (filter, sort, branch, retry) beyond what a flat sequence of tool calls expresses naturally.
Cons#
- A real sandboxed execution environment is a hard prerequisite, not a nice-to-have - this is genuine infrastructure (isolation technology, credential scoping, resource limits, monitoring), not a prompting change, and it needs to be built and operated correctly before any of the token savings materialize safely.
- The model's generated code is a new and larger attack/failure surface - arbitrary code execution carries the same risks as any untrusted-code problem (resource exhaustion, attempted sandbox escape, unintended network access), on top of the ordinary risk that the code is simply buggy and produces a wrong result with no error at all.
- Debugging is harder - a failure or a silent wrong-answer lives inside model-written glue code rather than a single well-scoped tool call, and diagnosing it requires visibility into program execution, not just a single call's arguments and error message (see "How it works" above).
- Overkill, and a net cost, for small or shallow tasks - a task that needs one tool call, or a handful of independent calls, gains nothing from being wrapped in a sandboxed program and simply adds sandbox startup/execution overhead and a new failure mode for no token savings, since there was little intermediate data to keep out of context in the first place.
Alternatives#
- Direct tool calling (
tool-use-agentic-loop/01-03) - the default shape; every call and every result passes through the model's context individually. Simpler to reason about, debug, and secure (a validated schema is a far smaller attack surface than arbitrary code), and entirely adequate when the tool library is small and results are compact - which describes most agents. Remains the right default until token cost or tool-library scale actually becomes a measured problem. - Parallel tool calling (
tool-use-agentic-loop/04) - addresses latency for independent calls by executing severaltool_useblocks concurrently, but every result still lands fully in the model's context; it is a complementary optimization to code execution, not a substitute for it, since it solves a different resource (wall-clock time, not token budget). - Server-side result shaping and pagination (
model-context-protocol/04) - a tool author designs each individual tool's output to already be small, filtered, and high-signal, so less needs to be read into context per call in the first place. This reduces the size of the problem code execution solves but doesn't eliminate the N-round-trips-per-task cost that code execution collapses to one; the two techniques compound rather than compete. - On-demand/dynamic tool loading without code execution - some harnesses let the model discover and load only the tool definitions relevant to the current task (reducing the "menu" cost) while still executing each call individually and returning full results (not reducing the "every result in context" cost). A partial fix for the definition-list half of the problem, not the result-size half.
When to use it#
Reach for code execution as the invocation shape when a task involves several dependent or data-heavy steps whose intermediate results the model does not need to read - filtering large collections, relaying bulk content between two systems (the transcript-to-CRM pattern), or working against a tool library large enough that listing every definition directly is itself a meaningful token cost (hundreds to thousands of tools/endpoints). The larger the gap between "how much data flows between steps" and "how much of that data the model actually needs to reason about," the more this pattern pays for itself - and the infrastructure investment (a real sandbox with proper isolation) is justified once that gap is large and recurring, not a one-off.
When NOT to use it#
Do not reach for this pattern for a small tool library with compact results, a single call, or a handful of independent calls best served by parallel execution (tool-use-agentic-loop/04) - the sandbox provisioning and execution overhead, the added attack surface of arbitrary code execution, and the harder debugging story are all real costs with nothing to offset them when there is little intermediate data to keep out of context in the first place. Also avoid it when the task genuinely benefits from the model seeing an intermediate result before deciding what to do next (the same "stop early on the first confirming result" case lesson 04 flags) - collapsing everything into one uninterrupted program removes the model's chance to change course mid-task based on what an early step returned, unless the generated code itself is written to branch on that outcome, which shifts that judgment call into code the model wrote once and cannot revise mid-execution.
Key takeaways / mental model#
Direct tool calling and code execution offer the same underlying capabilities through two different invocation shapes: one call per model turn with every result read into context, versus one program per model turn that calls many tools internally and returns only what the code decides to surface. The trade is real infrastructure (a properly isolated sandbox, scoped credentials, execution limits) and a harder debugging surface (failures live inside generated code, not a single well-defined call) in exchange for a documented order-of-magnitude reduction in tokens on tasks where a lot of data has to move between tools but little of it needs to reach the model's own reasoning. It is not a competitor to parallel tool calling (tool-use-agentic-loop/04, which saves latency, not tokens) and not a replacement for good tool-schema and result design (tool-use-agentic-loop/02, model-context-protocol/04, which shrink the problem this pattern collapses) - it is a third, complementary lever, worth pulling once the tool library or the data volume between steps is large enough that the other two levers stop being sufficient on their own.
Self-check questions#
- An agent needs to fetch a 2-hour meeting transcript from one tool and attach it verbatim to a record in another. Explain, using this lesson's mechanism, exactly where the ~50,000 tokens go under direct tool calling and why code execution avoids that cost entirely rather than just reducing it.
- A colleague argues that since code execution already collapses multiple calls into one round trip, parallel tool calling (
tool-use-agentic-loop/04) is now redundant and the team should standardize on code execution everywhere. Evaluate this claim - what resource does each pattern actually optimize, and is there a task shape where you would still want plain parallel calling? - Your team is deciding whether to invest in a sandboxed code-execution environment for a 12-tool internal agent where results average a few hundred tokens each. Using the When to use it / When NOT to use it sections, make the call and justify it with the specific cost/benefit factors that apply at this scale.
- A code-execution agent's generated program successfully completes 7 of 10 planned tool calls inside the sandbox, then throws on the 8th. Contrast how you would diagnose and recover from this versus a single failed direct tool call, and explain what new information the harness needs to expose to make this tractable.
- Explain why the isolation techniques underneath code execution (microVMs, gVisor-style sandboxes, hardened containers) are treated in this lesson as swappable implementation detail rather than part of the durable concept - what is the actual invariant that has to hold regardless of which one is used?
References#
- Anthropic Engineering: Code execution with MCP - building more efficient AI agents (Nov 2025)
- Cloudflare Blog: Code Mode - the better way to use MCP (2026)
- InfoQ, "Cloudflare Launches Code Mode MCP Server to Optimize Token Usage for AI Agents" (Apr 2026), https://www.infoq.com/news/2026/04/cloudflare-code-mode-mcp-server/
- Northflank, "How to sandbox AI agents in 2026: MicroVMs, gVisor & isolation strategies" (2026), https://northflank.com/blog/how-to-sandbox-ai-agents
tool-use-agentic-loop/02(schema design for individually-invoked tools) andtool-use-agentic-loop/04(parallel tool calls) for the two patterns this lesson differentiates itself frommodel-context-protocol/04(result-shaping and context-cost design at the MCP server level) for the complementary, non-code-execution mitigation