# sand-boxer meta-framework specification Version 0.1 — derived from `research/03-meta-framework-synthesis.md` and `INTENT.md`. sand-boxer is the **sandbox establishment service**: one API for consumers, many extension backends. It provisions *where and how* code runs; sibling projects own agent harnessing, validation, and code generation. --- ## Resource model | Resource | Description | |----------|-------------| | **Profile** | Named, versioned sandbox recipe: extension binding, isolation, network, TTL, placement | | **Extension** | Backend adapter implementing provision / wait_ready / teardown | | **Host** | Registered placement target for self-hosted extensions; read-only telemetry via `profile.sandbox-canary` (see `docs/host-telemetry.md`) | | **Sandbox** | Running instance of a profile | | **Snapshot** | Point-in-time workspace checkpoint (`sandboxer snapshot` / `restore`) | | **Route** | Extension selection policy when multiple backends qualify | | **Meter** | Usage record for payments layer (SaaS extensions — SAND-WP-0006) | --- ## Lifecycle states ``` requested → provisioning → ready → active → { expired | failed } → destroying → destroyed ``` | State | Meaning | |-------|---------| | `requested` | Create accepted; not yet handed to extension | | `provisioning` | Extension running provision + wait_ready | | `ready` | Reachability confirmed; consumer may attach | | `active` | Consumer has marked sandbox in use (optional transition) | | `expired` | TTL elapsed before explicit destroy | | `failed` | Provision or readiness failed | | `destroying` | Teardown in progress | | `destroyed` | Resources released; record retained for audit | ### State Hub event mapping Each transition emits a State Hub progress event (or dedicated registration API when available): | Transition | `event_type` | Required fields | |------------|--------------|-----------------| | → `requested` | `note` | `sandbox_id`, `profile_id`, `consumer` | | → `provisioning` | `note` | `extension_id`, `host` | | → `ready` | `milestone` | `reachability` descriptor | | → `active` | `note` | `actor_type`, timestamps | | → `failed` | `note` | `error` summary | | → `destroying` | `note` | — | | → `destroyed` | `milestone` | `duration_s`, cleanup report | Event `detail` payload (JSON): ```json { "sandbox_id": "abc12345", "profile_id": "profile.compose-e2e", "extension_id": "ext.compose-ssh", "host": "coulombcore", "consumer": {"actor": "atm", "project": "wise-validator", "run_id": "..."}, "actor_type": "atm", "state": "ready", "reachability": { "ssh": "root@coulombcore", "remote_dir": "/tmp/sandboxer/abc12345", "tunnel": "localhost:12222", "tunnel_via": "ops-bridge", "identity": "ops-warden" }, "timestamps": {"created_at": "...", "ready_at": "..."} } ``` Extends the `build-agent` self-register pattern: generic sandbox identities carry `profile_id` + `extension_id` instead of build-machine metadata. --- ## Core API operations (v0) | Operation | Description | v0 scope | |-----------|-------------|----------| | `create` | Provision from profile + inputs | **Yes** | | `get` | Inspect sandbox status | **Yes** | | `list` | List sandboxes (filter by consumer optional) | **Yes** | | `extend_ttl` | Extend time-to-live | **Yes** | | `recreate` | Destroy and reprovision from stored seed | **Yes** | | `destroy` | Idempotent teardown | **Yes** | | `snapshot` / `restore` | Checkpoint workspace | **Yes** (compose-ssh, saas-stub) | | `exec` | Run command in sandbox | Harness-owned via SSH (glas-harness) | HTTP surface (optional v0; CLI calls core library directly): - `POST /v1/sandboxes` — create - `GET /v1/sandboxes/{id}` — get - `GET /v1/sandboxes` — list - `DELETE /v1/sandboxes/{id}` — destroy - `POST /v1/sandboxes/{id}/snapshot` — checkpoint - `POST /v1/snapshots/{id}/restore` — restore - `GET /v1/snapshots` — list checkpoints - `POST /v1/sandboxes/{id}/recreate` — recreate - `PATCH /v1/sandboxes/{id}/ttl` — extend TTL - `POST /v1/sandboxes/expire` — TTL reap (query `apply=true`) - `GET /v1/sandboxes/{id}/reachability` — enriched descriptor + SSH one-liner --- ## Reachability descriptor When a sandbox reaches `ready`, sand-boxer emits a **reachability** block on `SandboxStatus`, lifecycle events, and `sandboxer reachability show `. | Field | Source | Description | |-------|--------|-------------| | `ssh` | Extension | SSH target (`user@host`) | | `remote_dir` | Extension | Workspace root on remote host | | `host` | Extension | Placement host name | | `tunnel` | Profile + env | Local port (`localhost:PORT`) or VM alias | | `tunnel_via` | Profile spec | Route owner (default `ops-bridge`) | | `identity` | Profile spec | Warden actor hint (default `ops-warden`) | Tunnel metadata is enriched from profile `reachability` and environment: - `SANDBOXER_TUNNEL_PORT` / handle `tunnel_port` or `ssh_port` - `SANDBOXER_TUNNEL_ALIAS` / handle `vm_target` - `SANDBOXER_TUNNEL_VIA` (optional override) sand-boxer **does not** bring tunnels up. Consumers use ops-bridge (MCP or `bridge` CLI) to attach SSH routes; the descriptor is a pointer only. `secret_refs` from `profile.setup` are resolved at the provision boundary and passed to the extension handle — they never appear on `SandboxStatus` or State Hub events. --- ## Consumer attribution Every create request carries a **consumer** block: ```yaml consumer: actor: adm | agt | atm project: # e.g. wise-validator, glas-harness session_id: run_id: ``` | Actor | Typical caller | |-------|----------------| | `adm` | Human operator via CLI | | `agt` | LLM agent session | | `atm` | Deterministic automation (CI, activity-core, wise-validator) | sand-boxer records attribution on every lifecycle event. It does not interpret agent intent or authorize the caller — flex-auth owns authorization when enforced. --- ## Extension interface Each extension implements: ```text provision(profile, inputs, placement) → SandboxHandle wait_ready(handle) → Reachability teardown(handle) → CleanupReport estimate_cost?(profile, duration) → MeterQuote # optional; SaaS only ``` Registration requirements (validated at load time): - `id` — unique extension identifier (`ext.`) - `capabilities` — isolation levels, regions, persistence, pricing model - `handler` — Python entry point or built-in registry binding Extensions are discovered from `extensions/*.yaml` at repo root and loaded via `sandboxer.extensions.registry`. --- ## Routing policy vocabulary When multiple extensions satisfy a profile capability: | Strategy | Behavior | |----------|----------| | `prefer-self-hosted` | Self-hosted extensions first; SaaS fallback (default Coulomb posture) | | `lowest-cost` | Cheapest `estimate_cost` quote wins | | `lowest-latency` | Closest region / host wins | | `explicit` | Profile names a single `extension`; no auto-routing | Routing engine v0: `sandboxer.routing.resolver` — see `docs/routing.md`. --- ## Security limits sand-boxer commits to: 1. **Default-deny network** unless profile explicitly allows egress 2. **Secrets at provision boundary** — `secret_refs` resolved via ops-warden / OpenBao; never returned to agent context 3. **Blast-radius isolation** — dedicated hosts (sandboxer01, CoulombCore) away from Railiance01 production 4. **Observable lifecycle** — every transition attributed to `adm` / `agt` / `atm` 5. **Honest limits** — allowed tool paths can be abused by compromised agents sand-boxer does **not** provide intent-aware egress filtering in v1. --- ## Out of scope (sibling ownership) | Concern | Owner | |---------|-------| | Agent gateway, tools, memory | glas-harness | | e2e.yml semantics, health checks, test pass/fail | wise-validator | | Code generation, setup instructions content | snuggle-inventor | | SSH tunnels | ops-bridge | | SSH certificates | ops-warden | | Workstream / task state | state-hub | See `docs/integrations/` for per-sibling contracts.