Skip to main content
Station provides sandboxed environments for agents to execute code safely. Two modes are available depending on your use case.

Sandbox Modes

Compute Mode

Ephemeral containers for quick calculations and data processing. Each call runs in a fresh container.

Code Mode

Persistent Linux sandbox for iterative development. Full shell access, package installation, and file persistence across calls.
FeatureCompute ModeCode Mode
LifecycleFresh container per callPersistent per workflow
Use caseCalculations, transformationsDevelopment, compilation
Toolsandbox_runsandbox_open, sandbox_exec, etc.
FilesVia files param onlyFull filesystem access

Enabling Sandbox

Via MCP Tools

"Create a data-processor agent with Python sandbox enabled"
The create_agent and update_agent tools accept sandbox configuration:
{
  "name": "data-processor",
  "description": "Process data with Python",
  "prompt": "You process data. Use sandbox_run to execute Python code.",
  "environment_id": "1",
  "sandbox": "{\"runtime\": \"python\", \"pip_packages\": [\"pandas\"]}"
}
Enable Code Mode:
{
  "agent_id": "42",
  "sandbox": "{\"mode\": \"code\", \"session\": \"workflow\"}"
}
Disable sandbox:
{
  "agent_id": "42",
  "sandbox": "{}"
}

Via Frontmatter (.prompt file)

Add sandbox: to your agent’s frontmatter:
---
model: openai/gpt-4o
metadata:
  name: "Data Processor"
sandbox: python
---

Process data using Python. Use sandbox_run to execute code.
The agent receives a sandbox_run tool for executing code:
{
  "code": "print(sum(range(1, 101)))",
  "runtime": "python"
}

Code Mode

Enable persistent Linux sandbox with mode: code:
---
model: openai/gpt-4o
metadata:
  name: "Developer Agent"
sandbox:
  mode: code
  session: workflow
---

You have access to a full Linux sandbox. Install packages,
compile code, and run any shell commands.

Code Mode Tools

When mode: code is enabled, agents receive these tools:
ToolDescription
sandbox_openGet or create a sandbox session
sandbox_execExecute any shell command
sandbox_fs_writeWrite files to the sandbox
sandbox_fs_readRead files from the sandbox
sandbox_fs_listList directory contents
sandbox_fs_deleteDelete files or directories
sandbox_closeClose the session (optional)

Example: Full Development Workflow

# Open sandbox (ubuntu:22.04)
sandbox_open({})

# Install dependencies
sandbox_exec({"command": "apt-get update && apt-get install -y build-essential curl"})

# Write source code
sandbox_fs_write({
  "path": "main.c",
  "content": "#include <stdio.h>\nint main() { printf(\"Hello!\\n\"); return 0; }"
})

# Compile and run
sandbox_exec({"command": "gcc -o main main.c && ./main"})
# Output: Hello!

# Install Python packages
sandbox_exec({"command": "pip install pandas numpy"})

# Run Python script
sandbox_fs_write({
  "path": "analyze.py", 
  "content": "import pandas as pd\nprint(pd.__version__)"
})
sandbox_exec({"command": "python analyze.py"})

Configuration Reference

Compute Mode

sandbox:
  runtime: python           # python, node, or bash
  image: "python:3.11-slim" # optional: custom image
  timeout_seconds: 120      # execution timeout
  allow_network: false      # network access
  pip_packages:             # pre-install packages
    - pandas
    - requests

Code Mode

sandbox:
  mode: code
  session: workflow         # workflow or agent
  runtime: linux            # linux, python, node, or custom image
  timeout_seconds: 300      # per-command timeout
  limits:
    max_file_size_bytes: 10485760  # 10MB
    max_files: 100

Runtime Images

RuntimeDocker Image
linux (default)ubuntu:22.04
pythonpython:3.11-slim
nodenode:20-slim
CustomAny Docker image

Session Scoping (Code Mode)

Sessions can be scoped to share containers:
Container shared across all agents in a workflow:
sandbox:
  mode: code
  session: workflow
Workflow: build-and-test
├── Agent 1: writes code → sandbox_fs_write
├── Agent 2: runs tests → sandbox_exec (files still there!)
└── Complete → container destroyed

Deployment Requirements

Requires Dagger (auto-managed):
# Docker socket access
docker run -v /var/run/docker.sock:/var/run/docker.sock ...

Fly Machines Backend

When running Station on Fly.io, use the Fly Machines backend instead of Docker (Fly’s Firecracker VMs don’t support Docker-in-Docker).

Configuration

sandbox:
  enabled: true
  backend: fly_machines
  fly_machines:
    org_slug: your-org        # Or use FLY_ORG env var
    region: ord               # Fly.io region
    image: python:3.11-slim   # Container image
    memory_mb: 256
    cpu_kind: shared
    cpus: 1
Required environment variables:
  • FLY_API_TOKEN - Your Fly.io API token
  • FLY_ORG - Organization slug (if not in config)

Custom Images

Create a Dockerfile with your required tools:
FROM python:3.11-slim

RUN apt-get update && apt-get install -y git curl unzip && rm -rf /var/lib/apt/lists/*

# Install Terraform
RUN curl -fsSL https://releases.hashicorp.com/terraform/1.7.0/terraform_1.7.0_linux_amd64.zip -o /tmp/tf.zip \
    && unzip /tmp/tf.zip -d /usr/local/bin/ && rm /tmp/tf.zip

# Install kubectl
RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \
    && install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl && rm kubectl

WORKDIR /workspace
Build and push:
docker build -t yourusername/station-sandbox:latest .
docker push yourusername/station-sandbox:latest
Then configure:
sandbox:
  fly_machines:
    image: yourusername/station-sandbox:latest

Private Registry Authentication

For private images, add registry credentials:
sandbox:
  fly_machines:
    image: ghcr.io/your-org/station-sandbox:latest
    registry_auth:
      username: your-username
      password: ${GITHUB_TOKEN}
      server_address: ghcr.io
registry_auth:
  username: your-github-username
  password: ${GITHUB_TOKEN}  # PAT with read:packages
  server_address: ghcr.io

Environment Variable Injection

Inject secrets and configuration into sandbox containers using the STN_CODE_ prefix. This is essential for CLI tools like Terraform, AWS CLI, kubectl, etc.

How It Works

Environment variables on the Station host with the STN_CODE_ prefix are automatically injected into sandbox containers with the prefix stripped:
# Set on Station host (or in Fly secrets):
export STN_CODE_AWS_ACCESS_KEY_ID="AKIAXXXXXXXXXXXX"
export STN_CODE_AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG..."
export STN_CODE_AWS_DEFAULT_REGION="us-east-1"
export STN_CODE_TERRAFORM_TOKEN="tf-xxxxxxxxxxxxxxxx"

# Inside sandbox container, agents see:
AWS_ACCESS_KEY_ID="AKIAXXXXXXXXXXXX"
AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG..."
AWS_DEFAULT_REGION="us-east-1"
TERRAFORM_TOKEN="tf-xxxxxxxxxxxxxxxx"

Setting Variables

# Set secrets on your Fly app
fly secrets set \
  STN_CODE_AWS_ACCESS_KEY_ID="AKIAXXXX" \
  STN_CODE_AWS_SECRET_ACCESS_KEY="secret" \
  STN_CODE_TERRAFORM_TOKEN="tf-xxx" \
  --app your-station-app

Common Use Cases

ToolEnvironment Variables
AWS CLISTN_CODE_AWS_ACCESS_KEY_ID, STN_CODE_AWS_SECRET_ACCESS_KEY, STN_CODE_AWS_DEFAULT_REGION
TerraformSTN_CODE_TF_TOKEN_app_terraform_io, STN_CODE_AWS_*
kubectlSTN_CODE_KUBECONFIG (base64 encoded)
GitHub CLISTN_CODE_GITHUB_TOKEN
Google CloudSTN_CODE_GOOGLE_APPLICATION_CREDENTIALS

Example: Terraform Agent

---
model: openai/gpt-4o
metadata:
  name: "Terraform Agent"
sandbox:
  mode: code
  session: workflow
  image: hashicorp/terraform:latest
---

You can run Terraform commands. The AWS credentials and Terraform token 
are pre-configured in the environment.

Example workflow:
1. sandbox_open({})
2. sandbox_fs_write({"path": "main.tf", "content": "..."})
3. sandbox_exec({"command": "terraform init"})
4. sandbox_exec({"command": "terraform plan"})
Never hardcode secrets in agent prompts or workflow definitions. Always use STN_CODE_* environment variables.

Session Persistence (Fly Machines)

When running on Fly.io, sandbox sessions are persisted to NATS KV store. This enables sessions to survive Station restarts.

How It Works

Station Start


┌─────────────────────────────────────┐
│  NATS KV Store (sessions bucket)    │
│  ┌─────────────────────────────┐    │
│  │ fly_abc123 → Machine ID     │    │
│  │ fly_def456 → Machine ID     │    │
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘


SessionManager loads from KV


Fly Machines still running ✓

Benefits

ScenarioWithout PersistenceWith NATS KV
Station restartSessions lost, orphan machinesSessions recovered
Workflow continuesFails (session not found)Works seamlessly
Multi-step workflowsMust complete before restartCan span restarts

Configuration

Session persistence is automatic when using Fly Machines backend. No additional configuration required.
sandbox:
  enabled: true
  backend: fly_machines
  # Session persistence enabled automatically

Session Recovery

On Station startup, existing Fly Machines are automatically recovered:
[INFO] Session store: Initialized with NATS KV (bucket: sandbox_sessions)
[INFO] Recovered 3 sandbox sessions from NATS KV
Sessions are keyed by workflow run ID for workflow-scoped sessions, ensuring the same sandbox is reused across workflow steps even after Station restarts.

Security

Both modes provide isolation:
  • Unprivileged containers (no --privileged)
  • No Docker socket access from within sandbox
  • Network disabled by default
  • Resource limits enforced
  • Timeout protection
Enable allow_network: true only when necessary. Network access allows the sandbox to reach external services.

File Staging

Stage files between your local system and sandbox containers via NATS Object Store.
File staging solves the challenge of getting large files into sandboxes without passing content through LLM context (which is slow, expensive, and size-limited).

How It Works

Local Files  ────▶  NATS Object Store  ────▶  Sandbox Container
             upload                    stage_file
                         ◀────────────────────
                         download     publish_file

File Staging Tools

When Code Mode is enabled, agents get two additional tools:
ToolDescription
sandbox_stage_fileFetch file from store → write to sandbox
sandbox_publish_fileRead from sandbox → upload to store

Example Workflow

# 1. Upload input file
$ stn files upload data.csv
Uploaded: files/f_01JGXYZ123ABC (2.4 MB)

# 2. Run workflow with file reference
$ stn workflow run csv-pipeline --input '{"input_file": "files/f_01JGXYZ123ABC"}'

# 3. Agent stages file into sandbox
sandbox_stage_file({
  "sandbox_id": "ses_abc123",
  "file_key": "files/f_01JGXYZ123ABC",
  "destination": "input/data.csv"
})

# 4. Agent processes file
sandbox_exec({"cmd": ["python", "transform.py", "input/data.csv", "output/result.csv"]})

# 5. Agent publishes output
sandbox_publish_file({
  "sandbox_id": "ses_abc123",
  "source": "output/result.csv"
})
# Returns: {"file_key": "files/f_01JGDEF456XYZ"}

# 6. Download result
$ stn files download f_01JGDEF456XYZ -o result.csv

Tool Reference

Fetch a file from NATS Object Store and write it to the sandbox.
ParameterTypeRequiredDescription
sandbox_idstringYesSession ID from sandbox_open
file_keystringYesFile key (e.g., files/f_abc123)
destinationstringYesPath relative to /workspace
{
  "sandbox_id": "ses_abc123",
  "file_key": "files/f_01JGXYZ123ABC",
  "destination": "input/data.csv"
}

File Key Conventions

PatternDescriptionLifecycle
files/{file_id}User uploadsPermanent until deleted
runs/{run_id}/output/*Workflow outputsAuto-cleanup after TTL
sessions/{session_id}/*Session artifactsCleanup with session

CLI File Management

See the full stn files CLI reference for upload, download, list, and delete commands.

Next Steps