Workspace & Sandbox
Sandbox (compute) and workspace (persistent files) are account-scoped resources. You define each once in code with defineSandbox and defineWorkspace, then reference them from any agent.
- A sandbox is the compute backend plus a collection of bash and filesystem tools
(
bash,read,write,edit,glob,grep) and apermissionMode. - A workspace is the persistent S3-backed filesystem that gets mounted into a sandbox.
Agents that reference the same
workspaceIdread and write the same files.
A sandbox can be attached agent-wide (config.sandbox) or per workspace
(workspaces[].sandbox). A workspace's effective sandbox follows a simple cascade:
workspaces[].sandbox === null → read-only, S3-direct reads (opt out of compute entirely)
workspaces[].sandbox === "sb_…" → that sandbox (override)
workspaces[].sandbox omitted → inherit config.sandbox (read-only via mount if there is none)
This is what lets one agent give different workspaces different sandboxes and
permissionModes, lets two agents that share a workspace access it through their own
sandboxes, and lets a single workspace be read-only. A read-only workspace reads through
a service-managed read-only mount by default (so it sees committed writes immediately);
sandbox: null opts out of that mount and reads straight from S3 (no Lambda, cheapest, but
reads lag mount writes — see Lambda). config.sandbox also powers
stateless bash when there is no workspace at all.
Code-First Configuration
Define sandbox and workspace resources in broods/, then pass them to an agent:
import {
defineAgent,
defineSandbox,
defineWorkspace,
env,
} from "broods";
export const lambdaSandbox = defineSandbox({
name: "default",
config: {
provider: "lambda",
network: { mode: "allow-all" },
permissionMode: "ask",
},
});
export const notes = defineWorkspace({
name: "notes",
config: {
storage: { provider: "s3" },
harness: { enabled: true },
},
});
export const myAgent = defineAgent({
name: "my-agent",
config: {
provider: { openai: { apiKey: env.OPENAI_API_KEY } },
model: { provider: "openai", modelId: "gpt-5.5" },
agent: { system: "You are a helpful assistant." },
sandbox: lambdaSandbox,
workspaces: [
notes, // inherit agent sandbox
{ workspace: notes, sandbox: null }, // read-only, S3-direct
],
},
});
The CLI compiles these into a manifest, resolves references, and syncs them. You can also create records via the raw account API — see the API Reference for POST /accounts/me/sandboxes and POST /accounts/me/workspaces.
Tool surface
Tool availability is decided per workspace, from that workspace's effective sandbox
(workspaces[].sandbox → else config.sandbox → else none). The agent's tool set is the
union across its workspaces:
| Workspace's effective sandbox | Tools for that workspace |
|---|---|
| present (mounted) | read, write, edit, glob, grep, bash (+ MEMORY/TASKS harness) |
| none (read-only, default) | read, glob — via a read-only mount (fresh reads) |
none, sandbox: null | read, glob — straight from S3 (no mount/cold start, lagged) |
Plus the agent-level cases:
| Agent references | Tools exposed |
|---|---|
| sandbox, no workspace | bash only — stateless (each call is a fresh container; nothing persists) |
| neither sandbox nor workspace | none |
For mounted workspaces, every provider should expose the same model-facing filesystem:
bash starts in the selected workspace directory and the file tools take paths relative to
that directory. Ordinary prompts should use relative paths; provider mount paths are
implementation details for logs and debugging.
When workspaces have different sandboxes, the model picks one with the
workspaceargument; each call routes to that workspace's sandbox and inherits itspermissionMode. Every file tool lists all workspaces (so an omittedworkspacealways resolves to the configured default, never a silent substitute). Selecting a read-only workspace forwrite/edit/grepreturns a clean "workspace is read-only" error, andbashreports "no sandbox available for this command" — in both cases with no approval prompt, because a workspace with no sandbox has nopermissionModeto ask against.
permissionMode
permissionMode lives on the sandbox and replaces the old needsApproval boolean:
| Mode | read/glob/grep | write/edit | bash |
|---|---|---|---|
ask | auto | ask | ask |
edit | auto | auto | ask |
bypass | auto | auto | auto |
Runtime model
The workspace namespace is derived from accountId:workspaceId (not the
conversation), which is what makes a workspace shared across agents and conversations.
Set workspace.harness.enabled: false to suppress the MEMORY/TASKS guidance while still
loading an existing MEMORY.md.