Skip to content
Code Mode

Code Mode Isolate Drivers

Code Mode runs model-written code in a sandbox. The sandbox you can use depends on where the server runs.

An isolate driver is that sandbox. All drivers have the same IsolateDriver shape, so you change the driver in one line and keep the rest of your code.

Pick a driver

Your server runs onDriverPackage
Node.jscreateNodeIsolateDriver()@tanstack/ai-isolate-node
BuncreateQuickJSBunIsolateDriver()@tanstack/ai-isolate-quickjs-bun
A browser, the edge, or anywhere without native addonscreateQuickJSIsolateDriver()@tanstack/ai-isolate-quickjs
Any server, with code that runs on CloudflarecreateCloudflareIsolateDriver()@tanstack/ai-isolate-cloudflare
Any server, with code that runs in a full Linux sandboxcreateDaytonaIsolateDriver() or createE2BIsolateDriver()@tanstack/ai-isolate-daytona or @tanstack/ai-isolate-e2b

Then give the driver to createCodeMode. This example uses QuickJS:

ts
import { createCodeMode } from "@tanstack/ai-code-mode";
import { createQuickJSIsolateDriver } from "@tanstack/ai-isolate-quickjs";

const { tools, systemPrompt } = createCodeMode({
  driver: createQuickJSIsolateDriver(),
  tools: [fetchWeather],
});

Nothing else in your route changes.

Compare the drivers

Node (isolated-vm)QuickJS (WASM)QuickJS Bun (bun:ffi)Cloudflare WorkersDaytonaE2B
Best forNode.js serversBrowsers, edge, portabilityBun serversEdge deployments on CloudflareFull remote Linux sandboxesRemote E2B sandboxes
SpeedFast (V8 JIT)Slower (interpreted)Fast (native QuickJS)Fast (V8 on Cloudflare edge)Fast (native runtime, remote call)Fast (native Node, remote call)
Native depsYes (C++ addon)NoneNone (TinyCC at runtime)NoneNoneNone
Runs in a browserNoYesNo (Bun only)N/ANoNo
Memory limitYesYesYesN/ANoYes
Stack size limitN/AYesYesN/AN/AN/A
Setuppnpm addpnpm addbun addDeploy a Worker firstCreate or pass a Daytona sandboxCreate or pass an E2B sandbox

Node.js driver

@tanstack/ai-isolate-node runs the code in a V8 isolate with the isolated-vm native addon. It is the fastest choice on a Node.js server.

shell
npm i @tanstack/ai-isolate-node

The package needs Node.js 22 or later. isolated-vm is a native C++ addon. It must be compiled for your platform.

ts
import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'

const driver = createNodeIsolateDriver({
  memoryLimit: 128, // MB
  timeout: 30_000, // ms
})
OptionTypeDefaultDescription
memoryLimitnumber128Largest heap for the V8 isolate, in MB. The execution stops if it goes over.
timeoutnumber30000Longest wall-clock time for one execution, in ms.
skipProbebooleanfalseSkips the compatibility probe for isolated-vm. If you made sure that the addon works on your Node.js version, you can set it.

QuickJS driver

@tanstack/ai-isolate-quickjs runs QuickJS compiled to WebAssembly. It has no native dependencies, so it runs anywhere JavaScript runs: Node.js, browsers, Deno, Bun, and Cloudflare Workers (with no separate Worker).

shell
npm i @tanstack/ai-isolate-quickjs
ts
import { createQuickJSIsolateDriver } from '@tanstack/ai-isolate-quickjs'

const driver = createQuickJSIsolateDriver({
  memoryLimit: 128, // MB
  timeout: 30_000, // ms
  maxStackSize: 524288, // bytes (512 KiB)
  wasmLocation: '/assets/quickjs/emscripten-module.wasm',
})
OptionTypeDefaultDescription
memoryLimitnumber128Largest heap for the QuickJS VM, in MB.
timeoutnumber30000Longest wall-clock time for one execution, in ms.
maxStackSizenumber524288Largest call stack, in bytes (512 KiB). Increase it for deep recursion. Decrease it to stop runaway recursion sooner.
wasmLocationstringnoneURL or path to load the QuickJS WASM binary from. If you leave it out, quickjs-emscripten uses its bundled binary.

To host the WASM binary yourself, for example on a CDN, serve the synchronous release binary that @jitl/quickjs-wasmfile-release-sync/wasm exports. Then set wasmLocation to its URL. For a URL on another origin, configure the host to allow cross-origin requests.

QuickJS interprets JavaScript and does not JIT-compile it. Code with heavy computation runs slower than on the Node driver. Most generated scripts spend their time on external_* calls, so the difference is small.

QuickJS Bun driver

