Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a06def-6490-7033-8448-2eab2d12ed44
4 KiB
Extension SDK
Author guide for sand-boxer backend adapters. Version 0.1 — SAND-WP-0005.
Contract
Every extension implements three methods:
provision(profile, inputs, host) → handle dict
wait_ready(handle) → reachability dict
teardown(handle) → cleanup report dict
Optional (SaaS): estimate_cost(profile, duration) → MeterQuote
Optional (checkpoints): supports_snapshots(), snapshot(handle),
restore_from_snapshot(profile, snapshot_meta, inputs, host)
Optional (owner-mediated execution): supports_execution(), then
execute(handle, command, credential_route_refs, execution_context, timeout_seconds, max_output_bytes, stdin_text). The default implementation fails closed.
ext.bwrap is the reference implementation. An executing extension must
validate that its workspace belongs to the exact sandbox handle, sanitize the
child environment, use an argument vector, enforce the requested bounds, and
must not fall back to a host workspace.
Bounded stdin is content-bearing and must never be included in lifecycle logs or
execution evidence.
Base class
from sandboxer.extensions.base import SandboxExtension
class MyExtension(SandboxExtension):
def provision(self, profile, inputs, host): ...
def wait_ready(self, handle): ...
def teardown(self, handle): ...
Reference implementations:
| Extension | Module | Mode |
|---|---|---|
ext.compose-ssh |
compose_ssh.py |
Remote compose stack + tar snapshots |
ext.vm-packer |
vm_packer.py |
Attach workspace or Packer build mode |
ext.saas-stub |
saas_stub.py |
Metered stub + metadata snapshots |
ext.e2b |
e2b.py |
E2B cloud adapter |
ext.modal |
modal.py |
Modal cloud adapter |
ext.bwrap |
bwrap.py |
Local namespaces + owner-mediated execution |
Registration
- Add
extensions/ext.<name>.yaml:
id: ext.my-backend
title: My Backend
handler: sandboxer.extensions.my_backend:MyExtension
capabilities:
isolation_levels: [container]
pricing_model: self-hosted
config:
key: value
- Add a profile binding in
profiles/profile.<slug>.yamlwithextension: ext.my-backend. - Register capability metadata in
registry/when ready for reuse-surface.
Loader validates capabilities.isolation_levels and capabilities.pricing_model
at startup (sandboxer.extensions.registry).
Handle and reachability
Handle (returned by provision, stored in manager): opaque dict passed to
wait_ready and teardown. Include at minimum:
sandbox_idhost(placement host)- Fields your extension needs for SSH/API (e.g.
remote_dir,vm_target)
Reachability (returned by wait_ready): exposed on SandboxStatus.reachability:
ssh— SSH destination stringremote_dir— workspace path on remotehost— placement hostcompose_project— compose-ssh only
Inputs convention
Profiles declare semantics; extensions validate required inputs keys:
| Extension | Required inputs | Optional |
|---|---|---|
| compose-ssh | repo |
sandbox_id |
| vm-packer (attach) | vm or ssh_target |
repo, tunnel_port, ssh_port, workspace_dir |
| vm-packer (build) | packer_template, vm_name |
mode=build, packer_var_* |
Consumer attribution travels on SandboxCreateRequest.consumer, not extension inputs.
Testing
Mock SSH/subprocess in unit tests. See tests/test_compose_ssh.py, tests/test_vm_packer.py.
Pattern:
with patch.object(SSHConfig, "run", return_value=(0, "ready")):
ext = VMPackerExtension()
handle = ext.provision(profile, {"vm": "haskell-build"}, "localhost")
Metered extensions (SAND-WP-0006)
Implement estimate_cost and meter_actual on SandboxExtension. Register with
pricing_model: metered. See docs/payments.md and ext.saas-stub.
Deferred
| Feature | Workplan |
|---|---|
Packer build orchestration from create |
Future WP |
| Daytona OSS cloud adapter | Future WP |
| fin-hub billing export | Future |
| Cross-host snapshot transfer | Future |