---
title: "Run Vercel AI SDK agents in a Promigence sandbox"
description: "Pass a Promigence sandbox to the Vercel AI SDK as experimental_sandbox, or to bash-tool, so an agent's commands and files live there."
url: "https://www.promigence.ai/docs/guides/vercel-ai-sdk"
site: "Promigence"
---

# Run Vercel AI SDK agents in a Promigence sandbox

Pass a Promigence sandbox to the Vercel AI SDK as experimental_sandbox, or to bash-tool, so an agent's commands and files live there.

The Vercel AI SDK lets a tool reach a sandbox through `experimental_sandbox`, and `bash-tool` takes a
sandbox of its own. The `promigence-ai-sdk-sandbox` package wraps a Promigence sandbox in one object
that both accept. Your tools run commands, read files and write files in the sandbox, and nothing
the model asks for touches your server.

## What you need

- A Promigence API key in `PROMIGENCE_API_KEY`.
- Node.js 20 or newer.
- `ai` 7.0.106 and, if you use it, `bash-tool` 1.3.19. Those are the versions the package was tested
  against, on TypeScript 5.9.

## Install

```bash
npm install promigence-ai-sdk-sandbox @promigence/sdk ai zod bash-tool
export PROMIGENCE_API_KEY=prom_api_...
```

## A minimal example

Create the sandbox with `@promigence/sdk`, then wrap it:

```ts
import { generateText, tool } from "ai";
import { Sandbox } from "@promigence/sdk";
import { promigenceSandbox } from "promigence-ai-sdk-sandbox";
import { z } from "zod";

const sandbox = await Sandbox.create({ timeoutMs: 600_000, spendCap: 1 });
const session = promigenceSandbox(sandbox, { workingDirectory: "/workspace" });

try {
  await generateText({
    model: "anthropic/claude-sonnet-4-5",
    prompt: "run the tests and fix what fails",
    experimental_sandbox: session,
    tools: {
      shell: tool({
        description: "run a shell command",
        inputSchema: z.object({ command: z.string() }),
        execute: async ({ command }, { experimental_sandbox }) => {
          const { exitCode, stdout, stderr } = await experimental_sandbox!.run({ command });
          return `${exitCode}\n${stdout}${stderr}`;
        },
      }),
    },
  });
} finally {
  await session.stop();
}
```

With no snapshot named, `Sandbox.create` starts from the built-in base. To start from your own
environment, pass a snapshot alias (`Sandbox.create("my-env", { ... })`) or an image digest with
`{ image: "registry/name@sha256:...", allowBuild: true }`. A snapshot is a saved, verified
environment, and each sandbox starts as a fresh copy of it. The first use of a new image builds that
snapshot, and `allowBuild: true` is how you agree to that build. Alternatively, build it once ahead
of time with `promigence snapshot create --image <ref> --alias my-env`. Either way, the build is
billed per second while it runs, whether it verifies or not, up to $0.17 at the default small size
([quickstart](https://www.promigence.ai/docs/quickstart)), and sandboxes started from the snapshot pay no further build
charge.

The same session works as `bash-tool`'s sandbox:

```ts
import { createBashTool } from "bash-tool";

const { tools } = await createBashTool({ sandbox: session, destination: "/workspace" });
```

`promigenceSandbox` accepts `description` (what the agent is told about its environment),
`workingDirectory` (default `/workspace`, created before the first command that uses it), `env`,
`timeoutMs`, `maxOutputBytes` and `user`. Beyond `run`, the session has `spawn` for a background
command with streamed `stdout` and `stderr`, and read and write methods for text and binary files.
Relative paths resolve under `workingDirectory`.

## One sandbox per conversation

The package does not create or end sandboxes beyond `stop()`. It wraps the handle you give it. A
sensible default is one sandbox per agent run or per user conversation, created at the start and
stopped at the end, so no two runs share files.

## Setting a spend cap

Pass `spendCap` (US dollars) to `Sandbox.create`, or set `PROMIGENCE_SPEND_CAP`. Without either, your
organization's default applies. It is the most the sandbox may spend. `timeoutMs` on
`Sandbox.create` bounds how long the sandbox lives.

## Cleaning up

`await session.stop()` ends the sandbox. Put it in `finally`, as above. `promigence sandbox list`
shows anything left running.

A command that exits non-zero is the agent's result and comes back in `exitCode`. A failure of the
sandbox itself is thrown as `PromigenceEnvironmentError` with a `failureClass`, because an exit code
of 1 would wrongly say the agent's command failed. `failureClassOf(error)` classifies any error the
Promigence SDK throws.

## What is not supported yet

- Sandbox lifecycle beyond `stop()`. Create the sandbox with `@promigence/sdk`; copies and parking
  stay there too.
- `SandboxProcess.pid`. The command id is on `process.execId` instead.
- Byte-by-byte output. `spawn`'s streams deliver chunks as they are read.
- Per-file errors from `writeFiles`. It stops at the first failure.
- Directory upload, port previews and interactive terminals through this wrapper. Use the
  `@promigence/sdk` handle for those.

## Next

Read [the agent briefing](https://www.promigence.ai/agents) for what a sandbox gives an agent, or start with
[the quickstart](https://www.promigence.ai/docs/quickstart).