@tanstack/ai-isolate-quickjs-bun runs QuickJS natively on Bun through bun:ffi, with the quickjs-bun package. Bun compiles the QuickJS C sources one time per process with its embedded TinyCC. You need no native dependencies and no build step. On Bun, it is the fastest sandbox for Code Mode.

shell
npm i @tanstack/ai-isolate-quickjs-bun

The driver needs Bun 1.3.14 or later. On Node.js, it throws an error when it creates a context, so use the Node or QuickJS WASM driver there. On Windows, set the QUICKJS_BUN_NATIVE_LIBRARY environment variable to a prebuilt QuickJS dynamic library.

ts
import { createQuickJSBunIsolateDriver } from '@tanstack/ai-isolate-quickjs-bun'

const driver = createQuickJSBunIsolateDriver({
  memoryLimit: 128, // MB
  timeout: 30_000, // ms
  maxStackSize: 524288, // bytes (512 KiB)
})
OptionTypeDefaultDescription
memoryLimitnumber128Largest heap for the QuickJS runtime, in MB.
timeoutnumber30000Longest wall-clock time for one execution, in ms.
maxStackSizenumber524288Largest call stack, in bytes (512 KiB). Increase it for deep recursion. Decrease it to stop runaway recursion sooner.
maxToolCallsnumber1000Most host tool calls in one execution. It stops untrusted code that fans out, for example Promise.all over a huge array. If the code goes over, the sandbox throws an error that the code can catch.

On Bun, use this driver and not the WASM one. The asyncify bridge of quickjs-emscripten is not reliable for async tool calls under Bun.

Cloudflare Workers driver

@tanstack/ai-isolate-cloudflare runs the code in a Cloudflare Worker. Your server sends the code to the Worker over HTTP. When the code calls a tool, the Worker asks your server, and your server runs the tool. Your tools stay on your server.

shell
npm i @tanstack/ai-isolate-cloudflare
  1. Add a wrangler.toml to your Worker project:
toml
# wrangler.toml
name = "code-mode-worker"
main = "src/worker.ts"
compatibility_date = "2026-05-01"
compatibility_flags = ["nodejs_compat"]

[[worker_loaders]]
binding = "LOADER"
  1. Add the Worker entry. The package exports a ready-made Worker:
ts
// src/worker.ts
export { default } from '@tanstack/ai-isolate-cloudflare/worker'
  1. Run wrangler dev to test locally on http://localhost:8787, or run wrangler deploy.

  2. Point the driver at the Worker URL:

ts
import { createCloudflareIsolateDriver } from '@tanstack/ai-isolate-cloudflare'

const driver = createCloudflareIsolateDriver({
  workerUrl: 'https://my-code-mode-worker.my-account.workers.dev',
  authorization: process.env.CODE_MODE_WORKER_SECRET,
  timeout: 30_000,
  maxToolRounds: 10,
})
OptionTypeDefaultDescription
workerUrlstringnoneRequired. The full URL of the Worker.
authorizationstringnoneA value that the driver sends as the Authorization header on each request.
timeoutnumber30000Longest wall-clock time for the full execution, tool round trips included, in ms.
maxToolRoundsnumber10Most tool-call rounds in one execution. It stops code that calls tools in a loop.

Warning

The Worker runs any code that it gets. Protect the Worker with authentication, rate limits, and an allowed-origin list before you deploy it. The ready-made Worker does not check the Authorization header, so check it in your own Worker or use Cloudflare Access.

The worker_loader binding works with wrangler dev on the Workers Free plan. To deploy, or to run wrangler dev --remote, you need the Workers Paid plan.

Daytona driver

@tanstack/ai-isolate-daytona runs the code in a Daytona sandbox with sandbox.process.codeRun. Your tools stay in your server process. The sandbox gets only the wrapped code and the tool results.

shell
npm i @tanstack/ai-isolate-daytona @daytona/sdk

You need @daytona/sdk only if your app creates the sandboxes with the official Daytona SDK.

ts
import { Daytona } from '@daytona/sdk'
import { createDaytonaIsolateDriver } from '@tanstack/ai-isolate-daytona'

const daytona = new Daytona()
const sandbox = await daytona.create({ language: 'typescript' })

const driver = createDaytonaIsolateDriver({
  sandbox,
  timeout: 30_000,
  maxToolRounds: 10,
})
OptionTypeDefaultDescription
sandboxDaytonaSandboxLikenoneRequired. A Daytona sandbox that you own, or any object with process.codeRun(code, params?, timeout?).
timeoutnumber30000Longest wall-clock time for the full execution, replay rounds included, in ms.
maxToolRoundsnumber10Most tool callback rounds between the sandbox and your server. It stops code that asks for tools in a loop.

The sandbox must use a runtime that can run the JavaScript that Code Mode makes. Your app owns the sandbox lifecycle, file system, network, cleanup, and secret policy. A Code Mode context does not create or delete a Daytona sandbox.

E2B driver

@tanstack/ai-isolate-e2b runs the code as a node process in an E2B sandbox that your app creates. Your tools stay in your server process. The code runs one time, from start to finish.

shell
npm i @tanstack/ai-isolate-e2b e2b

Set E2B_API_KEY in your server environment. Then create a sandbox and give it to the driver:

ts
import { Sandbox } from 'e2b'
import { createE2BIsolateDriver } from '@tanstack/ai-isolate-e2b'

const sandbox = await Sandbox.create()

const driver = createE2BIsolateDriver({
  sandbox,
  timeout: 30_000,
})
OptionTypeDefaultDescription
sandboxE2BSandboxLikenoneRequired. The E2B sandbox that runs the code, for example from Sandbox.create(). A sandbox from @e2b/code-interpreter also works.
timeoutnumber30000Longest time for one execution, tool calls included, in ms.

The memoryLimit of createCodeMode sets the heap limit of the node process.

Your app owns these parts:

  • The sandbox lifecycle. A context does not create, pause, or kill the sandbox.
  • Who shares a sandbox. All executions in one sandbox see the same files and network. Use one sandbox for each user or trusted scope.
  • Secrets. The driver sends no API key or server environment into the sandbox.

Advanced

How the Node driver works

Each execute_typescript call creates a fresh V8 isolate. Your tools enter the isolate as async reference functions. When the code calls external_myTool(...), the call crosses back into the host Node.js process, runs your tool, and returns the result. The driver captures the console output (log, error, warn, info) and returns it with the result. The isolate is destroyed after each call.

The first call to createNodeIsolateDriver runs a probe in a subprocess. The probe finds a native addon that does not match your Node.js version, which can otherwise crash the process with a segfault. If the probe fails, the driver throws an error that tells you to use the QuickJS driver or a supported Node.js version.

How the QuickJS drivers work

The WASM driver runs the synchronous WASM build. It bridges your async tools through QuickJS promises, so the WASM stack does not suspend. All contexts share one WASM module.

The Bun driver gives each context its own native QuickJS runtime, with its own memory limit, stack size, and timeout. Contexts run independently.

On both drivers, a fatal error (out of memory, stack overflow) disposes of the VM and returns a structured error. With the Bun driver, create a fresh context after a fatal error.

How the remote drivers call your tools

The Cloudflare and Daytona drivers use a request and response loop. Your server runs the tools:

plaintext
Driver (your server)              Worker or sandbox
--------------------              -----------------
Send: code + tool schemas  ---->  Run the code
                           <----  Return: needs tool X with args Y
Run tool X locally
Send: tool result          ---->  Continue
                           <----  Return: final result, or needs tool Z
...repeat until done...

Each round trip adds network latency. maxToolRounds stops runaway scripts and caps the number of hops. The driver collects the console output from all rounds and returns it in the final result.

The Daytona driver uses this loop with no parent Worker. It replays the code with the tool results that it has so far.

The E2B driver keeps one node process alive for the full execution:

plaintext
Driver (your server)                 E2B sandbox
--------------------                 -----------
Start one node process    --------->  Run the generated code
                          <---------  Tool call request (stdout)
Run the tool locally
Send the result (stdin)   --------->  Continue from the same point
                          <---------  Final result

Because the process does not replay, side effects in the sandbox happen one time, also when the code calls many tools. A file write and an HTTP request are examples of such side effects.

The E2B driver kills the process group on timeout, on dispose(), and after each execution. This includes processes that the code started, unless they moved to their own process group (detached: true). If the driver cannot confirm the kill after an execution, the result log records it, and the process can still run in the sandbox. dispose() does not throw. The sandbox template must have node, setsid, and timeout. The default E2B templates have them.

The IsolateDriver interface

All drivers implement this interface from @tanstack/ai-code-mode. Implement it yourself to build a custom driver, for example on Docker or a Deno subprocess:

ts
import type { NormalizedError, ToolBinding } from '@tanstack/ai-code-mode'

interface IsolateDriver {
  createContext(config: IsolateConfig): Promise<IsolateContext>
}

interface IsolateConfig {
  bindings: Record<string, ToolBinding>
  timeout?: number
  memoryLimit?: number
}

interface IsolateContext {
  execute<T = unknown>(code: string): Promise<ExecutionResult<T>>
  dispose(): Promise<void>
}

interface ExecutionResult<T = unknown> {
  success: boolean
  value?: T
  logs?: Array<string>
  error?: NormalizedError
}