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.

PyPI License: BSL 1.1 Python 3.12+ Tests EU AI Act Website Live Demo

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_box compliance mode, and sandcastle 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 · Releases

Previous: 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?

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 (or eu) 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, region local — you pay for electricity, not tokens.
  • Your data never leaves the box. Fully local and air-gappable. Pair Spark Mode with the docker sandbox backend and data_residency=local for 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_over fan-outs.
  • One-glance status. sandcastle serve prints a Spark Mode banner, the dashboard shows a ⚡ Spark Mode badge, and GET /api/runtime exposes spark_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_on controls 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