Skip to content

Architecture Overview

SAM is a serverless platform for ephemeral AI coding environments. The architecture splits into three layers: edge (Cloudflare), compute (Hetzner VMs), and external services (GitHub, DNS).

┌─────────────────────────────────────────────────────────┐
│ Browser │
│ React SPA (app.domain) ──── xterm.js ──── Agent Chat │
└─────────┬───────────────────────┬───────────────────────┘
│ HTTPS │ WSS
▼ ▼
┌─────────────────────────────────────────────────────────┐
│ Cloudflare Edge │
│ │
│ ┌─────────────┐ ┌──────┐ ┌────┐ ┌────┐ │
│ │ API Worker │ │ D1 │ │ KV │ │ R2 │ │
│ │ (Hono) │──│SQLite│ │ │ │ │ │
│ │ │ └──────┘ └────┘ └────┘ │
│ │ + Proxy │ │
│ │ + Auth │ ┌──────────────────────┐ │
│ │ + DOs │ │ Cloudflare Pages │ │
│ └──────┬───────┘ │ (React SPA) │ │
│ │ └──────────────────────┘ │
└─────────┼───────────────────────────────────────────────┘
│ HTTP/WSS (proxied via DNS-only records)
┌─────────────────────────────────────────────────────────┐
│ Hetzner Cloud VM │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ VM Agent (Go, :8080) │ │
│ │ ├── PTY Manager (terminal sessions) │ │
│ │ ├── Container Manager (Docker) │ │
│ │ ├── ACP Gateway (Claude Code) │ │
│ │ └── JWT Validator (JWKS) │ │
│ └───────────────┬───────────────────────┘ │
│ │ │
│ ┌───────────────▼───────────────────────┐ │
│ │ Docker Engine │ │
│ │ ├── Workspace Container 1 │ │
│ │ └── Workspace Container N │ │
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

Every request to *.domain passes through the same Cloudflare Worker. The Host header determines routing:

PatternDestinationHow
app.{domain}Cloudflare PagesWorker proxies to {project}.pages.dev
api.{domain}Worker API routesDirect handling by Hono router
ws-{id}.{domain}VM Agent on port 8080Worker proxies via DNS-only vm-{nodeId}.{domain}
*.{domain} (other)404No matching route

The API Worker (apps/api/) is a Hono application handling:

  • Authentication — GitHub OAuth via BetterAuth
  • Resource management — CRUD for nodes, workspaces, projects
  • Reverse proxy — workspace subdomain traffic to VMs
  • Durable Objects — per-project chat data (ProjectData DO), node lifecycle (NodeLifecycle DO)
  • Cron triggers — provisioning timeout checks every 5 minutes
RoutePurpose
/api/auth/*GitHub OAuth sign-in/out, sessions
/api/nodes/*Node CRUD, lifecycle, health callbacks
/api/workspaces/*Workspace CRUD, lifecycle, boot logs, agent sessions
/api/credentials/*Cloud provider + agent API key management
/api/github/*GitHub App installations, repos
/api/terminal/tokenWorkspace JWT for WebSocket auth
/api/agent/*VM Agent binary download
/api/bootstrap/:tokenOne-time credential injection
ServiceBindingPurpose
D1 (SQLite)DATABASEUsers, nodes, workspaces, credentials, sessions
D1OBSERVABILITY_DATABASEError storage for admin dashboard
KVKVAuth sessions, bootstrap tokens, boot logs
R2R2VM Agent binaries, Pulumi state
Durable ObjectsPROJECT_DATAPer-project chat sessions, messages, activity
Durable ObjectsNODE_LIFECYCLEPer-node warm pool state machine

The VM Agent (packages/vm-agent/) is a Go binary running on each node:

SubsystemPackageResponsibility
PTY Managerinternal/pty/Terminal multiplexing, ring buffer replay
Container Managerinternal/container/Docker exec, devcontainer CLI
ACP Gatewayinternal/acp/Claude Code protocol, streaming responses
JWT Validatorinternal/auth/Validates workspace JWTs via JWKS endpoint
Persistenceinternal/persistence/SQLite tab storage
Boot Loggerinternal/bootlog/Reports provisioning progress
Push to main
├── Phase 1: Infrastructure (Pulumi)
│ └── D1, KV, R2, DNS records
├── Phase 2: Configuration
│ └── Sync wrangler.toml, read security keys
├── Phase 3: Application
│ └── Build → Deploy Worker → Deploy Pages → Migrations → Secrets
├── Phase 4: VM Agent
│ └── Build Go (multi-arch) → Upload to R2
└── Phase 5: Validation
└── Health check polling

CI runs lint, typecheck, tests, and build on every push. The deploy workflow only triggers on pushes to main.

DecisionRationale
Single Worker as API + reverse proxySimplifies infrastructure — one Worker handles everything
D1 for persistent stateSQLite at the edge with zero management
User-provided Hetzner tokens (BYOC)Users own their infrastructure and costs
Callback-driven provisioningVMs POST /ready when bootstrapped — no polling
Dynamic DNS per workspaceInstant subdomain resolution; cleaned up on stop
Durable Objects for chat dataHigh-throughput writes without D1 contention
No credentials in cloud-initBootstrap tokens for secure credential injection