arcbox
Python SDK for ArcBox sandboxes: isolated microVMs on your Mac, driven over the local daemon's Unix socket with the Connect protocol. Requires Python ≥ 3.10.
uv add arcbox # or: pip install arcbox
Hello world
With the daemon running (abctl daemon start):
from arcbox import Sandbox
# Local daemon over ~/.arcbox/run/arcbox.sock — zero config.
with Sandbox.create("", ttl=300) as sandbox:
sandbox.files.write_text("/tmp/hello.txt", "hello from arcbox\n")
check = sandbox.commands.run(["/bin/cat", "/tmp/hello.txt"])
print(check.expect().stdout, "→ exit", check.exit_code)
job = sandbox.commands.run("for i in 1 2 3; do echo line$i; done", background=True)
for chunk in job.output:
print(chunk.data.decode(), end="")
print("background job exited", job.wait_for_exit().exit_code)
# context exit: sandbox killed, nothing leaked
Async is a first-class mirror (AsyncSandbox, async with, async for):
from arcbox import AsyncSandbox
async def main() -> None:
sandbox = await AsyncSandbox.create("", ttl=300)
async with sandbox:
await sandbox.files.write_text("/tmp/hello.txt", "hello from arcbox\n")
result = await sandbox.commands.run(["/bin/cat", "/tmp/hello.txt"])
print(result.expect().stdout)
Non-zero exit is data (result.exit_code), never an exception —
result.expect() (or run(..., check=True), subprocess.run-style) is
the opt-in raise. Every daemon error maps to a typed class in
arcbox.errors (SandboxNotFoundError, CapabilityError,
ConnectionFailedError, ...) carrying a machine-readable code, an
actionable suggestion, and the failed operation. Time arguments are
seconds (floats) everywhere.
Connection
Resolution order: explicit option > environment > default.
| Environment | Meaning |
|---|---|
ARCBOX_SOCKET |
daemon Unix socket (default $ARCBOX_DATA_DIR/run/arcbox.sock; data dir default ~/.arcbox, or ~/.arcbox-dev under ARCBOX_PROFILE=development) |
ARCBOX_API_URL |
remote daemon / cloud front door; setting it selects the remote tier (reserved, CORE-63) |
ARCBOX_API_KEY |
bearer credential, attached as Authorization when set; unused by the local daemon |
Every entry point takes a connection=Connection(...) slot
(socket_path / api_url / api_key / request_timeout / injected
http_client for mocking — pass an httpx.Client to the sync surface,
an httpx.AsyncClient to the async one).
The Sandbox / AsyncSandbox classmethods resolve a hidden connection
per call, and the returned handle closes its HTTP client on context
exit. Long-lived programs should hold an ArcBox / AsyncArcBox
instead: it is a context manager (or call .close() / .aclose()),
and every handle it creates shares its client. An injected
http_client always belongs to the caller and is never closed by the
SDK.
Development
Inside the arcbox repo (sdk/python):
uv sync # create .venv from uv.lock
uv run python scripts/gen_proto.py # regenerate src/arcbox/_gen from ../../rpc/arcbox-protocol/proto
uv run python scripts/gen_sync.py # regenerate src/arcbox/_sync from src/arcbox/_async
uv run ruff check . && uv run ruff format --check .
uv run pyright
uv run pytest # includes the sync-tree lockstep + parity checks
Generated code under src/arcbox/_gen/ is committed and is never
exported from the package — public shapes are hand-written and mapped
at the transport boundary.
The async tree (src/arcbox/_async/) is the source of truth; the sync
tree (src/arcbox/_sync/) is generated from it by an unasync token
transform and committed. Edit only the async tree, then rerun
scripts/gen_sync.py. Lockstep is CI-enforced twice: the transform is
rerun and diffed (scripts/gen_sync.py --check, also wired into
pytest), and a parity test asserts identical public surfaces modulo
async markers.
Optional pre-commit hooks (scoped to sdk/python), via
prek or classic pre-commit:
prek install -c sdk/python/prek.yaml
The end-to-end hello-world loop runs only against a live daemon and is opt-in:
ARCBOX_SDK_E2E=1 uv run pytest tests/test_e2e.py
Toolchain notes
- uv is the package/project manager (
uv_buildbackend,uv.lockcommitted). Publishing is CI-only:uv build, then upload viapypa/gh-action-pypi-publishunder PyPI trusted publishing (OIDC, with PEP 740 attestations —uv publishemits none, astral-sh/uv#15618) — see Releasing; noUV_PUBLISH_TOKENanywhere. - ruff is both linter and formatter (
E,F,W,I,UP,B,SIM,RUF). - pyright (strict) is the authoritative type checker. Evaluated
alternatives (2026-08): ty 0.0.65 reports 16 false positives here
(all
unresolved-attributeon protobuf generated-module members) — kept in dev-deps foruv run ty check, may replace pyright when it stabilizes; pyrefly 1.2.0 passes cleanly (it imports the pyright config) and serves as an informational second opinion — one authoritative checker avoids double-suppression drift. - msgspec parses the SDK's one JSON seam — Connect error bodies and
EndStreamResponseframes — as typed, validated Structs at the untrusted-input boundary (chosen for the typed decoding, not speed). - Message types are upstream protobuf runtime code generated by the protoc bundled with grpcio-tools (dev-dep); the bundled protoc version matches the pinned runtime.
- A future native-acceleration path (if profiling ever demands one) is a maturin/PyO3 extension crate in this repo's workspace; nothing in the current SDK needs it.
TODO(CI): wire the gates above into .github/workflows as an
sdk-python job (follow-up; workflow changes are intentionally not part
of this branch).
Releasing
The SDK is a release-please component (sdk-python in
release-please-config.json), released on its own cadence, independent
of the main arcbox release train:
- Conventional commits touching
sdk/pythonaccumulate onmaster. - release-please maintains a dedicated release PR for the component
(separate from the root, fleet-agent, and sdk-typescript PRs) that
bumps the
pyproject.tomlversion and updatesCHANGELOG.md. - Merging that PR creates the GitHub release and the tag
sdk-python-vX.Y.Z(same convention assdk-typescript-vX.Y.Z). - The tag is what a PyPI publish workflow
(
.github/workflows/release-sdk-python.yml) triggers on: it checks out the tag's tree, re-runs the full gate suite (ruff check,ruff format --check,pyright,pytest,gen_sync.py --check), builds withuv build, and publishes viapypa/gh-action-pypi-publishunder trusted publishing (OIDC, PEP 740 attestations included) — tokenless: the job'sid-token: writepermission is exchanged for a short-lived PyPI credential. The job skips cleanly if the version is already on PyPI, so a re-dispatch never fails on an already-published release.
A tag minted while the publish workflow was absent (or a tag whose run failed) is not replayed by a later push — re-dispatch the workflow against the existing tag instead (it takes the tag as a
workflow_dispatchinput), never publish by hand: a hand publish would need an API token, which the pending-publisher bootstrap below exists to avoid.
One-time bootstrap — unlike npm, PyPI supports pending publishers: the trusted publisher is registered before the first upload and CI does the first publish, so there is no local bootstrap publish and no API token at any point:
- On pypi.org → account → Publishing → "Add a new pending publisher"
(GitHub): PyPI project name
arcbox, ownerarcboxlabs, repositoryarcbox, workflow filenamerelease-sdk-python.yml, environment left empty. Empty is deliberate: PyPI calls the environment "optional but strongly recommended", but its value is the protection rules an environment can carry (required reviewers gating a publish), and this repo configures none — naming one today would add a label, not a gate. Add the environment and a matchingenvironment:key in the workflow together with the reviewer rule, not before. - The first tag-triggered run then creates the
arcboxproject on PyPI as it publishes, and the pending publisher becomes the project's regular trusted publisher.
A pending publisher does not hold the name: PyPI "does not create a
project or reserve a project's name until it is actually used to
publish",
and if someone else registers arcbox first the pending publisher is
invalidated. arcbox is short, generic, and still unclaimed — do the
first publish promptly after registering, and re-check the name is free
if the bootstrap has been sitting for a while.
Status
Phase 1 of CORE-58 — the hello-world closed loop: Sandbox /
AsyncSandbox create/connect/list, kill/pause/info (pause and
the paused-sandbox reconnect path are wire-complete but reject with an
unimplemented error until the daemon's CORE-21 lands), commands.run
(foreground result + background handle with streamed output,
wait_for_exit, kill), and whole-file files read/write. Deferred:
PTY, ports, wait_for_port/wait_for_log, stdin, filesystem path
verbs (stat/list/mkdir/...), Template statics, events(),
set_lifecycle, the capabilities handshake, and the SDK-side default
idle-reaping policy (design decision 4 — applied once the daemon
enforces the lifecycle knobs).
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file arcbox-0.1.1.tar.gz.
File metadata
- Download URL: arcbox-0.1.1.tar.gz
- Upload date:
- Size: 52.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf0af5ad64c901177474568623a68333118ca6e7592de5ec56466c2fa50a2f59
|
|
| MD5 |
4388b63503271d865602816d07bfe4ad
|
|
| BLAKE2b-256 |
604e428234417c7131e1a7e65cc489ecbb2a51c1738d950c62ca1447e816ba91
|
Provenance
The following attestation bundles were made for arcbox-0.1.1.tar.gz:
Publisher:
release-sdk-python.yml on arcboxlabs/arcbox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
arcbox-0.1.1.tar.gz -
Subject digest:
cf0af5ad64c901177474568623a68333118ca6e7592de5ec56466c2fa50a2f59 - Sigstore transparency entry: 2386580774
- Sigstore integration time:
-
Permalink:
arcboxlabs/arcbox@c5865a9fea813851fd48e7ade5abf22e1726fe49 -
Branch / Tag:
refs/tags/sdk-python-v0.1.1 - Owner: https://github.com/arcboxlabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-sdk-python.yml@c5865a9fea813851fd48e7ade5abf22e1726fe49 -
Trigger Event:
push
-
Statement type:
File details
Details for the file arcbox-0.1.1-py3-none-any.whl.
File metadata
- Download URL: arcbox-0.1.1-py3-none-any.whl
- Upload date:
- Size: 69.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7bfc3f1435b039b8b63cc341a7028dd076fd56a1e04e8b9265bc238c66f61218
|
|
| MD5 |
49714eece3a8a796a3054c8466db5eed
|
|
| BLAKE2b-256 |
6c6f8a84a29aea9b647b41c87eb67bd6bc4d4222b3632e2a7f7589824c0b8561
|
Provenance
The following attestation bundles were made for arcbox-0.1.1-py3-none-any.whl:
Publisher:
release-sdk-python.yml on arcboxlabs/arcbox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
arcbox-0.1.1-py3-none-any.whl -
Subject digest:
7bfc3f1435b039b8b63cc341a7028dd076fd56a1e04e8b9265bc238c66f61218 - Sigstore transparency entry: 2386580795
- Sigstore integration time:
-
Permalink:
arcboxlabs/arcbox@c5865a9fea813851fd48e7ade5abf22e1726fe49 -
Branch / Tag:
refs/tags/sdk-python-v0.1.1 - Owner: https://github.com/arcboxlabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-sdk-python.yml@c5865a9fea813851fd48e7ade5abf22e1726fe49 -
Trigger Event:
push
-
Statement type: