codemode-executor
Calling one tool at a time costs a round trip per call, and every result goes into the model’s context whether anything needed it or not. A program collapses that into one execution. Your tools get called from inside it, the rows nobody needs stay where they are, and only the value the program returns goes back to the model. Cloudflare’s Code Mode makes that trade on the Workers platform. A program written by a model arrives unreviewed, so it runs in a V8 isolate created for the one call, inside a child process the executor supervises. This project ships that isolate, the bounds around it and the boundary the tool calls cross, and a Node process you run is all it needs underneath.
One execution
Section titled “One execution”One call runs the program, routes its tool call into your process and returns the total:
import { IsolateExecutor } from '@supolka/codemode-executor'
const overdue = new Map([ ['acme', [120, 340, 830]], ['globex', [55]],])
const executor = new IsolateExecutor()
const { result, error, logs } = await executor.execute({ code: `async () => { const amounts = await billing.overdue('acme')
return amounts.reduce((total, amount) => total + amount, 0) }`, providers: [{ name: 'billing', tools: ['overdue'] }], dispatch: async (provider, tool, argsJson) => { const [account] = JSON.parse(argsJson)
return JSON.stringify({ result: overdue.get(account) }) }, outbound: null,})// { result: 1290, logs: [] }
await executor.dispose()billing is the proxy the executor generated from that declaration, and billing.overdue('acme') sends the provider name, the tool name and the arguments out of the isolate as three strings. dispatch receives them in your process, where your data and your credentials already are. Its return value is the only thing that crosses back into the isolate: it travels as JSON, the proxy unwraps result, and the program adds the three numbers up without any of them passing through the model.
Hosts read error first. Programs that throw put their message there and leave result undefined, and that text is the input to the model’s next attempt. execute still resolves, because a program that failed is an outcome and not a fault on your side. But a broken worker or a full queue rejects the call, with a code on the error. That envelope is also all that survives the run, because the isolate starts with an empty global and is disposed as the result comes back. retainHandles on the request is the exception: a retained execution keeps its isolate alive after the call returns.
Bounds on every run
Section titled “Bounds on every run”V8 enforces the memory limit and ends a program that exhausts the isolate’s heap. Time is measured on the host clock and never from inside the guest. A program that awaits is ended by the entry module inside the isolate; one that never yields is ended by the worker outside it, a grace period later.
A caller that never gets a worker is a different problem. execute rejects once the queue is full, instead of taking work the pool cannot finish. Every message a run sends outward is bounded where it is built, because every transport in the path caps one message. The failure then lands on the run that sent it, and the transport stays up.
Each of those bounds is a safety claim, and the threat model names the boundary behind it, says where that boundary stops, and hands the rest to whoever deploys it. An adversarial corpus attacks those boundaries from inside the isolate on every run of pnpm verify. A case passes on what the host observed and never on what the program reported. Benchmark states the method behind every number.
Where to go
Section titled “Where to go”Start here if you have not run it yet, and the two pages under it get a first execution out of either arrangement. From there the guides take one host decision per page: what the model may call, whether a program can use the network, which bounds apply to it, how failures come back. Agent loop with the AI SDK puts the whole chain together, from the prompt to your own tool function. But hosts already running @cloudflare/codemode belong at Replacing DynamicWorkerExecutor instead. That page names the one default that reverses when you swap.
Executor protocol states what another implementation has to satisfy, Deploying the daemon covers what a deployment decides, and the internals chapters describe each subsystem from the code that implements it. Changing any of it starts at contributing.