07 — Configuration¶
See ADR-0008.
DSN grammar¶
Examples: docker://python:3.12-slim, e2b://code-interpreter?timeout=600, fake://, and
— once the Modal adapter lands in v0.1.1 — modal://base?gpu=T4.
Rules:
-
timeoutis the only parameter defined in v0.1. A backend MAY define further scalar parameters, and they arrive with that adapter, not before:gpuis Modal's and is not accepted by any v0.1 backend. Anything not defined by the resolved backend is an unknown parameter and raises. -
The grammar is public API under semver. Adding a scheme or parameter is additive; changing the meaning of an existing parameter is breaking.
- Query parameters are simple scalars only. Anything structured — network allowlists, resource shapes, secrets, metadata — is typed-object-only. A DSN MUST NOT be able to express a security policy ambiguously.
- An unknown scheme raises
BackendNotFoundlisting installed backends, plus the install command for a known-but-missing one. - An unknown parameter MUST raise
ConfigurationError, never be ignored. - The DSN parser is a front end that constructs the same typed objects, never a second configuration code path.
Typed configuration¶
sandboxio.create(E2BConfig(
template="code-interpreter",
network=NetworkPolicy(allow=("api.openai.com",)),
resources=Resources(memory_mb=2048),
))
Production usage SHOULD prefer typed config: reviewable, autocompleting, no stringly-typed policy.
Credentials¶
- Credentials come from per-provider environment variables by that provider's own
convention (
E2B_API_KEY,MODAL_TOKEN_ID/MODAL_TOKEN_SECRET, …). - Credentials MUST NOT appear in a DSN. The parser SHOULD detect credential-looking
parameters and raise
ConfigurationErrornaming the correct env var. - sandboxio MUST NOT persist credentials, and MUST NOT log them even at debug verbosity.
- A missing credential MUST raise
AuthErrornaming the exact variable (04).
Backend resolution¶
- Explicit
sandboxio.register(name, "pkg:Class")registrations. - Entry points in group
sandboxio.backends, read lazily viaimportlib.metadata. - Nothing found →
BackendNotFound/BackendNotInstalled.
Resolution MUST be lazy and cached. Entry-point scanning counts against the import budget (ADR-0004).
Routing file¶
Deferred to v0.2. Nothing in this section is implemented in v0.1, and no v0.1 caller can load a routing file or call
route_for(). The design is kept here because the DSN and typed-config surfaces above were shaped to leave room for it; the MUSTs below bind the implementation when it lands, not v0.1 adapters (build-order).
Designed now, loadable by the library before any server exists
(ADR-0009). Mental model: Kubernetes
RuntimeClass for isolation classes, LiteLLM config.yaml for the policy surface.
# sandboxio-routing.yaml
backends:
docker-local: { adapter: docker, image: "python:3.12-slim" }
e2b-fast: { adapter: e2b, template: code-interpreter, api_key: os.environ/E2B_API_KEY }
modal-gpu: { adapter: modal, gpu: T4, api_key: os.environ/MODAL_TOKEN }
isolation_classes: # cf. Kubernetes RuntimeClass
standard: { backend: docker-local } # trusted / dev
sandboxed: { backend: modal-gpu } # gVisor tier
isolated: { backend: e2b-fast } # microVM tier — untrusted multi-tenant
routes: # first match wins
- match: { tool: run_python }
class: isolated
- match: { tool: data_transform }
class: sandboxed
- match: { tenant_tier: enterprise }
class: isolated
default_class: standard # mandatory
policy:
network: { egress: deny }
limits: { timeout_s: 300, memory_mb: 1024 }
spend: { per_tenant_daily_usd: 50 } # server-mode only
Rules:
- Secrets only as
os.environ/NAMEreferences. A literal secret in this file MUST be a load-time error, not a warning. - First-match routing.
default_classis a top-level, mandatory key — a config without it MUST fail to load. It is deliberately not a pseudo-route in therouteslist: a default is not a match rule, and encoding it as one made the list heterogeneous. - Every route entry has exactly the keys
matchandclass. An unknown key MUST be a load-time error. - The example above is parse-tested in CI. The original design set's version of this config was not valid YAML at all (readme errata); every config sample in this spec MUST be machine-verified, not eyeballed.
- Library use:
sandboxio.create(route_for(tool="run_python", tenant_tier="enterprise")). - Which backend or isolation class a tool or tenant gets MUST be a YAML change, never a code change.
spendis meaningful only in server mode; the library MUST reject it with a clear message rather than ignoring it.