Wrap a sandbox as an agent tool
An agent calls tools one at a time. If you register a dozen tools directly, the model must
plan and issue every call itself, one turn per call. createSandboxTool collapses that:
it exposes an entire Sandbox as one tool whose input is a TypeScript script. The
model writes a script that orchestrates many of your tools — with loops, branches, and
Promise.all — and the sandbox runs it as a single resumable unit. This guide wires that
tool into an agent run. For the run loop itself, see
Configure and run an agent.
Wrap the sandbox
Section titled “Wrap the sandbox”Build a Sandbox with the tools you want the model to orchestrate, then wrap it. A Sandbox
takes a Record<string, Tool> keyed by the name each is called under in a script — from
providers.toTools(), or built with defineTool / toTool. See
Tools and transports. createSandboxTool is async — it
reads the sandbox’s generated types up front to build the tool description — so await it:
import { Sandbox, createSandboxTool } from '@grundlag/sandbox';
const sandbox = new Sandbox({ actions: tools });
const runScript = await createSandboxTool(sandbox, { id: 'sandbox.runScript', name: 'Run script',});The options are all optional:
| Option | Default | Description |
|---|---|---|
id |
'sandbox.runScript' |
The tool id. |
name |
'Run script' |
The tool name shown to the model. |
description |
generated | The tool description. The default is generated from the sandbox’s tool types so the model sees the exact globals and signatures it may call. |
The generated description tells the model what it is working with: a persistent data global
that carries state between calls like a notebook kernel, console.log as the only way to
surface results, and one typed async function per action — all in a sealed runtime with no
network, filesystem, or imports.
Wire it into an agent run
Section titled “Wire it into an agent run”The result is a normal Tool, so it goes into AgentRun’s actions map like any other:
import { AgentRun } from '@grundlag/agent';import OpenAI from 'openai';
const client = new OpenAI();
const run = new AgentRun({ client, model: 'gpt-4o', systemPrompt: 'You accomplish tasks by writing TypeScript scripts for the sandbox.', prompts: [userPrompt], actions: { runScript },});Now the model has a single tool. To do work it emits a script; the tool runs it and returns
the script’s console output plus the keys currently on data as the tool result. The
result is deliberately shaped so the model sees only what the script chose to log — data
itself stays internal.
What the tool threads for you
Section titled “What the tool threads for you”createSandboxTool handles the plumbing that makes multi-turn scripting coherent:
dataacross turns. The tool stores the sandbox’sdataobject as a conversation item and feeds the latest version into the next script, so state accumulates across calls like cells in one notebook kernel. A newdataversion is emitted only when a script completes successfully — a rejected or abandoned run leaves the notebook state untouched.- The call log across turns. When a script interrupts, the tool checkpoints its call log
and position into the interrupt’s private
state. It never leaks to the model. - Interrupts surfaced as agent interrupts. A
SandboxInterruptis translated into aToolInterrupt, so it flows through the agent exactly like any other interrupted tool call. When you resume the run with an answer, the tool hands it back to the sandbox, which replays the script from its checkpoint and delivers the answer to the call that paused.
You do not manage any of this yourself — resuming the agent run resumes the script. See Resume an interrupted script for the underlying sandbox mechanics, and Interrupts for the shared model.
When to reach for this
Section titled “When to reach for this”Wrap the sandbox as a tool when a task is naturally multi-step over your actions — fetch, filter, combine, act — and you would rather the model express that as one script than as a long chain of individual tool calls. When you want the model to call actions directly, one per turn, register the actions on the run instead and skip the sandbox.