Repository navigation
Session workspaces (#23): a sandbox's /workspace can now outlive the VM that
wrote it, under a strict, creator-controlled TTL. This is the first release in
which "the assistant remembers things between calls" is a property of the engine
rather than of the model's context window.
Added
- Session volumes (#23):
sandbox_spec_t.session_keygives a sandbox a
persistent/workspace;ttl_secondssets its lifetime. Volumes are a
pre-formatted sparse ext4 image built with the template and cloned per session
(reflink where the filesystem supports it, sparse extent copy otherwise), keyed
bysha256(session_key)inSANDBOX_WORKSPACE_ROOT, created in a staging
directory andrename()d into place. A session is exclusive-use: a lease
prevents two VMs writing one image, and asession_keycreate bypasses the warm
pool because Firecracker cannot swap a block device after restore. - TTL policy (D5):
expires_at = created_at + clamp(ttl, 60 s, platform max),
default 7 days, maximum 30 days (SANDBOX_WORKSPACE_MAX_TTL). Absolute from
creation, never extended automatically. A reaper thread expires sessions on a
timer (SANDBOX_REAP_INTERVAL_SECONDS, default 60 s) and also collects
abandoned staging directories. - Renewal:
sandbox_session_renew(key, extra_seconds, &expires_at)and
sandbox-cli renew --session-key KEY [--extra SECONDS], plus
sandbox::renew_session()in the C++ SDK. Keyed by session, not by sandbox, so
a quiet conversation can be kept alive with nothing running.extra_secondsis
added to now, clamped to the platform maximum, never shortened; a past
deadline returnsSANDBOX_ERR_SESSION_EXPIRED(the volume is already gone), an
unknown keySANDBOX_ERR_SESSION_UNKNOWN, and a session already at the maximum
SANDBOX_ERR_SESSION_LIMIT. - Durable teardown: the engine asks the guest to
sync()before it SIGKILLs
the VMM (reservedSANDBOX_CONTROL=flushon the vsock control channel,
acknowledged withSANDBOX-FLUSH-OK, bounded bySANDBOX_FLUSH_MS). Without
it, a page written by the workload was still in the guest's page cache when the
VM died, and the next sandbox of the same session simply did not see the file. - In-guest persistence evidence: the guest agent appends a checksum of each
injection to/workspace/.sandbox-probeand reportsmounted=,lines=,
first=andlast=on the serial console, so the privileged e2e test proves
persistence from inside the guest instead of reading back a file the host wrote. - Workspace metrics and
docs/WORKSPACE_DISK.md(including the deviations
from the original D7 design and why they were made).
Fixed
- ext4 corruption after restore (
f1a2ba7): the guest mounted/workspace
before the template snapshot was captured, so every restore resumed a mounted
ext4 whose backing file had been replaced — the guest then reported
EXT4-fs error (device vdb): checksumming directory block 0. The volume is now
mounted by the agent on first use after each restore, which also makes
"a clone is byte-identical to its base" true by construction and lets ext4
replay the journal of a sandbox killed mid-write. - Leaked jail bind mount (
976299b):jail_cleanup()never unmounted
/workspace.ext4, so removing the jail hitEBUSY, the jail survived, and
later creates failed at[jailer] step 6 failed: errno=2. Unmount added, and
bind failures now name the source, the destination and whether the source
exists. - Latency probe segfault: the generated probe in
scripts/benchmark.shbuilt
itssandbox_spec_ton the stack without initialising it; the new pointer field
turned that into a wild dereference. Zero-initialised, and the contract is now
stated indocs/API.md. - Network test flake: the broker port was hard-coded to 8080 (a genuinely busy
port) and the drop verdict failed if any frame arrived, including a
retransmission from the previous connection. The port is now chosen per run and
the verdict is keyed to the connection under test. - Test isolation: the privileged suite now clears its test root before a run.
Re-runs on the same runner were inheriting the previous run's session volumes
(durable by design), which surfaced as a "new volume starts empty" failure.
Changed
SANDBOX_BROKER_PORT(default8080) is the single source of truth for the
port guests may reach on the host: the egress rules and the broker listener both
derive from it, so moving the broker off a busy port is now one variable instead
of two values that silently disagree.sandbox_spec_tgrew two fields. Callers must zero-initialise it
(sandbox_spec_t spec = {0};); an uninitialisedsession_keyis a crash, not a
default. All in-tree callers already did.
Verified
- Privileged e2e (self-hosted, KVM): all 7 suites pass, including the session
persistence test (create → write in guest → destroy → re-create → the file is
still there, with the pre-destroy line first) andtests/e2e/smoke.sh. - Restore latency on the reference host: 38.49 ms cold (provisioned),
55.76 ms average over five provisioned restores, budget 250 ms.
Artifacts: per architecture (x86_64, aarch64) — staged install tree, bare-binary archive, individual binaries and shared libraries, and SHA-256 checksums. Both built natively from this tag.
Verified on the privileged runner: all 7 privileged suites pass with zero skips, in-guest persistence across destroy/re-create, cold restore 38.49 ms / provisioned 55.76 ms against a 250 ms budget.
Upgrade note: 0.13.0 templates still load, but have no workspace — rebuild to use session_key. sandbox_spec_t grew two fields: zero-initialise it (= {0}).