Skip to content

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.

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.

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.

createSandboxTool handles the plumbing that makes multi-turn scripting coherent:

  • data across turns. The tool stores the sandbox’s data object 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 new data version 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 SandboxInterrupt is translated into a ToolInterrupt, 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.

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.