sandkiln
Client SDK for sandkiln — a compute primitive for safely running untrusted or AI-generated code in hardware-isolated Firecracker microVMs. Each sandbox is a real microVM: its own kernel, its own filesystem, its own network.
This package is the client. It talks to a sandkilnd daemon over HTTP —
you need one running somewhere reachable (see the main repo for how to
run one; there is no hosted service). Zero runtime dependencies —
urllib from the standard library is all it needs.
Install
Not published to PyPI yet — install from this repo:
pip install ./packages/python
Usage
from sandkiln import Sandbox
sandbox = Sandbox.create(tags={"env": "ci"})
result = sandbox.run_command("python3", ["analyze.py"])
print(result.stdout, result.exit_code)
sandbox.write_file("/tmp/config.json", '{"ok": true}')
data = sandbox.read_file("/tmp/config.json")
running = Sandbox.list(tags={"env": "ci"})
sandbox.stop()
Configuration
- Daemon URL: pass
base_urltoSandbox.create()/Sandbox.list(), or setSANDKILN_DAEMON_URL. Defaults tohttp://127.0.0.1:7777. - Auth: pass
auth_token, or setSANDKILN_AUTH_TOKEN, if the daemon hasSANDKILN_AUTH_TOKENset. Omit entirely for an unauthenticated local daemon.
API
Sandbox.create(name=None, tags=None, base_url=None, auth_token=None, vcpu_count=None, mem_size_mib=None, image_id=None)— boots a sandbox.nameis a caller-given identity, unique among live sandboxes and held snapshots (409 if already taken) — seeby_name/get_or_createbelow to find it again later.vcpu_count/mem_size_miboverride the daemon's configured defaults for this one sandbox, subject to the daemon's configured ceiling.image_idboots from a registered image (seeImage.registerbelow) instead of the daemon's configured default rootfs.Sandbox.attach(id, base_url=None, auth_token=None)— wraps an existing sandbox id without a network round-trip.Sandbox.by_name(name, base_url=None, auth_token=None)— resolves a name to a live sandbox and returns a handle to it. RaisesSandkilnApiError(409) if the name currently belongs to a stopped (snapshotted) sandbox instead — useget_or_createif you want that resumed automatically.Sandbox.get_or_create(name, tags=None, base_url=None, auth_token=None, vcpu_count=None, mem_size_mib=None)— resolvesnameto a sandbox in one race-safe call: a live sandbox with this name is returned as-is, a stopped one is resumed, otherwise a fresh one is created and given this name. Returns(sandbox, created).Sandbox.list(tags=None, base_url=None, auth_token=None)— lists sandboxes;tagsfilters by exact match on every given key.sandbox.run_command(command, args=None)— returns anExecResult(stdout,stderr,exit_code).sandbox.read_file(path)— returns file contents asbytes.sandbox.write_file(path, content)—contentisstrorbytes.sandbox.preview_url(port, path="/")— the URL a browser can open directly to reach a server listening onportinside this sandbox, proxied through the daemon.sandbox.stop(keep=None)— stops the sandbox. By default (keepomitted orTrue) this preserves its state as a resumable snapshot, same assnapshot(), and returns aStopResult(kept, snapshot_id). Passkeep=Falsefor the old "just destroy it" behavior — no snapshot, nothing left to resume.sandbox.snapshot()— saves the sandbox's full state to disk and stops it; returns a snapshot id. The daemon can also do this on its own, for an idle sandbox, if the operator hasSANDKILN_AUTO_SUSPEND_TIMEOUT_SECSconfigured — seeSandbox.list_snapshotsbelow for how to notice it and find the resulting snapshot.Sandbox.resume(snapshot_id, base_url=None, auth_token=None)— boots a new sandbox from a snapshot, consuming it (the snapshot is gone afterward).Sandbox.fork(snapshot_id, base_url=None, auth_token=None)— boots a new sandbox from a snapshot without consuming it, so it can be forked or resumed again later. Only one live fork of a given snapshot may run at a time — a second concurrentfork()raisesSandkilnApiErrorwith status 409 until the first is stopped; seeROADMAP.md's "Persistence and snapshotting" section for why.Sandbox.list_snapshots(source_sandbox_id=None, base_url=None, auth_token=None)— lists snapshots.source_sandbox_idnarrows this to the (at most one) snapshot taken from that original sandbox id — the way to find out whether a sandbox id that dropped out ofSandbox.list()turned into a snapshot (via a manualsnapshot()or the daemon's auto-suspend) and what its new id is.Image.register(id, path, base_url=None, auth_token=None)— registers an already-built ext4 rootfs file atpathon the daemon's own host filesystem underid, forSandbox.create(image_id=...)to boot from. Not a file upload — the daemon can't verify the guest agent is baked in without root access to loop-mount it (ImageInfo.guest_agent_verifiedis alwaysFalse); runscripts/preflight-check.sh --root-checks --rootfs-image <path>out of band first.Image.list(base_url=None, auth_token=None)/Image.delete(id, base_url=None, auth_token=None)— list registered images, or delete one (refused with 409 while any live sandbox, in-flight boot, or held snapshot still references it).
This mirrors the JS/TS SDK
exactly — same daemon, same operations, Python-idiomatic naming
(run_command not runCommand, snake_case fields).
Status
This SDK matches the daemon's current HTTP API exactly — no more, no less. Still open: publishing to PyPI, streamed command output, and attaching persistent drives at create time (supported by the daemon and CLI, not yet exposed here); see the roadmap.
License
MIT
Metadata
Release files for sandkiln 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sandkiln-0.2.0.tar.gz | 22.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sandkiln-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 48.9 kB
Release files / sandkiln-0.2.0.tar.gz
| Download URL | sandkiln-0.2.0.tar.gz |
|---|---|
| Size | 22.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
875f9bfd0cd0599ae356d983992637babab4d0cbeb5b9dbec04a0eafa2428cf2
|
|
BLAKE2b-256 checksum How to use checksums |
c2bcc72bac1ba761737318dada80115ee00625c4c5a9e873c70ce7ff75eb9db7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logRelease files / sandkiln-0.2.0-py3-none-any.whl
| Download URL | sandkiln-0.2.0-py3-none-any.whl |
|---|---|
| Size | 26.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9557e0e693cdbc88ca96a40a9ad4917d5ac6f52c2569caee7e5cf02e1aa7c6e8
|
|
BLAKE2b-256 checksum How to use checksums |
2497b4f6b9fb2fbfd1dfc61035657d401235bd81dbb123ff3f4d9614e22a9111
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency log