Skip to main content
Interpreters give agents a programmable, in-memory workspace inside the agent loop. The agent writes code to complete a task, and the runtime executes it and returns only the relevant results. Intermediate results do not become part of the model context. Where sandboxes are a code-first way for acting on an environment (such as running commands, installing dependencies, and editing files), interpreters are a code-first way for composing tools, preserving state, and deciding what information should return to the model.
Interpreters are in beta. APIs and lifecycle behavior may change between releases.
Interpreters require @langchain/quickjs.

Why use interpreters?

Most agent work alternates between model reasoning and tool calls. A model can fire several tool calls in one turn, but that batch is fixed the moment it is emitted. Nothing can loop, branch on a result, retry a failure, or feed one call’s output into the next without another model turn, and every result returns to the model’s context. The model also decides how many calls to issue, so asking it to dispatch work across hundreds of items is unreliable, and it tends to cover a sample rather than every item. Interpreters move that orchestration into code so the model reasons about what to do, not every intermediate step.

Programmatic tool calling (PTC)

Call selected tools from interpreter code, including loops, retries, branching, and parallel batches.

Dynamic subagents

Dispatch subagents from code for fan-out, verification, and recursive workflows over large inputs.

Stateful work

Keep intermediate values in runtime state without overloading the model context.

Deterministic transforms

Sort, group, parse, validate, score, and aggregate structured data without another model turn.

Choose a pattern

Use interpreters for code inside the agent loop: composing tools, preserving state, and controlling what returns to the model. Use sandboxes for code against an environment: shell commands, package installs, tests, filesystem edits, and OS-level execution.
NeedUse
One or two simple external callsNormal tool calling
Pure in-memory JavaScript: loops, branches, retries, or data transforms (no external tools)Interpreter
Many external tool calls orchestrated from code (requires PTC)Interpreter with programmatic tool calling (PTC)
Many independent units of work, multiple perspectives, or recursive analysis over large inputsInterpreter with dynamic subagents
Shell commands, package installs, tests, or full OS filesystem accessSandboxes

Quickstart

Install the QuickJS middleware package, then pass interpreter middleware using the middleware argument on createDeepAgent.
npm install deepagents @langchain/quickjs
pnpm add deepagents @langchain/quickjs
yarn add deepagents @langchain/quickjs
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "google-genai:gemini-3.5-flash",
  middleware: [createCodeInterpreterMiddleware()],
});
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "openai:gpt-5.5",
  middleware: [createCodeInterpreterMiddleware()],
});
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "anthropic:claude-sonnet-4-6",
  middleware: [createCodeInterpreterMiddleware()],
});
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "openrouter:openrouter:z-ai/glm-5.2",
  middleware: [createCodeInterpreterMiddleware()],
});
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "fireworks:accounts/fireworks/models/glm-5p2",
  middleware: [createCodeInterpreterMiddleware()],
});
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "baseten:zai-org/GLM-5.2",
  middleware: [createCodeInterpreterMiddleware()],
});
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "ollama:north-mini-code-1.0",
  middleware: [createCodeInterpreterMiddleware()],
});

How interpreters work

The middleware adds an eval tool to the agent. When useful, the agent writes JavaScript and calls eval; you do not call the interpreter directly. The tool runs code in a QuickJS context whose variables can persist between eval calls, depending on the persistence mode. It captures console.log, console.warn, and console.error, and returns the result of the last expression. The agent can write code like this:
const rows = [
  { team: "alpha", score: 8 },
  { team: "beta", score: 13 },
  { team: "alpha", score: 21 },
];

const totals = rows.reduce((acc, row) => {
  acc[row.team] = (acc[row.team] ?? 0) + row.score;
  console.log(`${row.team} score: ${acc[row.team]}`);
  return acc;
}, {});

totals;
Code runs against QuickJS, a lightweight JavaScript runtime. By default, interpreter code has no access to the host filesystem, network, shell, package manager, or clock. It can compute, hold state, and write to console.log, console.warn, or console.error, and nothing more. Two explicit bridges extend that reach:
  • Tools, through programmatic tool calling (PTC). Provide an allowlist of tools as async functions under the tools namespace. These can be the agent’s own tools or standalone tools you define and pass in.
  • Subagents, through dynamic subagents. When the agent has subagents configured, the interpreter exposes a task() global for dispatching them from code.
Programmatic tool calling is off until you enable it. Subagent dispatch through task() is on by default whenever the agent has subagents, and you can turn it off. Nothing else crosses the QuickJS boundary.

Programmatic tool calling (PTC)

Programmatic tool calling (PTC) exposes selected agent tools inside the interpreter under the global tools namespace. Instead of asking the model to issue one tool call, wait for the result, and then decide the next call, the agent can write code that calls tools in loops, branches, retries, or parallel batches. This helps when intermediate results are only inputs to the next step: the interpreter filters or aggregates them before anything returns to the model, keeping multi-step workflows token-efficient. It is model-agnostic, implemented by middleware rather than a provider-specific tool-calling API. The middleware exposes each allowlisted tool as an async function under tools. The agent calls it with await, processes the result in code, and the model sees only the final interpreter output, not every intermediate value. Tool names are converted to camelCase while the input object still follows the tool’s schema, so a tool named web_search becomes tools.webSearch(...):
const result: string = await tools.webSearch({
  query: "deepagents interpreters",
});

Enable PTC

Enable PTC with an explicit allowlist:
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "google-genai:gemini-3.5-flash",
  middleware: [createCodeInterpreterMiddleware({ ptc: ["web_search"] })],
});
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "openai:gpt-5.5",
  middleware: [createCodeInterpreterMiddleware({ ptc: ["web_search"] })],
});
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "anthropic:claude-sonnet-4-6",
  middleware: [createCodeInterpreterMiddleware({ ptc: ["web_search"] })],
});
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "openrouter:openrouter:z-ai/glm-5.2",
  middleware: [createCodeInterpreterMiddleware({ ptc: ["web_search"] })],
});
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "fireworks:accounts/fireworks/models/glm-5p2",
  middleware: [createCodeInterpreterMiddleware({ ptc: ["web_search"] })],
});
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "baseten:zai-org/GLM-5.2",
  middleware: [createCodeInterpreterMiddleware({ ptc: ["web_search"] })],
});
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model: "ollama:north-mini-code-1.0",
  middleware: [createCodeInterpreterMiddleware({ ptc: ["web_search"] })],
});
After PTC is enabled, the agent can call the allowlisted tool from interpreter code. This example searches several topics in parallel and combines the results before returning to the model:
const topics = ["retrieval", "memory", "evaluation"];

const results = await Promise.all(
  topics.map((topic) =>
    tools.webSearch({ query: `${topic} best practices 2025` }),
  ),
);

results.join("\n\n");
PTC calls currently execute through the interpreter bridge and do not go through the normal tool calling path. As a result, interruptOn approval workflows are not enforced per PTC-invoked tool call.

Dynamic subagents

The following overview below covers when to use dynamic subagents and a minimal task() pattern. For configuration, orchestration examples, workflow triggers, and safety notes, see Dynamic subagents. Dynamic subagents let the interpreter dispatch configured subagents from code using the built-in task() global. A task that spans many independent units, such as reviewing every file in a directory or triaging a batch of tickets, becomes a loop that fans out work and synthesizes the results. Use dynamic subagents for:
  • Fan-out and synthesize: Run the same kind of work across many items in parallel, then combine the results.
  • Verification: Send findings to independent verifier subagents and keep only confirmed results.
  • Recursive workflows: Keep a working set in interpreter variables, select slices, call subagents, and refine the result.
const paths = ["src/auth.ts", "src/routes/api.ts"];

const reviews = await Promise.all(
  paths.map((path) =>
    task({
      description: `Review ${path} for authentication issues`,
      subagentType: "reviewer",
    }),
  ),
);

reviews.join("\n\n");

Security

Interpreters use QuickJS to run untrusted JavaScript with strict default isolation. Treat that as a scoped interpreter runtime, not a full production sandbox backend. Every tool you expose through PTC is an outside capability that interpreter code can use. Treat the PTC allowlist as a permission boundary: expose only the tools the agent needs, and avoid bridging broad tools that can access sensitive systems, spend money, mutate data, or call unrestricted networks unless that behavior is intentional.
CapabilityAvailable by defaultHow to expose it
JavaScript executionYesAdd interpreter middleware
Top-level awaitYesUse promises in interpreter code
console.log, warn, error captureYesDisable with captureConsole: false
Agent toolsNoAdd a PTC allowlist
Filesystem accessNoAdd the built-in filesystem tools via the PTC allowlist
Network accessNoExpose a specific network tool through PTC
Wall-clock or datetime accessNoExpose an explicit time tool if needed
Shell commands, package installs, tests, OS-level executionNoUse a sandbox backend
How code execution worksInterpreter code runs in a WASM-sandboxed QuickJS runtime via QuickJS-Emscripten, not in the host Node.js process. Treat interpreters as a capability-scoped execution layer: bridge only the tools and subagents the agent needs, and keep the PTC allowlist narrow.

Configuration

createCodeInterpreterMiddleware accepts the following options:
OptionDefaultPurpose
memoryLimitBytes64 * 1024 * 1024
(64 MB)
Cap QuickJS heap memory per session.
maxStackSizeBytes320 * 1024Cap QuickJS stack size per session.
executionTimeoutMs5000Timeout limit in milliseconds for each eval call. Negative values disable the timeout.
toolName"eval"Name of the interpreter tool exposed to the model.
captureConsoletrueCapture console.log, console.warn, and console.error in the tool response. Set to false to discard console output.
maxResultChars4000Truncate result, error, and console output returned to the model to a maximum character count.
systemPromptnullCustom system prompt for the interpreter tool. Defaults to the built-in prompt when null.
ptcomittedAllowlist of tool names or StructuredToolInterface instances exposed as tools.* inside the interpreter. Omit to disable. See Enable PTC.
maxPtcCalls256Maximum tools.* calls allowed per eval. Set to null only in trusted environments. See Programmatic tool calling (PTC) and Security.
subagentstrueExpose the built-in task() global when the agent has subagents. Set to false to require dispatch through the normal task tool. See Dynamic subagents.