Runbook¶
How to operate the project: develop, verify, release, and respond when something upstream breaks or someone reports a vulnerability.
Commands below assume uv and the decisions from
Step 0. Anything marked (planned)
does not exist yet.
Dev setup¶
One command, and it stays one command — a contributor who cannot get running in five minutes does not become a contributor.
uv sync --all-extras # core + docker + e2b + modal + dev
uv run pytest # unit + fake contract suite; no Docker, no network
Requirements: Python 3.14 for development (floor is 3.11 —
ADR-0015), Docker for the Docker contract job, and
provider credentials only for cloud jobs. Credentials live in a git-ignored .env
(E2B_API_KEY=...) and reach the process only through uv run --env-file .env; the
library never reads dotfiles. uv python install 3.14 if you do not have it;
the floor is proven by CI, not by your local interpreter.
Daily loop¶
uv run pytest -q # fast: fake backend only
uv run pytest -m docker # Docker contract suite (needs Docker)
uv run --env-file .env pytest -m e2b # E2B contract suite (needs E2B_API_KEY in .env)
uv run ruff check . && uv run ruff format --check .
uv run pyright && uv run mypy
uv run python -X importtime -c "import sandboxio" 2>&1 | tail -1 # import budget
uv run python scripts/check_doc_links.py # doc links + anchors
uv run mkdocs serve # docs site (--group docs)
Before pushing, run what CI runs on PRs: unit + fake + docker contract + lint + types + import budget.
CI gates¶
Full matrix in spec/08. The gates that block merge:
| Gate | Threshold | Why it exists |
|---|---|---|
| Import budget | <150 ms, hard fail >200 ms | agents cold-start this on every run (H8) |
| No network at import | zero sockets under pytest-socket |
offline and air-gapped CI must work |
| Wheel contents | base install pulls no adapter code; extras resolve | supply-chain surface (H10) |
| Types | pyright + mypy strict on public API; py.typed in wheel |
without py.typed, downstream mypy sees Any |
| Contract suite | 100% for every declared capability | ADR-0007 |
| No leaked containers | zero sandboxio-labelled containers after Docker jobs | H2 |
| Error catalog | every code has a page, every page a code | spec/04 |
| Doc samples | every YAML/JSON sample in spec/ parses; every Python sample compiles |
the input set shipped a config that was not valid YAML |
| Docs site | mkdocs build --strict passes and every error code resolves under /errors/ (tests/test_docs_site.py) |
the error links ship inside the wheel (ADR-0029) |
| Doc links | every relative link and heading anchor resolves (scripts/check_doc_links.py), and no link leaves docs/ |
spec/ is the contract; a dead link into it is a dead requirement |
| Executable docs | every README Python block (tests/test_readme_examples.py) and every program in examples/ (tests/test_examples.py) runs, with docker/e2b resolving to the fake |
a copied example that does not run is a P0 bug; it should fail our build, not a stranger's first attempt |
A red gate is not overridden. If a gate is wrong, change the gate in its own PR, with a reason.
Provider churn response¶
The nightly latest-SDK canary is the early-warning system, and absorbing what it catches is the core value proposition (H5).
It is the latest-SDK canary job in ci.yml: it installs
every provider SDK unpinned, then runs tests/test_sdk_surface.py — the symbols and methods
each adapter binds to — plus the whole credential-free suite. The live E2B contract suite
runs on top of that only when E2B_API_KEY is set, so a rename upstream is caught with or
without a provider account.
When the canary goes red:
- It opens a
provider-churnissue, or comments on the open one rather than filing a fresh issue every night. Triage within one working day — silence here is the failure mode. - Reproduce against the pinned SDK version to confirm it is upstream, not us.
- Classify:
- Breaking API change → absorb it in the adapter. Users should not need to change code. If they must, it is a minor bump with a migration note.
- Behaviour change → check whether the contract suite caught it. If it did not, the suite has a gap; add the test in the same PR.
- New capability →
Capabilityflag if ≥2 backends have it, otherwise.native. - Removed capability → capability flag goes false for that backend; document it as a provider change, not an sandboxio regression.
- Write the churn-log entry. Date, provider, versions affected, what broke, what we did, whether users need to act.
- Release. Churn absorption should ship in days, not at the next milestone.
Churn-absorption log¶
A public docs page — churn-log.md — maintained per absorbed break. It is the marketing for the core value
prop — the evidence that the abstraction pays for itself — so it is written for users, not
as a changelog dump. Never let it go stale; an empty churn log and a busy one both tell a
story.
Release¶
- Green CI on
main, including nightly cloud contract jobs. CHANGELOG.mdupdated (keep-a-changelog). Note every absorbed provider break.- Verify the semver contract: error codes, DSN grammar, protocols,
ExecResultshape,Capability/IsolationTiermembers, contract suite (spec/03)..nativeis excluded — changes there are not breaking. - Deprecations landing in this release carry
DeprecationWarningwith correctstacklevel, PEP 702@deprecated, a changelog entry, and a stated removal window. - Tag; publish via PyPI Trusted Publishing with PEP 740 attestations. No token-based
publishing, no local
twine upload. - Publish the MCP container image; update the Docker MCP Catalog entry.
- Verify the install path a user actually takes:
uvx sandboxio demoon a clean machine. - Confirm the Docs workflow deployed and the codes this release touched resolve, e.g.
https://docs.sandboxio.dev/errors/SBX_E1002.
Never: publish from a laptop, hard-pin a provider SDK to force a green build, or ship a release with a skipped contract test.
Security advisories¶
SECURITY.md and this process exist from day one, not from the first report.
- Intake — private report via GitHub Security Advisory. Acknowledge within 48 hours.
- Triage — assess severity, with particular weight on anything in the policy enforcement path (H1) or, in Phase 2, the server auth path (server hazards).
- Fix in private — draft advisory, fix, and a regression test in the contract suite. A security fix without a test is not finished.
- Coordinate — if the root cause is a provider's, coordinate disclosure with them.
- Release and disclose — patch release, published advisory with a CVE where warranted, changelog entry. Do not bury it.
- Post-incident — what class of bug was it, and which gate should have caught it? Add the gate.
For a policy-enforcement bug specifically: state plainly which control did not apply, from which version, and what users should check. Users made trust decisions on our word.
Issue triage¶
Weekly, or as they arrive for anything security-adjacent.
- Issue templates require reproduction. No repro, no triage — stated up front, applied without debate (H11).
- Ask for
sandboxio doctor --jsonoutput on any environment report; it is designed to be pasted and carries no secrets (spec/10). - Labels:
provider-churn(auto from canary),adapterandadapter:<name>,security,spec-gap,breaking,dependencies, and GitHub's owngood first issue— spelled with spaces, because that is the one its newcomer search reads. - A bug that the contract suite should have caught gets a
spec-gaplabel and a suite test in the fix PR. That loop is what keeps the suite meaningful. - Q&A goes to GitHub Discussions — searchable and indexed, unlike chat.
- PRs need a DCO
Signed-off-byline (ADR-0016); there is no CLA.
Quarterly review¶
The competitor and market facts underpinning hazards.md were a
Q3 2026 snapshot. This space moves monthly; a stale snapshot silently becomes a wrong
strategy.
Every quarter:
- [ ] Re-verify competitor state: ComputeSDK, VibeKit, llm-sandbox, LangChain sandbox
backends, OpenAI Agents SDK
SandboxConfig. - [ ] Re-verify each backend's isolation tier against current provider docs; update the dated sources (H4).
- [ ] Check every pre-committed gate against reality, and write down the answer even when it is "no change".
- [ ] Review hazards.md: any tripwire fired? any hazard now stale?
- [ ] Review open-questions.md: anything DEFERRED whose trigger fired?
- [ ] Check the OTel GenAI semconv version for renames (ADR-0011).
- [ ] Daytona: isolation tier, SDK state, license state —
UNKNOWNuntil proven. - [ ] Any backend still reporting
UNKNOWN: can it be verified and promoted this quarter?
The point of pre-committing the thresholds is that this review is mechanical. Do not renegotiate a threshold during the review in which it fires.