Interpreters require
langchain-quickjs>=0.1.0 and Python >=3.11.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 one. Interpreters give the agent a runtime for that work. A loop runs every iteration, tools are called from code, intermediate values stay in variables, and only a compact result returns to the model.Programmatic tool calling (PTC)
Call selected tools from interpreter code, including loops, retries, branching, and parallel batches.
Programmatic 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, aggregate, and explore structured data in code.
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.| Need | Use |
|---|---|
| One or two simple external calls | Normal tool calling |
| A small program that loops, branches, retries, or aggregates results | Interpreter |
| Many selected tool calls that should run from code | Interpreter with programmatic tool calling (PTC) |
| Many independent units of work, multiple perspectives, or recursive analysis over large inputs | Interpreter with programmatic subagents |
| Shell commands, package installs, tests, or full OS filesystem access | Sandboxes |
Quickstart
Install the QuickJS middleware package, then pass interpreter middleware using themiddleware argument on create_deep_agent.
How interpreters work
The middleware adds aneval 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 persistent context, captures console.log, and returns the result of the last expression.
The agent can write code like this:
console.log, and nothing more.
Two explicit bridges extend that reach:
- Tools, through programmatic tool calling (PTC). Expose an allowlist of tools as async functions under the
toolsnamespace. These can be the agent’s own tools or standalone tools you define and pass in. - Subagents, through programmatic subagents. Dispatch configured subagents from code and orchestrate them in plain JavaScript.
Programmatic tool calling (PTC)
Programmatic tool calling (PTC) exposes selected agent tools inside the interpreter under the globaltools 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 camel case while the input object still follows the tool’s schema, so a tool named web_search becomes tools.webSearch(...):
Enable PTC
Enable PTC with an explicit allowlist:Programmatic subagents
Programmatic subagents let the interpreter dispatch configured subagents from code using the built-intask() 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 work out and synthesizes the results.
Use programmatic 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.
Persistence
CodeInterpreterMiddleware snapshots interpreter state after each agent run and restores it before the next run by default. A snapshot is a serialized copy of the interpreter’s in-memory JavaScript state, including globals, variables, functions, and imported modules that exist when the agent finishes running code.
Across conversation turns, the lifecycle is:
- A turn starts, and
CodeInterpreterMiddlewarerestores the latest interpreter snapshot for the thread. - The agent calls
eval, and the code can read or mutate interpreter variables. - The agent run finishes, and the middleware snapshots the updated interpreter state into graph state.
- The next turn starts from that restored interpreter state instead of an empty runtime.
eval calls use the live interpreter context object. The middleware does not snapshot and restore between those calls; it snapshots the context when the run completes so it can be restored on a later turn or checkpoint replay.
Between conversation turns, snapshots only retain values that can be reasonably serialized. Use them for data, not for live runtime objects. Functions, classes, and other unserializable values are restored as unaccessible artifacts. If interpreter code accesses one after restore, the eval tool will throw an error like
Value for 'fn' was not restored because it is not serializable (type: function).snapshot_between_turns=False.
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.| Capability | Available by default | How to expose it |
|---|---|---|
| JavaScript execution | Yes | Add interpreter middleware |
Top-level await | Yes | Use promises in interpreter code |
console.log capture | Yes | Disable with capture_console=False |
| Agent tools | No | Add a PTC allowlist |
| Filesystem access | No | Add the built-in filesystem tools via the PTC allowlist |
| Network access | No | Expose a specific network tool through PTC |
| Wall-clock or datetime access | No | Expose an explicit time tool if needed |
| Shell commands, package installs, tests, OS-level execution | No | Use a sandbox backend |
How code execution worksInterpreter code runs in an embedded QuickJS context, not a separate VM or process. In Python, this runtime is provided by
quickjs-rs, which documents the same-process execution boundary in its Security guide.Treat interpreters as a capability-scoped execution layer, not a host-memory isolation boundary. For untrusted or semi-trusted code, run agents in isolated worker processes or containers and keep the PTC allowlist narrow.Configuration
CodeInterpreterMiddleware accepts the following options:
| Kwarg | Default | Purpose |
|---|---|---|
memory_limit | 64 * 1024 * 1024 (64 MB) | QuickJS heap memory limit in bytes. |
timeout | 5.0 | Per-eval timeout in seconds. |
max_ptc_calls | 256 | Maximum tools.* calls per eval. Use None only in trusted environments. |
tool_name | "eval" | Name of the interpreter tool exposed to the model. |
max_result_chars | 4000 | Maximum characters returned from result and stdout blocks. |
capture_console | True | Whether console.log, console.warn, and console.error output is captured. |
subagents | True | Expose the built-in task() global for programmatic subagents. Set to False to require subagent dispatch through the normal task tool path. |
ptc | None | PTC allowlist: list of tool names or BaseTool instances. |
snapshot_between_turns | True | Whether interpreter state snapshots persist across agent turns. |
max_snapshot_bytes | None | Maximum serialized snapshot size. Defaults to memory_limit. |
Connect these docs to Claude, VSCode, and more via MCP for real-time answers.

