Every real change,nothing rounded up.
Tracks notable changes across the whole project (daemon, core crates,
clients). The JS/TS SDK is published as sandkiln on
npm, the CLI as
sandkiln-cli (installs
the kiln command), and the Python SDK as sandkiln on
PyPI — each versioned
independently, since a Python-only or CLI-only change doesn't require
bumping the others. The daemon and core crates don't have their own
release yet; where a change only affects one of those, it's called out
explicitly instead of implying it shipped to a package registry. Format
loosely follows Keep a Changelog.
Daemon & core — 2026-10-07
Covers every daemon/sandkiln-vmm/sandkiln-guest-agent/sandkiln-protocol
change since the dated section below. Full narrative in ROADMAP.md's "Dev
servers and live preview" section and the engineering notebook.
Added
- Local tunnel: a new
TUNNEL_PORTvsock port, the one deliberate exception to this project's "host always connects in" invariant — the guest agent dials out to the host using Firecracker's guest-initiated- connection mechanism (a pre-bound<uds_path>_<port>Unix socket, not theCONNECT <port>handshake the other three ports use). NewRequest::StartTunnel/StopTunnel(sandkiln-protocol), a guest-side TCP listener + relay (sandkiln-guest-agent::tunnel), a host-side acceptor dispatching bytunnel_id(sandkiln-vmm::tunnel), andPOST/DELETE /sandboxes/:id/tunnel+ a multiplexingGET .../tunnel/:tunnel_id/ws(sandkiln-daemon::routes_tunnel). See ROADMAP's "Dev servers and live preview" section and the Internals page for the full design.
Daemon & core — 2026-09-16
The daemon and core crates don't have their own release train (see the
note above), so everything below is dated instead of versioned — it
covers every daemon/sandkiln-vmm/sandkiln-store change since the last
SDK/CLI release (0.7.0, 2026-09-08). None of it needed an SDK, CLI, or
package-registry change unless a line says otherwise. Full narrative for
any of this lives in ROADMAP.md's matching section (linked per entry);
this list stays terse on purpose.
Fixed
POST /sandboxesreturning200doesn't mean the guest agent is listening yet: a cold sandbox's real firstexecmeasured ~420-460ms (every exec after: ~3-5ms) — a gap no existing benchmark caught, since they all stopped atInstanceStartsucceeding. A pre-warmed pool's resumed sandbox pays ~4-18ms instead: a real ~25-100x win, not the ~2x previously measured. Also fixed the same fixed-sleep bug inVm::call/open_pty/open_exec_stream's retry loops via one sharedretry_with_backoffhelper. See ROADMAP's Benchmarking section.wait_for_socketpolled Firecracker's API socket with a fixedsleep(20ms)on every single boot; replaced with a real connect-retry.Vm::boot34.00ms → ~11.3ms, cold create 167.65ms → ~144ms (~14% faster). 300/300 integration suite.- Corrected two earlier benchmarking conclusions after a full per-phase profiling pass: the network lease (~4.4ms) and Firecracker's config-PUT calls (~1.6ms) were never the bottleneck, and neither was a non-CoW filesystem — the rootfs clone is the actual dominant cost (74% of a cold create). A device-mapper/CoW-filesystem layer is no longer planned for this reason. See ROADMAP's Benchmarking section for the full corrected story.
egress::applybatched from ~9 sequentialiptablesspawns into oneiptables-restorecall: ~8.3ms → ~3.1ms average per policy-bearing create. Newegress_applyCreatePhasemetric makes this cost visible going forward.scripts/integration-test.sh's topic files now run in parallel (default 4 at a time,SANDKILN_INTEGRATION_TEST_PARALLELISM=1for the old fully-sequential behavior) instead of one shared sourced shell — each topic gets its ownWORKDIR/counters/tracked-resource arrays, and the untracked-checkpoint history sweep moved to run once, after every topic finishes, instead of once per topic (racing another still-running topic's own checkpoints otherwise).sandkiln-store's sqlite history write used sqlite's defaultjournal_mode=DELETE/synchronous=FULL(fsyncs twice per write). Switched tojournal_mode=WAL/synchronous=NORMAL: an isolated A/B on the same disk measured 1897.8µs → 37.1µs mean per write, a ~51x cut.
Added
- Per-phase cold-create timings:
/metricsgainscreate_phase_duration_ms{phase="rootfs_clone"|"network_lease"|"setup"|"egress_apply"|"total"}.scripts/dev-tools/profile-cold-create.shdrives a repeatable run. - Streamed background exec sessions (
kiln sandbox exec-stream/logs,Sandbox.execStream()/.listExecStreams()/.attachLogs()in the JS/TS SDK — not published under a new npm version yet). Any number of callers can attach to one running/finished command's output over time, each getting a full replay then a live tail. See ROADMAP's "Dev servers and live preview" section. - Remote storage mounts (daemon HTTP API only so far):
POST/GET/DELETE /sandboxes/:id/mountsmounts an S3-compatible bucket viarclone mountinside the guest. Needed a custom guest kernel (CONFIG_FUSE_FS, not in Firecracker's stock kernels) and a statically-linkedrclone+fusermount3baked into the rootfs. See ROADMAP's "Drives and remote storage" section. - Time-travel restore (daemon HTTP API only so far):
POST /snapshots/:id/resumeretires its checkpoint intoGET /snapshots/historyinstead of deleting it by default, restorable again viaPOST /snapshots/history/:id/restore, reclaimable viaDELETE /snapshots/history/:id. Found and fixed a real pre-existing bug along the way:forkshared its source's rootfs file directly instead of a private copy, corrupting state across two sequential forks of the same snapshot. See ROADMAP's "Persistence and snapshotting" section, including the real unbounded-disk-growth cost this surfaced. - Snapshot lineage:
Snapshot.parent_snapshot_idplus a?parent_snapshot_id=filter, walking a full resume/fork lineage tree in either direction. See ROADMAP's "Persistence and snapshotting" section. - Per-sandbox egress (outbound) policy (daemon HTTP API only so far):
egress: {mode, allow_cidrs, deny_cidrs}onPOST /sandboxes, enforced via one iptables chain per sandbox, deny always beating allow by rule order. Survives snapshot/resume/fork. See ROADMAP's "Firewall and egress policy" section. - Tiered idle lifecycle, archive tier:
SANDKILN_ARCHIVE_TIMEOUT_SECSmoves a held snapshot's memory/state files to a separateSANDKILN_ARCHIVE_DIRonce it's sat unresumed that long. See ROADMAP's "Persistence and snapshotting" section. - Pre-warmed pools:
max_countceiling with queueing — a claim at capacity queues (event-driven, not polled) for up to 30s instead of rejecting outright or silently exceeding the ceiling. Also added to the Python SDK (Pool.create/list/delete, not published to PyPI under a new version yet). - Guest-accessible metadata service: every named/tagged sandbox serves
{id, name, tags}via Firecracker's native MMDS. See ROADMAP's "Tags and sandbox metadata" section. - Durable sandbox history:
GET /sandboxes/history, backed by a newsandkiln-storesqlite crate, surviving a daemon restart (live sandboxes themselves still don't). See ROADMAP's "Tags and sandbox metadata" section. - PTY WebSocket sessions now covered by
scripts/integration-test.shitself, plus a fix for an orphaned-shell-on-client-disconnect bug. - Two new reference examples verifying real distinctions live against
the daemon:
examples/snapshot-lifecycle(fork vs. resume) andexamples/named-persistent-sandbox(persistent-by-default stop across two separate process runs).
sandkiln (Python) [0.3.0] — 2026-10-07
Added
- Local tunnel:
Sandbox.tunnel(guest_port, local_port)andAsyncSandbox.tunnel(...)— the reverse ofpreview_url. This package's first WebSocket-based feature: since the standard library has no WebSocket client,tunnel.py/atunnel.pyhand-roll the RFC 6455 handshake and frame masking directly (verified against the RFC's own published test vector), matching this SDK's existing zero-runtime- dependency design.
sandkiln (Python) [0.2.0] — 2026-09-16
Added
AsyncSandbox/AsyncDrive/AsyncImage/AsyncPool—asyncio-native mirrors ofSandbox/Drive/Image/Pool, same methods, built on a hand-rolledasyncio.open_connection-based HTTP client (_http_async.py) rather than a new dependency. Separate classes from the sync ones by design, not a mixed sync/async API on one class — seepackages/python/AGENTS.md.
sandkiln (Python) [0.1.0] — 2026-09-15
First PyPI release (pip install sandkiln) — the package itself has
mirrored the JS/TS SDK's full surface for a while (see the [0.7.0]
entry below and earlier), this just ships it to PyPI via
.github/workflows/publish-python-sdk.yml's OIDC trusted-publishing
flow. No code change; versioned separately from the JS/TS SDK/CLI table
below from here on, since the two don't need to move in lockstep.
[0.12.0] — 2026-10-07
Added
- Local tunnel:
Sandbox.tunnel(guestPort, { localPort })(JS/TS),kiln sandbox tunnel <id> <guest-port> --local-port <port>(CLI) — the reverse of dev-server preview, exposing a service on the caller's own machine to code running inside a sandbox. Newexamples/local-tunnelreference project. See the daemon/core section above for the guest-initiated-vsock mechanism this is built on.
[0.11.0] — 2026-09-16
Added
- Per-sandbox egress (outbound network) policy, now exposed in both SDKs
and the CLI (the daemon HTTP API itself shipped earlier — see the
dated daemon/core section above):
Sandbox.create({ egress })/.getOrCreate({ egress })(JS/TS),egress_mode/egress_allow_cidrs/egress_deny_cidrskwargs (Python),--egress-mode/--allow-cidr/--deny-cidr(CLI). Newexamples/egress-policyreference project.
[0.10.0] — 2026-09-16
Added
- Remote storage mounts, now exposed in both SDKs and the CLI (the
daemon HTTP API itself shipped earlier — see the dated daemon/core
section above):
Sandbox.mount()/.listMounts()/.unmount()(JS/TS),mount()/list_mounts()/unmount()(Python),kiln sandbox mount|mounts|unmount(CLI).examples/remote-storage-mountupdated to use the real methods instead of rawfetch()calls.
[0.9.0] — 2026-09-16
Added
- Per-exec/per-create environment variables:
env: {key: value}onPOST /sandboxes/get-or-createis baked in for a sandbox's whole lifetime;exec/exec-streameach accept their ownenv, merged on top with the per-call value winning on a key conflict.Sandbox.create({env})/.runCommand(cmd, args, {env})/.execStream(cmd, args, {env})(JS/TS SDK), matchingenv=kwargs (Python SDK —create()/get_or_create()/run_command(), noexecStream()there yet),--env key=value(repeatable) onkiln sandbox create|get-or-create|exec|exec-stream(CLI). Persists through snapshot/resume/fork liketags. SeeROADMAP.md's Client SDKs section.
[0.8.0] — 2026-09-16
Added
- Streamed background exec sessions:
POST /sandboxes/:id/exec-streamstarts a detached command inside a sandbox and returns a session id immediately;GET /sandboxes/:id/exec-streamlists sessions;GET /sandboxes/:id/exec-stream/:id/logs(WebSocket) attaches, replaying everything captured so far then live-tailing — any number of callers can attach over time, including after the command has finished, and each gets the identical output.Sandbox.execStream()/.listExecStreams()/.attachLogs()(JS/TS SDK),kiln sandbox exec-stream <id> -- <command> [args...]/kiln sandbox logs <id> [session-id](CLI). SeeROADMAP.md's "Dev servers and live preview" section for the full design. - Pool
max_count: an optional ceiling on total (warm + claimed) instances for a pool. A claim past it queues instead of cold-creating unbounded, waking the instant a slot frees rather than polling, and returns a real503after 30s if nothing frees up in time.
Changed
sandkiln-cli's dependency onsandkilnbumped to match this release — both are published together going forward when either one's API surface changes.
[0.7.0] — 2026-09-08
Added
- Pre-warmed snapshot pools:
POST/GET /pools,DELETE /pools/:id(daemon),Pool.create/list/delete(JS/TS SDK),kiln pool create|ls|rm(CLI). A plainPOST /sandboxes/Sandbox.create()(nodrives, norateLimit) matching a configured pool's image/resources transparently resumes a warm snapshot instead of cold-booting, once the background replenisher has one ready — no separate "create from pool" call. Live-verified with a real measured win on a clean claim (roughly 70–200ms vs. a cold create's ~160–200ms) — but that clean case is honestly not the reliable common case on this dev box today: seeROADMAP.md's "Persistence and snapshotting" section for two real Firecracker/KVM findings this surfaced and exactly how each is handled — a guest-kernel-panic-on-resume failure mode measured at roughly 1-in-3 to 2-in-3 resumes (not rare), handled with a real post-resume health check that falls back to a normal cold create rather than ever handing back a broken sandbox, and MMDS not surviving snapshot/restore. 19 newscripts/integration-test.shchecks (18-pool.sh), 234/234 passing overall.
[0.6.0] — 2026-09-08
Added
- Interactive terminal access:
Sandbox.pty()(JS/TS SDK, returns a nativeWebSocket) andkiln sandbox pty <id>(CLI, raw terminal mode) open a live, bidirectional shell session inside a sandbox overGET /sandboxes/:id/pty, distinct from batchexec. A newexamples/interactive-terminalreference project replaces ad hoc testing for this one. Live-verified end to end on real hardware, including a real bug found and fixed during that verification: the guest agent originally left one side of a disconnected session blocked forever (or, in the other direction, an orphaned shell process running with nothing attached to it) — fixed with an explicit socket shutdown and aSIGHUPto the shell's process group, matching real terminal hangup semantics. SeeROADMAP.md's "Dev servers and live preview" section for the auth (?token=), the per-sandbox 64-session cap, and the no-live-resize caveat.
[0.5.0] — 2026-09-08
Added
- Persistent drives exposed in both SDKs and the CLI for the first time
(
Drive.create/list/delete, attach at create time viadrives/--drive <id[:ro]>) — previously daemon-HTTP-API-only. - Full filesystem operations:
chmod/chown/mkdir/rename/copy/symlink/readlink/truncate/directory listing with metadata, as new vsock protocol commands plus daemon routes, both SDKs, and the CLI. Also exposeslist_dir, which existed in the protocol/guest agent already but was never wired up above that layer. Live-verified end to end after rebuilding and re-injecting the guest agent: 21 newscripts/integration-test.shchecks, 193/193 passing — seeROADMAP.mdfor exactly what's covered.
Changed
- Reorganized
scripts/: one-time host-provisioning steps (create-tap-pool.sh,grant-net-admin.sh,install-firecracker.sh,allow-passwordless-cap-grant.sh,start-dns-proxy.sh) moved intoscripts/host-setup/; narrow manual-debugging tools (boot-test-vm.sh,setup-tap-network.sh) moved intoscripts/dev-tools/. Every internal call site and doc reference was updated.preflight-check.sh,setup.sh,sandkilnd-ctl.sh,install-systemd-service.sh(+ its template),integration-test.sh,load-test.sh,remote.sh, and the newdev.shdispatcher all stay at the top level — no code deleted, purely a reorganization. Action needed on any host that already ranallow-passwordless-cap-grant.shunder its old path: that sudoers rule is keyed to the exact old path and won't match the new one — re-runsudo scripts/host-setup/allow-passwordless-cap-grant.shonce, interactively, to restore passwordlessCAP_NET_ADMINgranting on daemon restart. Verified live on the dev box after applying that fix:sandkilnd-ctl.sh restartgrants the capability cleanly again, and a fullscripts/integration-test.shrun withSANDKILN_AUTH_TOKENset passes 167/167 (154 base + 8 rate-limit + 5 auth checks).
[0.4.0] — 2026-09-08
Added
- Per-sandbox I/O rate limiting via Firecracker's own token-bucket rate
limiter:
POST /sandboxes(andget-or-create) accepts an optionalrate_limit: {bandwidth_bytes_per_sec?, ops_per_sec?}, applied uniformly to the rootfs drive, every attached drive, and both directions of the network interface. At least one sub-field must be set and non-zero ifrate_limitis present at all —0or an empty object is rejected with400, the same convention asvcpu_count/mem_size_mib.None/omitted means unlimited host I/O, unchanged from before this existed. Both SDKs (rateLimit/rate_limit_bandwidth_bytes_per_sec+rate_limit_ops_per_sec) and the CLI (--rate-bandwidth/--rate-opsonkiln sandbox createandget-or-create) are updated to match. Not yet exposed as a standalone daemon-level ceiling/floor — seeROADMAP.md's Security hardening section.
[0.3.0] — 2026-09-01
Custom/managed images, named sandboxes, persistent-by-default stop, read-only shared drives, and auto-suspend on idle — all new, backward-compatible additions, nothing removed or changed.
Added
- Custom/managed base images:
POST /imagesregisters an already-built ext4 rootfs from a host path (GET /imagesto list,DELETE /images/:idrefused while anything still references it);POST /sandboxesaccepts animage_idto boot from one instead of the daemon's default rootfs. The daemon can't verify the guest agent is baked in (runs unprivileged, no loop-mount) —scripts/preflight-check.sh --root-checks --rootfs-image <path>does that check out of band. Both SDKs (Image.register/list/ delete,imageId/image_idoncreate) and the CLI (kiln image ls|create|rm,kiln sandbox create --image) are updated to match. - Named sandboxes and persistent-by-default stop:
DELETE /sandboxes/:idnow auto-snapshots on stop by default instead of destroying (?keep=falseorkiln sandbox rm --destroyopts back into the old hard-destroy behavior); sandboxes can carry a caller-givenname(unique among live sandboxes) viaPOST /sandboxes, resolved withGET /sandboxes/by-name/:name(live only) or resumed/created in one race-safe call withPOST /sandboxes/get-or-create. Both SDKs and the CLI (--name,sandbox get-or-create,sandbox get,rm --destroy) are updated to match. - Auto-suspend on idle (
SANDKILN_AUTO_SUSPEND_TIMEOUT_SECS): an idle sandbox is paused and snapshotted instead of destroyed, freeing its VM/network while keeping it resumable.SANDKILN_IDLE_TIMEOUT_SECS(plain destroy) still works as before and now acts as a backstop when both are set (must be strictly longer).GET /snapshots ?source_sandbox_id=<id>and matching SDK/CLI methods find what a vanished sandbox turned into. - Read-only shared drives: a drive attached read-only may now be attached
to arbitrarily many sandboxes (and held snapshots) concurrently; any
read-write attachment, existing or requested, still needs exclusive
access. API shape change:
GET /drives/POST /drives'sattached_tofield changed fromOption<String>to a list of holders (each with aread_onlyflag) — not yet published to any SDK, so no external break, but worth knowing if you're consuming the daemon's raw HTTP API directly.
Fixed
scripts/integration-test.shgrew 65 checks covering all of the above (154 total, up from 89) — running it for real against live hardware after this release caught two genuine bugs, both fixed: five of the script's ownDELETE /sandboxes/:idcalls predating persistent-by- default stop still expected the old full-destroy response and needed an explicit?keep=false; andPOST /sandboxes/get-or-createwith nonamefield returned an undocumented422instead of a clean400(missing#[serde(default)]let axum's own JSON rejection fire before the daemon's validation ever ran).
[0.2.0] — 2026-08-30
resume/fork/snapshot/previewUrl and vcpuCount/memSizeMib
overrides — all new, backward-compatible additions to the SDK. First
publish of the CLI, as sandkiln-cli rather than the obvious kiln
(already an unrelated package on npm) — npm install -g sandkiln-cli
still gives you the kiln command.
Added
- Persistent drives (
POST /drives, attachable at sandbox creation), with cross-sandbox conflict detection and persistence across sandboxes verified live. - Snapshot/resume/fork: save a running sandbox's full state to disk and
boot a new sandbox from it, either consuming the snapshot (
resume) or not (fork, so the same state can be resumed repeatedly). Durable across a daemon restart (metadata persisted atomically, reconciled at startup). Exposed through both SDKs andkiln. - Host-side reverse proxy for dev-server preview
(
/sandboxes/:id/preview/:port), token-in-query-param auth for browser/iframe use,Sandbox.previewUrl()in both SDKs,kiln sandbox preview, and anexamples/dev-server-previewreference. - Per-sandbox resource overrides (
vcpu_count/mem_size_mibonPOST /sandboxes) with enforced, configurable ceilings. - Automatic idle-timeout reaper (
SANDKILN_IDLE_TIMEOUT_SECS). - Opt-in Firecracker jailer support (chroot, cgroup v2 limits, a
dedicated uid/gid per VM) via
SANDKILN_JAILER_ENABLED. - Request-id correlation (caller-supplied or generated
X-Request-Id) threaded through every VM operation an HTTP request triggers; a/metricsendpoint (Prometheus text format);SANDKILN_LOG_FORMAT=json; guest serial console captured to a per-VM log file. - Universal base rootfs image build (
images/build-universal-image.sh): Ubuntu, current Node.js/Python, common tooling, multi-agent isolation users, built reproducibly rather than a fetched test image. scripts/preflight-check.sh,scripts/sandkilnd-ctl.sh, andscripts/install-systemd-service.sh+ a real systemd unit template — a tested, reproducible self-hosting path, written up in full inSELF_HOSTING.md.kilnCLI first published to npm (assandkiln-cli), and the CLI/SDK publish workflows gained signed provenance.
Fixed
- Preview URLs 404ing on every SDK-generated URL: axum's
*pathwildcard doesn't match a bare trailing slash with nothing after it, which is exactly whatpreviewUrl()'s default path produces — added the missing explicit route. - Snapshots living only in memory, so a daemon restart made every existing snapshot's files permanently unreachable through the API even though the bytes were still on disk — fixed by persisting metadata atomically and reconciling it at startup.
[0.1.0] — 2026-08-26
First publish. The core execution primitive plus a full first pass of lifecycle, networking, auth, and tooling.
Added
- Core execution primitive: Firecracker microVM boot, a guest agent (vsock exec / read_file / write_file / list_dir), and a host-side vsock client, all proven end to end on real hardware.
- HTTP daemon (
sandkilnd) with full sandbox lifecycle: create, list, exec, stop. - Per-sandbox networking: tap device pool, shared bridge, NAT, and a DNS proxy — verified with concurrent sandboxes reaching the internet.
- Cross-sandbox network isolation on the shared bridge (bridge port isolation) — verified: sandboxes can't reach each other, gateway and outbound internet still work.
- Bearer-token authentication on the daemon's API (
SANDKILN_AUTH_TOKEN). - Sandbox tags: set at creation, filterable via
?tag.<key>=<value>. - File read/write endpoints (
POST /sandboxes/:id/read-file,/write-file), exposing the guest agent's existing file protocol through the HTTP API. - Structured observability:
tracingspans from the VM lifecycle layer up through HTTP request/response logging. - JS/TS SDK (
sandkilnpackage, first npm publish):Sandbox.create()/attach()/list()(tags, auth token),runCommand(),readFile()/writeFile(),stop(). ESM + CJS + full types — matches the daemon's entire HTTP surface, verified live against an auth-enabled daemon, published with signed provenance from the CI build. - Python SDK (
sandkiln, not published to PyPI yet): mirrors the JS SDK exactly, zero runtime dependencies (stdliburllib). Verified live end to end, includingattach()and correct 404 handling on a stopped sandbox. kilnCLI:sandbox create|ls|rm|exec|read|write, a thin wrapper over the SDK for manual/agentic use without writing code (published to npm in 0.2.0 — see above).criterionbenchmarks (boot time, exec latency) and a scripted concurrent load-test script against the daemon's HTTP API.scripts/integration-test.sh: a full end-to-end test suite against a real running daemon (89 checks as of this release), covering every HTTP route this changelog lists.AGENTS.md: onboarding doc covering the non-obvious gotchas hit and fixed during development, so they don't get repeated.- Project website (
website/), deployed via GitHub Pages on every push. - GitHub Actions: CI (build + clippy + SDK typecheck/build on every push) and a manual/tag-triggered npm publish workflow.
examples/code-playground(JS/TS) andexamples/agent-runner(Python) reference projects.
Fixed
- Ambient
CAP_NET_ADMINnot reaching Tokio's worker/blocking threads because#[tokio::main]starts the runtime before the capability was raised — restructuredmain()to raise it before entering the runtime. DELETE /sandboxes/:idreturning200with an empty body instead of the documented204, which crashed the SDK's response parsing — found via live integration testing against a real daemon.- Tap device creation via
ip tuntap addfailing under ambientCAP_NET_ADMIN(theTUNSETIFFioctl needs real root) — switched to a pre-created persistent tap pool, leased/released via netlink calls only, which do work under ambient capability. - A
pkill -fpattern that could match its own invocation and kill the wrong process (including, once, the SSH session running it). - A duplicate-command crash in the CLI (
program.command()already registers a command; a trailingaddCommand()re-added the same instance) — caught on the very first live run. - Sandbox creation latency: rootfs clone now runs concurrently with the
network lease instead of after it, and uses
cp --reflink=auto(free copy-on-write where the filesystem supports it). Measuredcreatelatency mean dropped 369ms → 211ms. - Python SDK import crash on Python <3.14 (
from __future__ import annotationsneeded — seeAGENTS.md), caught by CI on 3.12 even though local testing on 3.14 didn't hit it. - CI build order:
kilnneedssandkiln'sdist/built before it can typecheck, since it resolves it as a real workspace dependency.
Known gaps
Current as of 0.7.0 plus the unreleased work above — check ROADMAP.md
for anything that's landed since:
- No true simultaneous parallel snapshot forking — at most one live fork of a given snapshot at a time (see the Persistence section).
- No OCI/Docker-image conversion for custom images — only an already-built ext4 rootfs file.
- WebSocket proxying (dev-server HMR/live-reload) through the preview proxy isn't implemented — plain HTTP only.
- Jailer's actual chroot/cgroup/uid-drop behavior hasn't been proven against a real installed jailer binary on real hardware yet — opt-in, not recommended as-is for adversarial workloads until verified.
- Egress policy covers IP/CIDR allow/deny only. No domain-level rules and no port matching — both need the shared DNS proxy to become source-IP-aware first.
- No per-sandbox seccomp filters and no disk-size ceiling.
- Three daemon capabilities have no SDK or CLI wrapper yet, and are reachable only over the raw HTTP API: remote storage mounts, snapshot history / time-travel restore, and per-sandbox egress policy.
- Remote storage mounts additionally need a guest kernel built with
CONFIG_FUSE_FSandrclone/fusermount3in the rootfs; they are not re-applied after resume, fork, or restore; and they have been verified against a local S3-compatible fixture rather than a hosted object store. - Time-travel restore has no automatic expiry — retained history grows disk usage until deleted explicitly.
- On ext4 (no copy-on-write), sandbox creation still pays real rootfs copy time — needs a CoW-capable filesystem or a device-mapper layer to actually eliminate, not just overlap with other work.
- Snapshot storage lives under
$TMPDIR— durable across a daemon restart, not necessarily a host reboot (depends on whether/tmpis tmpfs on that host).