Sandcastle
Build once. Run anywhere. Sandcastle is an open-source, production-ready orchestrator for AI agents. Describe a workflow in plain English and it builds it (or write the YAML yourself); run it on any model — Claude, GPT, Mistral, or a local model on your own box — and move between them with one line; deploy it your way — cloud, your own server, fully air-gapped, or EU-only; and it gets better over time, on its own. Local models run at $0/run with hard data-residency enforcement and a tamper-evident audit trail; the cloud is there too, with 7 providers and auto-failover, 25 step types, verified templates, and a full dashboard. Sovereign by default. European-built.
v0.40.0 — "Build Once, Run Anywhere" Describe what you want in plain English and Sandcastle builds the workflow. Run it on any model. Deploy it your way. And it gets better over time, on its own.
This release rebuilds the whole experience around that loop — and adds the machinery behind it:
- The Omnibox. The dashboard opens with one input: "What should your agent do?" Describe a task, Sandcastle generates the workflow, you run it (also one keystroke away with
⌘K).- The Black Box. A tamper-evident, signed hash-chain over every recorded run, a
black_boxcompliance mode, andsandcastle audit verify— a replayable, verifiable audit trail for the EU AI Act era.- The Architect. Generate → run → judge → refine until the workflow actually works, then ship it with a recorded cassette as a Proven template.
- Self-Healing Workflows, the Model Time Machine, and Sandcastle Mesh. Failed runs diagnose and patch themselves behind an approval gate; replay your real workload against a new model for a quality/cost/latency delta; route steps by capability (
requires: [gpu, browser]) across your own machines.- A self-explanatory builder, Night Shift, Mission Control, and verified template bundles (
.sctpl) with replayable proofs.- A new "Sand & Ink" look, a three-verb navigation, and a determinism fix so LLM steps no longer garble across providers.
PyPI:
pip install -U sandcastle-ai. Full notes: CHANGELOG.md · ReleasesPrevious: v0.33.0 — "Your Box, Your Brains" (June 4, 2026): auto-detected Spark Mode, first-class local
nim/*inference at $0/run, and Overnight Self-Tune (LoRA) on your own data.
Table of Contents
- Why Sandcastle?
- Start Local. Scale When Ready.
- ⚡ Spark Mode — local AI on NVIDIA DGX Spark
- 🕸️ Sandcastle Mesh — one workflow, your whole fleet
- Quickstart
- MCP Integration
- Features
- Pluggable Sandbox Backends
- Browser Step - LightPanda & Browserbase
- Multi-Provider Model Routing
- 64 Built-in Integrations
- Workflow Engine
- 25 Step Types
- Managed Agents
- Human Approval Gates
- Self-Optimizing Workflows (AutoPilot)
- Self-Healing Workflows
- Hierarchical Workflows (Workflow-as-Step)
- Policy Engine
- Privacy Router (PII Redaction)
- Cost-Latency Optimizer
- Cost Estimation API
- EU AI Act Compliance
- Tamper-Evident Audit Trail
- Black Box Flight Recorder
- OpenTelemetry
- Agent Memory
- Evaluations
- Directory Input & CSV Export
- Community Hub
- Verified Templates (.sctpl)
- Real-time Event Stream
- Run Time Machine
- Model Time Machine
- Budget Guardrails
- Security
- A2A & AG-UI Protocols
- Dashboard
- API Reference
- Multi-Tenant Auth
- Webhooks
- Architecture
- Configuration
- Development
- Acknowledgements
- License
Why Sandcastle?
Sovereignty first. Every mainstream agent stack assumes your data goes to someone else's cloud and your costs scale with someone else's token meter. Sandcastle inverts that default:
- Local-first execution. NVIDIA NIM, Ollama, and oMLX are first-class providers at
region=local. Spark Mode auto-detects an NVIDIA DGX Spark and flips to local-first defaults with no flags. Air-gap friendly by design. - $0 runs on your hardware. Inference on your own silicon is metered at
$0.00/run. No egress, no per-token bill. - Hard data-residency enforcement. Set
data_residency=local(oreu) and the engine raises before any step could route data off-box - enforcement in the executor, not a policy PDF. - Replayable, auditable runs. SHA-256 hash-chained audit trail on every run, plus deterministic cassettes: record once, replay offline at $0, identically, with a tamper-evident signature. Audit-ready for the EU AI Act era.
If your workloads live in regulated or sovereignty-sensitive environments - public sector, healthcare, finance - this is the difference between "we have a policy" and "the runtime physically cannot leak."
And it's a complete orchestrator, not just a residency gate. AI agent frameworks give you building blocks - LLM calls, tool use, maybe a graph. But when you start building real products, the glue code piles up fast:
- "Step A scrapes, step B enriches, step C scores." - You need workflow orchestration.
- "Fan out over 50 leads in parallel, then merge." - You need a DAG engine.
- "Bill the customer per enrichment, track costs per run." - You need usage metering.
- "Alert me if the agent fails, retry with backoff." - You need production error handling.
- "Run this every 6 hours and POST results to Slack." - You need scheduling and webhooks.
- "A human should review this before the agent continues." - You need approval gates.
- "Block the output if it contains PII or leaked secrets." - You need policy enforcement.
- "Pick the cheapest model that still meets quality SLOs." - You need cost-latency optimization.
- "Use Claude for quality, GPT for speed, Gemini for cost." - You need multi-provider routing.
- "Run on E2B cloud, Docker locally, or Cloudflare at the edge." - You need pluggable runtimes.
- "Connect to Slack, Jira, GitHub, Salesforce, SAP..." - You need integrations.
- "Show me what's running, what failed, and what needs attention." - You need a dashboard.
Sandcastle handles all of that. Define workflows in YAML, pick your sandbox backend, choose your models, and ship to production.
Start Local. Scale When Ready.
No Docker, no database server, no Redis. Install, run, done.
pip install sandcastle-ai
sandcastle init # asks for API keys, picks sandbox backend, writes .env
sandcastle serve # starts API + dashboard on one port
You'll need API keys for your chosen setup:
- ANTHROPIC_API_KEY - get one at console.anthropic.com (for Claude models)
- E2B_API_KEY - get one at e2b.dev (for E2B cloud sandboxes - free tier available)
Or use the docker backend (needs Docker installed) or local backend (dev only, no sandbox isolation) and skip the E2B key.
Dashboard at http://localhost:8080, API at http://localhost:8080/api, 248 workflow templates included, 64 integrations ready to connect.
Sandcastle auto-detects your environment. No DATABASE_URL? It uses SQLite. No REDIS_URL? Jobs run in-process. No S3 credentials? Files go to disk. Same code, same API, same dashboard - you just add connection strings when you're ready to scale.
Prototype Staging Production
--------- ------- ----------
SQLite PostgreSQL PostgreSQL
In-process queue --> Redis + arq --> Redis + arq
Local filesystem Local filesystem S3 / MinIO
Single process Single process API + Worker + Scheduler
| Local Mode | Production Mode | |
|---|---|---|
| Database | SQLite (auto-created in ./data/) |
PostgreSQL 16 |
| Job Queue | In-process (asyncio.create_task) |
Redis 7 + arq workers |
| Storage | Filesystem (./data/) |
S3 / MinIO |
| Scheduler | In-memory APScheduler | In-memory APScheduler |
| Setup time | 30 seconds | 5 minutes |
| Config needed | Just API keys | API keys + connection strings |
| Best for | Prototyping, solo devs, demos | Teams, production, multi-tenant |
Ready to scale?
When local mode isn't enough anymore, upgrade one piece at a time. Each step is independent - do only what you need.
Step 1 - PostgreSQL (concurrent users, data durability)
# Install and start PostgreSQL (macOS example)
brew install postgresql@16
brew services start postgresql@16
# Create a database
createdb sandcastle
# Add to .env
echo 'DATABASE_URL=postgresql+asyncpg://localhost/sandcastle' >> .env
# Run migrations
pip install sandcastle-ai # if not installed yet
alembic upgrade head
# Restart
sandcastle serve
Your SQLite data stays in ./data/. Sandcastle starts fresh with PostgreSQL - existing local runs are not migrated.
Step 2 - Redis (background workers, parallel runs)
# Install and start Redis (macOS example)
brew install redis
brew services start redis
# Add to .env
echo 'REDIS_URL=redis://localhost:6379' >> .env
# Restart API + start a worker in a second terminal
sandcastle serve
sandcastle worker
With Redis, workflows run in background workers instead of in-process. You can run multiple workers for parallel execution.
Step 3 - S3 / MinIO (artifact storage)
# Add to .env
echo 'STORAGE_BACKEND=s3' >> .env
echo 'STORAGE_BUCKET=sandcastle-artifacts' >> .env
echo 'AWS_ACCESS_KEY_ID=...' >> .env
echo 'AWS_SECRET_ACCESS_KEY=...' >> .env
# For MinIO, also set: STORAGE_ENDPOINT=http://localhost:9000
# Restart
sandcastle serve
Or skip all that and use Docker:
docker compose up -d # PostgreSQL + Redis + API + Worker, all configured
⚡ Spark Mode — local AI on NVIDIA DGX Spark
Sandcastle's environment auto-detection goes one step further on an NVIDIA DGX Spark (or any Grace-Blackwell host with 110 GB+ unified memory): it turns on Spark Mode automatically — no flags, no config.
- $0 per run. Point Sandcastle at a local OpenAI-compatible model server (Ollama, vLLM, or
NVIDIA NIM) on the Spark's GPU. Inference is tracked at
$0.00, regionlocal— you pay for electricity, not tokens. - Your data never leaves the box. Fully local and air-gappable. Pair Spark Mode with the
dockersandbox backend anddata_residency=localfor an offline agent appliance — the engine raises at runtime if a step targets a non-local model. - Auto-tuned throughput. Concurrency lifts automatically (5 → 40 workers) to use the Spark's
headroom for wide
parallel_overfan-outs. - One-glance status.
sandcastle serveprints a Spark Mode banner, the dashboard shows a⚡ Spark Modebadge, andGET /api/runtimeexposesspark_mode.
# On the Spark: serve a local model (example: Ollama), then point Sandcastle at it
OLLAMA_HOST=http://localhost:11434 sandcastle serve # Spark Mode auto-detects and engages
Override detection anytime with SANDCASTLE_SPARK_MODE=on|off. On any non-Spark machine nothing
changes — detection is fail-closed.
Spark Mode in Docker. The plain docker-compose.yml does not pass the GPU through, so Spark
Mode stays off in containers. With nvidia-container-toolkit installed on the Spark host, run:
docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d
Overnight Self-Tune goes one step further: with pip install 'sandcastle-ai[training]',
TRAINER_BACKEND=gpu, and EVOLUTION_AUTO_FINETUNE=true, the evolution loop fine-tunes a real
LoRA adapter (transformers + peft) on the workflow's own eval data and routes the workflow to it —
served by your local vLLM/Ollama/NIM at $0/run. See
docs/overnight-self-tune-spark.md.
Spark Mode auto-configures Sandcastle for NVIDIA DGX Spark and local NVIDIA GPU inference. It is not an official NVIDIA certification or endorsement. "NVIDIA", "DGX", and "DGX Spark" are trademarks of NVIDIA Corporation, used here only to describe compatibility.
🕸️ Sandcastle Mesh — one workflow, your whole fleet
Your machines have different superpowers. Sandcastle Mesh turns them into one orchestration mesh: a DGX Spark runs the GPU steps, a Mac mini runs the browser steps, and any spare box runs the code steps — all inside a single workflow.
┌─────────────┐ requires: [gpu] ┌─────────────┐
│ Coordinator │ ────────────────────────▶ │ DGX Spark │ gpu · spark · code
│ (any box) │ requires: [browser] ├─────────────┤
│ runs the │ ────────────────────────▶ │ Mac mini │ browser · code
│ workflow │ (no requires) ├─────────────┤
│ │ ──── runs locally ────── │ any server │ docker · code
└─────────────┘ └─────────────┘
Joining a machine is one command — it registers itself, auto-detects its capabilities
(DGX Spark → gpu+spark, Playwright → browser, Docker socket → docker, always code)
and heartbeats every 15 seconds:
# On the coordinator
MESH_ENABLED=true MESH_TOKEN=<shared-secret> sandcastle serve
# On each machine you want in the mesh
sandcastle node join http://coordinator:8080 --token <shared-secret>
Steps declare what they need; the dispatcher does the rest. Local execution is always preferred
when the local machine qualifies, and steps without requires behave exactly as before:
steps:
- id: scrape
type: browser
requires: [browser] # routed to the Mac mini
browser_config:
start_url: "https://example.com"
- id: analyze
type: llm
requires: [gpu] # routed to the Spark
prompt: "Analyze {steps.scrape.output}"
- id: summarize
prompt: "Summarize {steps.analyze.output}" # no requires - runs locally
The dashboard's Fleet page shows every node, its capability chips, heartbeat age and a live
status dot. Nodes are declared dead after 3 missed heartbeats, and unsatisfiable requires fail
fast with the full picture: which nodes exist, what they can do, and the exact join command to fix
it. All mesh traffic is authenticated with a shared MESH_TOKEN (constant-time comparison) — a
node never executes a step for an unauthenticated caller.
| Setting | Default | Meaning |
|---|---|---|
MESH_ENABLED |
false |
Master switch for the mesh planes |
MESH_TOKEN |
— | Shared secret for register/heartbeat/execute |
MESH_HEARTBEAT_SECONDS |
15 |
Heartbeat interval (dead after 3 missed) |
Quickstart
Production Mode - Docker (recommended)
One command. PostgreSQL, Redis, API server, and background worker - all configured.
git clone https://github.com/gizmax/Sandcastle.git
cd Sandcastle
# Add your API keys
cat > .env << 'EOF'
ANTHROPIC_API_KEY=sk-ant-...
E2B_API_KEY=e2b_...
SANDBOX_BACKEND=e2b
WEBHOOK_SECRET=your-signing-secret
EOF
docker compose up -d
That's it. Sandcastle is running at http://localhost:8080 with PostgreSQL 16, Redis 7, auto-migrations, and an arq background worker.
docker compose ps # check status
docker compose logs -f # tail logs
docker compose down # stop everything
Production Mode - Manual
If you prefer running without Docker:
git clone https://github.com/gizmax/Sandcastle.git
cd Sandcastle
cp .env.example .env # configure all connection strings
uv sync
# Start infrastructure (your own PostgreSQL + Redis)
# Set DATABASE_URL and REDIS_URL in .env
# Run database migrations
uv run alembic upgrade head
# Start the API server (serves API + dashboard on one port)
uv run python -m sandcastle serve
# Start the async worker (separate terminal)
uv run python -m sandcastle worker
Your First Workflow
# Run a workflow asynchronously
curl -X POST http://localhost:8080/api/workflows/run \
-H "Content-Type: application/json" \
-d '{
"workflow": "lead-enrichment",
"input": {
"target_url": "https://example.com",
"max_depth": 3
},
"callback_url": "https://your-app.com/api/done"
}'
# Response: { "data": { "run_id": "a1b2c3d4-...", "status": "queued" } }
Or run synchronously and wait for the result:
curl -X POST http://localhost:8080/api/workflows/run/sync \
-H "Content-Type: application/json" \
-d '{
"workflow": "lead-enrichment",
"input": { "target_url": "https://example.com" }
}'
Python SDK
Install from PyPI and use Sandcastle programmatically from any Python app:
pip install sandcastle-ai
from sandcastle import SandcastleClient
client = SandcastleClient(base_url="http://localhost:8080", api_key="sc_...")
# Run a workflow and wait for completion
run = client.run("lead-enrichment",
input={"target_url": "https://example.com"},
wait=True,
)
print(run.status) # "completed"
print(run.total_cost_usd) # 0.12
print(run.outputs) # {"lead_score": 87, "tier": "A", ...}
# List recent runs
for r in client.list_runs(status="completed", limit=5).items:
print(f"{r.workflow_name}: {r.status}")
# Stream live events from a running workflow
for event in client.stream(run.run_id):
print(event)
# Replay a failed step with a different model
new_run = client.fork(run.run_id, from_step="score", changes={"model": "opus"})
Async variant available for asyncio apps:
from sandcastle import AsyncSandcastleClient
async with AsyncSandcastleClient() as client:
run = await client.run("lead-enrichment", input={...}, wait=True)
CLI
The sandcastle command gives you full control from the terminal. Its help is
organized to mirror the dashboard's Build / Run / Improve / Operate mental
model — run sandcastle help for the grouped command list, or just
sandcastle for a short getting-started guide:
BUILD build / new / generate, templates, hub, publish-mcp, publish-skills, describe, lint, owners
RUN run, status, logs, ls, runs, cancel, replay, fork, schedule, approve, reject
IMPROVE eval, autopilot
OPERATE serve, worker, health, doctor, mcp, share, dlq, violations, tools, db, update, rollback
SETUP init, keys, providers
The fastest way to start mirrors the dashboard omnibox — describe what your agent should do, then run it:
# Describe a workflow in plain language (build/new are aliases of `generate`)
sandcastle build "enrich inbound leads and post a summary to Slack"
# Run it
sandcastle run lead-enrichment -i target_url=https://example.com
Everything below is still available as before (these aliases only ADD to the flat command list — nothing was renamed or removed):
# Interactive setup wizard (API keys, .env, workflows/)
sandcastle init
# Start the server (API + dashboard on one port)
sandcastle serve
# Generate a workflow (the classic form; `build` / `new` are friendly aliases)
sandcastle generate -d "enrich inbound leads" -o workflows/leads.yaml
# Run a workflow
sandcastle run lead-enrichment -i target_url=https://example.com
# Run and wait for result
sandcastle run lead-enrichment -i target_url=https://example.com --wait
# Check run status
sandcastle status <run-id>
# Stream live logs
sandcastle logs <run-id> --follow
# List runs, workflows, schedules
sandcastle ls runs --status completed --limit 10
sandcastle ls workflows
sandcastle ls schedules
# Manage schedules
sandcastle schedule create lead-enrichment "0 9 * * *" -i target_url=https://example.com
sandcastle schedule delete <schedule-id>
# Community Hub - browse and install templates
sandcastle hub search "lead scoring"
sandcastle hub install competitive-radar
sandcastle hub collections
sandcastle hub install-collection marketing-pro
# Cancel a running workflow
sandcastle cancel <run-id>
# Health check
sandcastle doctor
Connection defaults to http://localhost:8080. Override with --url or SANDCASTLE_URL env var. Auth via --api-key or SANDCASTLE_API_KEY.
MCP Integration
Sandcastle ships with a built-in MCP (Model Context Protocol) server. This lets Claude Desktop, Cursor, Windsurf, and any MCP-compatible client interact with Sandcastle directly from the chat interface - run workflows, check status, manage schedules, browse results.
flowchart LR
Client["Claude Desktop\nCursor / Windsurf"]
MCP["sandcastle mcp\n(MCP server)"]
API["localhost:8080\n(sandcastle serve)"]
Client -->|stdio| MCP -->|HTTP| API
Install the MCP extra:
pip install sandcastle-ai[mcp]
Available MCP Tools
| Tool | Description |
|---|---|
run_workflow |
Run a saved workflow by name with optional input data and wait mode |
run_workflow_yaml |
Run a workflow from inline YAML definition |
get_run_status |
Get detailed run status including all step results |
cancel_run |
Cancel a queued or running workflow |
list_runs |
List runs with optional status and workflow filters |
save_workflow |
Save a workflow YAML definition to the server |
create_schedule |
Create a cron schedule for a workflow |
delete_schedule |
Delete a workflow schedule |
Available MCP Resources
| URI | Description |
|---|---|
sandcastle://workflows |
Read-only list of all available workflows |
sandcastle://schedules |
Read-only list of all active schedules |
sandcastle://health |
Server health status (sandbox backend, DB, Redis) |
Client Configuration
Claude Desktop - add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"sandcastle": {
"command": "sandcastle",
"args": ["mcp"],
"env": {
"SANDCASTLE_URL": "http://localhost:8080",
"SANDCASTLE_API_KEY": "sc_..."
}
}
}
}
Cursor - add to .cursor/mcp.json in your project root:
{
"mcpServers": {
"sandcastle": {
"command": "sandcastle",
"args": ["mcp", "--url", "http://localhost:8080"]
}
}
}
Windsurf - add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"sandcastle": {
"command": "sandcastle",
"args": ["mcp"]
}
}
}
The MCP server uses stdio transport (spawned as a child process by the client). It requires a running sandcastle serve instance to connect to. Connection is configured via --url / --api-key CLI args or SANDCASTLE_URL / SANDCASTLE_API_KEY env vars.
What You Can Do from Chat
Once connected, ask your AI assistant to:
- "Run the lead-enrichment workflow for https://example.com"
- "What's the status of my last run?"
- "List all failed runs from today"
- "Create a schedule to run data-sync every day at 9am"
- "Cancel run abc-123"
- "Save this workflow YAML to the server"
- "Show me all available workflows"
- "Check if Sandcastle is healthy"
Features
| Capability | |
|---|---|
| Pluggable sandbox backends (E2B, Docker, Local, Cloudflare) | Yes |
| Multi-provider model routing (Claude, OpenAI, MiniMax, Google/Gemini, Mistral, Ollama, oMLX) | Yes |
| ⚡ Spark Mode — auto-detects NVIDIA DGX Spark, local-inference defaults, $0/run, air-gappable | Yes |
| 64 built-in integrations across 11 categories | Yes |
| 25 step types (standard, llm, http, code, race, sensor, gate, parse, managed-agent...) | Yes |
| Zero-config local mode | Yes |
| DAG workflow orchestration | Yes |
| Parallel step execution | Yes |
| Run Time Machine (replay/fork) | Yes |
| Budget guardrails | Yes |
| Run cancellation | Yes |
| Idempotent run requests | Yes |
| Persistent storage (S3/MinIO) | Yes |
| Webhook callbacks (HMAC-signed) | Yes |
| Scheduled / cron agents | Yes |
| Retry logic with exponential backoff | Yes |
| Dead letter queue with full replay | Yes |
| Per-run cost tracking | Yes |
| SSE live streaming | Yes |
| Multi-tenant API keys | Yes |
| Python SDK + async client | Yes |
| CLI tool | Yes |
| MCP server (Claude Desktop, Cursor, Windsurf) | Yes |
| Docker one-command deploy | Yes |
| Dashboard with real-time monitoring | Yes |
| 248 built-in workflow templates | Yes |
| 8 community-submitted templates (Community Hub) | Yes |
| Visual workflow builder | Yes |
| Directory input (file processing) | Yes |
| CSV export per step | Yes |
| Human approval gates | Yes |
| Self-optimizing workflows (AutoPilot) | Yes |
| Hierarchical workflows (workflow-as-step) | Yes |
| Policy engine (PII redaction, secret guard) | Yes |
| Privacy router (PII redaction, 7 patterns) | Yes |
Pre-run cost estimation (POST /runs/estimate) |
Yes |
| Cost-latency optimizer (SLO-based routing) | Yes |
| EU AI Act compliance (risk classification, transparency reports, Annex IV) | Yes |
| Tamper-evident audit trail (SHA-256 hash chain) | Yes |
| OpenTelemetry instrumentation (workflow + step spans) | Yes |
| Browser modes (LightPanda headless, Browserbase cloud) | Yes |
| Concurrency control (rate limiter, semaphores) | Yes |
| Agent memory (semantic search, decay, conflict detection) | Yes |
| Evaluations (test suites, assertions, pass rate tracking) | Yes |
| Credential encryption (Fernet AES-128-CBC) | Yes |
| API key rotation + IP allowlisting | Yes |
| Security headers + CSP | Yes |
| Distributed rate limiting (in-memory + Redis) | Yes |
| A2A protocol (Google Agent-to-Agent) | Yes |
| AG-UI protocol (CopilotKit SSE streaming) | Yes |
| Guided onboarding wizard | Yes |
| Global search (runs, workflows, integrations) | Yes |
| Health insights (system health score + per-page banners) | Yes |
| License key system (community / pro / enterprise tiers) | Yes |
Pluggable Sandbox Backends
Sandcastle uses the Sandshore runtime with pluggable backends for agent execution. Each step runs inside an isolated sandbox - choose the backend that fits your needs:
| Backend | Description | Best For |
|---|---|---|
| e2b (default) | Cloud sandboxes via E2B SDK | Production, zero-infra setup |
| docker | Local Docker containers with seccomp + CapDrop ALL | Self-hosted, air-gapped environments |
| local | Direct subprocess on the host (no isolation) | Development and testing only |
| cloudflare | Edge sandboxes via Cloudflare Workers | Low-latency, globally distributed |
# Set in .env or via sandcastle init
SANDBOX_BACKEND=e2b # default
SANDBOX_BACKEND=docker # requires Docker + pip install sandcastle-ai[docker]
SANDBOX_BACKEND=local # dev only, no isolation
SANDBOX_BACKEND=cloudflare # requires deployed CF Worker
All backends share the same SandboxBackend protocol - same YAML, same API, same dashboard. Switch backends without changing workflows.
Docker hardening: When using the Docker backend, containers run with all capabilities dropped, a seccomp profile restricting syscalls, PID limits (default 100), CPU quotas (default 50%), memory limits (default 512 MiB), and an unprivileged user (1000:1000). All configurable via environment variables.
Security-relevant settings:
| Setting | Default | Meaning |
|---|---|---|
CODE_STEPS_ALLOW_UNTRUSTED |
false |
In-process code steps are admin-only by default. Set true on single-tenant self-hosted deployments to let any authenticated caller run them; keep false for multi-tenant. |
CODE_STEPS_OUT_OF_PROCESS |
true |
Run code steps in a separate Python subprocess so a sandbox escape cannot reach the parent process memory (settings, DB session, other tenants' data). The child is really killed on timeout. Set false to fall back to the legacy in-process path. |
MEMORY_MCP_TOKEN |
— | Bearer token for the Memory MCP streamable-http transport. Required to start it outside local mode (fails closed); callers must send Authorization: Bearer <token>. stdio is unaffected. |
MEMORY_MCP_SCOPE_PREFIX |
— | Optional tenant scope prefix for the Memory MCP server. When set, every tool call (add/search/forget/list_memories) must operate within this prefix, so an authenticated token cannot cross tenants. Unset preserves single-tenant behavior. |
Browser Step - LightPanda & Browserbase
The built-in browser step type supports three modes for web automation:
| Mode | Description | Best For |
|---|---|---|
| playwright (default) | Chromium via Playwright (pre-baked in Dockerfile) | General web automation |
| lightpanda | 10x faster headless browsing via CDP (no Chromium) | High-throughput scraping |
| browserbase | Cloud-hosted browser sessions, zero cold-start | Production scraping, scalable |
steps:
- id: "scrape"
type: browser
browser:
mode: lightpanda # "playwright" | "lightpanda" | "browserbase"
url: "https://example.com"
actions:
- click: "#load-more"
- extract: ".results"
For Browserbase, set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID in your environment. LightPanda requires the lightpanda binary on PATH or set LIGHTPANDA_PATH.
Multi-Provider Model Routing
Use different AI providers per step. Claude for quality-critical tasks, cheaper models for simple scoring, or mix providers in a single workflow:
| Model ID | Provider | Runner | Pricing (per 1M tokens) |
|---|---|---|---|
sonnet |
Claude (Anthropic) | Claude Agent SDK | $3 in / $15 out |
opus |
Claude (Anthropic) | Claude Agent SDK | $15 in / $75 out |
haiku |
Claude (Anthropic) | Claude Agent SDK | $0.80 in / $4 out |
openai/codex-mini |
OpenAI | OpenAI-compatible | $0.25 in / $2 out |
openai/codex |
OpenAI | OpenAI-compatible | $1.25 in / $10 out |
minimax/m2.5 |
MiniMax | OpenAI-compatible | $0.30 in / $1.20 out |
google/gemini-2.5-pro |
Google (via OpenRouter) | OpenAI-compatible | $4 in / $20 out |
omlx/llama-4-scout |
oMLX (local) | OpenAI-compatible | Free (self-hosted) |
omlx/mistral-small |
oMLX (local) | OpenAI-compatible | Free (self-hosted) |
omlx/gemma-3 |
oMLX (local) | OpenAI-compatible | Free (self-hosted) |
omlx/qwen-3 |
oMLX (local) | OpenAI-compatible | Free (self-hosted) |
steps:
- id: "research"
model: opus # Claude for deep research
prompt: "Research {input.company} thoroughly."
- id: "score"
depends_on: ["research"]
model: haiku # Claude Haiku for cheap scoring
prompt: "Score this lead 1-100."
- id: "classify"
depends_on: ["research"]
model: openai/codex-mini # OpenAI for classification
prompt: "Classify the industry."
Automatic failover: if a provider returns 429 or 5xx, Sandcastle retries with the next provider in the fallback chain. Per-key cooldown prevents hammering a failing endpoint.
Universal Advisor
All AI-powered features (workflow generation, evolution, quality evaluation, error explanation) use a configurable advisor LLM. By default, Sandcastle uses Claude - but you can switch to any supported provider:
| Provider | Region | Env Vars |
|---|---|---|
| Anthropic (Claude) | US | ANTHROPIC_API_KEY |
| Mistral | EU | MISTRAL_API_KEY |
| OpenAI | US | OPENAI_API_KEY |
| Google (via OpenRouter) | US | OPENROUTER_API_KEY |
| MiniMax | US | MINIMAX_API_KEY |
| Ollama (local) | Local | None required |
| oMLX (local) | Local | None required |
# Switch to Mistral (EU data residency)
export SANDCASTLE_ADVISOR_PROVIDER=mistral
export SANDCASTLE_ADVISOR_MODEL=mistral-large-latest
export MISTRAL_API_KEY=your-key
# Switch to local Ollama (100% private, no cloud)
export SANDCASTLE_ADVISOR_PROVIDER=ollama
export SANDCASTLE_ADVISOR_MODEL=llama3.2
EU Data Residency Mode
Enforce that all AI processing stays within EU borders:
export DATA_RESIDENCY=eu
When active, only EU-region providers (Mistral) and local providers (Ollama, oMLX) are allowed. Attempts to use US providers will fail with a clear error message.
OpenRouter (100+ Models)
Access 100+ models from all major providers through a single API key:
export OPENROUTER_API_KEY=sk-or-...
This enables Google Gemini, Meta Llama, Cohere, and many more through the unified OpenRouter API. See openrouter.ai/models for the full list.
64 Built-in Integrations
Sandcastle ships with 64 zero-config tool connectors across 11 categories. Each integration is a lightweight JavaScript module that agents can call during workflow execution. Named connections let you wire multiple accounts (e.g. "production-slack" vs "staging-slack"), and all credentials are encrypted at rest with Fernet (AES-128-CBC + HMAC-SHA256).
| Category | Tools |
|---|---|
| Communication | Slack, Microsoft Teams, Discord, Twilio, SendGrid, Resend, WhatsApp |
| Project Management | Jira, Linear, Notion, Airtable, Google Sheets, Figma |
| CRM | HubSpot, Salesforce, Zendesk, Intercom |
| Data | MongoDB, Snowflake, Supabase, Pinecone, Redis, Database, Google Drive, Qdrant, GCS, Azure Blob |
| ERP | SAP, ServiceNow, Helios, ABRA |
| Payments | Stripe, Shopify, QuickBooks, Plaid, DocuSign |
| AI | OpenAI, Anthropic, ElevenLabs, Langfuse |
| DevOps | GitHub, AWS S3, Vercel, Cloudflare Workers, Datadog, PagerDuty |
| General | Webhook, Zapier, Calendly, Firecrawl, Tavily, Exa, MCP Bridge, Human Input, Filesystem, Shell, Python Runtime, Code Interpreter, Browser |
steps:
- id: "notify"
type: notify
service: slack
connection: production-slack
template: "Lead {steps.score.output.company} scored {steps.score.output.score}/100"
Workflow Engine
Define multi-step agent pipelines as YAML. Each step can run in parallel, depend on previous steps, pass data forward, and use different models.
Example: lead-enrichment.yaml
name: "Lead Enrichment"
description: "Scrape, enrich, and score leads for sales outreach."
default_model: sonnet
default_max_turns: 10
default_timeout: 300
steps:
- id: "scrape"
prompt: |
Visit {input.target_url} and extract:
company name, employee count, main product, contact info.
Return as structured JSON.
output_schema:
type: object
properties:
company_name: { type: string }
employees: { type: integer }
product: { type: string }
contact_email: { type: string }
- id: "enrich"
depends_on: ["scrape"]
prompt: |
Given this company data: {steps.scrape.output}
Research: revenue, industry, key decision makers, recent news.
retry:
max_attempts: 3
backoff: exponential
on_failure: abort
- id: "score"
depends_on: ["enrich"]
prompt: |
Score this lead 1-100 for B2B SaaS potential.
Based on: {steps.enrich.output}
model: haiku
on_complete:
storage_path: "leads/{run_id}/result.json"
Parallel Execution
Steps at the same DAG layer run concurrently. Use parallel_over to fan out over a list:
steps:
- id: "fetch-competitors"
prompt: "Identify top 3 competitors for {input.company_url}."
- id: "analyze"
depends_on: ["fetch-competitors"]
parallel_over: "steps.fetch-competitors.output.competitors"
prompt: "Analyze {input._item} for pricing and feature changes."
retry:
max_attempts: 2
backoff: exponential
on_failure: skip
- id: "summarize"
depends_on: ["analyze"]
prompt: "Create executive summary from: {steps.analyze.output}"
Data Passing Between Steps
When you connect steps with depends_on, data flows automatically. You don't need to reference the previous step's output explicitly - Sandcastle injects it as context:
steps:
- id: "research"
prompt: "Find all EU presidents and return as JSON."
- id: "enrich"
depends_on: ["research"]
prompt: "Add political party and key decisions for each president."
# Output from "research" is automatically available - no need for {steps.research.output}
For fine-grained control, you can still reference specific outputs explicitly using {steps.STEP_ID.output} or drill into fields with {steps.STEP_ID.output.field_name}:
- id: "score"
depends_on: ["scrape", "enrich"]
prompt: |
Score this lead based on company: {steps.scrape.output.company_name}
and enrichment: {steps.enrich.output}
Rules:
depends_oncontrols execution order and data flow- Unreferenced dependency outputs are appended as context automatically
- Explicitly referenced outputs (
{steps.X.output}) are placed exactly where you write them {input.X}references workflow input parameters passed at run time
25 Step Types
Sandcastle supports 25 step types for building complex workflows beyond simple LLM prompts:
| Phase | Type | Description |
|---|---|---|
| Core |
No comments yet
Be the first to share your take.