Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
0773e39
pool: reuse setIfLive in archive and wake rollbacks, fold putReplica,…
CMGS Nov 6, 2026
6d01e8f
pool: never serve a pooled key from a promoted template while its gol…
CMGS Nov 6, 2026
1b32a82
pool: a clone cut off by engine shutdown never counts against the golden
CMGS Nov 6, 2026
e0abeae
pool: state the claim event ordering guarantee the code gives
CMGS Nov 6, 2026
3551eff
sdk: never resend a redirected claim whose request may have reached t…
CMGS Nov 6, 2026
bd5829a
pool: retire a reader whose claim woke or left even while a connectio…
CMGS Nov 6, 2026
7bff688
pool: a tenant branches only its own tenant's checkpoints
CMGS Nov 6, 2026
a8d23b2
pool: a wake hand-off whose peer adopted before its reply timed out a…
CMGS Nov 6, 2026
137e817
config: archive_failover requires mesh.cluster_key
CMGS Nov 6, 2026
e6300df
pool, docs: state why the egress lane refuses hibernate per the cocoo…
CMGS Nov 6, 2026
f2cf625
pool: record a checkpoint's tun state under the transition lock it ca…
CMGS Nov 6, 2026
3464794
sdk/go: Run returns a stdin read failure alongside the exit code
CMGS Nov 6, 2026
c78696f
sdk/go: bound each Lookup owner probe like the Python SDK
CMGS Nov 6, 2026
6b03c15
silkd: kill a language server by its process group
CMGS Nov 6, 2026
77d3510
e2e: release the claim on every driver error path
CMGS Nov 6, 2026
00d91ea
scripts: make archive-e2e.sh executable like its siblings
CMGS Nov 6, 2026
8f96957
review: mcp tool arg types above the handlers, parseCommand beside parse
CMGS Nov 6, 2026
154bde1
review: sdk fork/promote/renew wire types beside Sandbox, APIError be…
CMGS Nov 6, 2026
b6f3fac
wire: inline the response decoder func type, trim the encodeTagged cl…
CMGS Nov 6, 2026
f9be6e6
sdk: one newClaim for the option loop and one templateQuery for the t…
CMGS Nov 6, 2026
7bff188
sdk: probe peek compares with errors.Is
CMGS Nov 6, 2026
f97186e
silkdtest: use the wire kind constants, inline the single-use buffer …
CMGS Nov 6, 2026
ff6f73e
sdk: one-line godoc for one-sentence contracts, drop docs that repeat…
CMGS Nov 6, 2026
4d79374
e2e: drop the guestserver sink done flag
CMGS Nov 6, 2026
aec08c9
e2e: volumesmoke closes unconditionally, Close is idempotent
CMGS Nov 6, 2026
2cf2917
e2e: harness.Poll replaces six hand-rolled retry loops
CMGS Nov 6, 2026
4890606
docs: shorten the README intro to a pitch and link; go-test runs the …
CMGS Nov 6, 2026
064a82d
docs: state each shared fact once; correct meshsmoke, usage node and …
CMGS Nov 6, 2026
1c9bccc
docs: SDK differences, Python-only text out of the Go page, narrower …
CMGS Nov 6, 2026
5d61f8f
docs: kernel range of the bare-metal rows; e2b-ci builds from base
CMGS Nov 6, 2026
976d68d
docs: flavor READMEs keep build notes and link the guides
CMGS Nov 6, 2026
6a44ccf
docs: a checkpoint branches only for its own tenant
CMGS Nov 6, 2026
9dcec8d
docs: a request pipelined behind fs_watch ends the watch without a te…
CMGS Nov 6, 2026
07712d3
metastore: add CAS One and All, and route the pg registries and sourc…
CMGS Nov 6, 2026
e95908f
metastoretest: name the schema with rand.Text
CMGS Nov 6, 2026
554eab2
movestest: rename Contract to Run like archivestest
CMGS Nov 6, 2026
73dbd8a
ca: drop the unreachable second exists check behind writeCAFiles' stat
CMGS Nov 6, 2026
d642750
server: normalize the preview base once in NewPreviewServer
CMGS Nov 6, 2026
0fda44e
server: build the exec frame with slices.Concat
CMGS Nov 6, 2026
7bf6a31
server: fold relay registration, deadline reset, keepalive and the 10…
CMGS Nov 6, 2026
eb47d3b
server: answer a down tenant store through writePoolErr
CMGS Nov 6, 2026
eb26249
types: tag VolumePatch slices omitempty like the other slices
CMGS Nov 6, 2026
15be936
usage: drop read's godoc that restates the loop
CMGS Nov 6, 2026
cc53aaa
outbound: drop nil checks on Flow, TunBytes and TunState that outboun…
CMGS Nov 6, 2026
dca01a9
tunnet: fold adjust's carry with fold
CMGS Nov 6, 2026
4eaada4
tunnet: drop the unreferenced OffloadUSO6
CMGS Nov 6, 2026
90f2e90
egress, tunnet: drop comments that repeat a godoc or label a section
CMGS Nov 6, 2026
c6225c9
outbound: read resolv.conf with os.ReadFile and strings.Lines
CMGS Nov 6, 2026
97dcc69
outbound: cancel the resolver at its one caller
CMGS Nov 6, 2026
5fa2f4e
tunnet/pg: inline the single-caller scan and exec helpers
CMGS Nov 6, 2026
faa33b9
egress: intersect the pool and tenant decisions in one helper
CMGS Nov 6, 2026
fe47733
engine: map an unexpected silkd frame to its error in frameErr
CMGS Nov 6, 2026
b29a16a
engine: build clone and restore argv with slices.Concat
CMGS Nov 6, 2026
7960d3b
tunnet: join the freed-file close error with cmp.Or
CMGS Nov 6, 2026
52e9276
mesh: assign the secret key unconditionally
CMGS Nov 6, 2026
b0590a0
review: move DialFunc next to DialUpstream and lead probe.go's const …
CMGS Nov 6, 2026
3d655b3
pool: stamp audit frames with one generic wrapper
CMGS Nov 6, 2026
aba3c89
pool: name the template visibility predicate once
CMGS Nov 6, 2026
5e5c378
pool: drop a promote comment that restates WithoutCancel
CMGS Nov 6, 2026
a6062be
pool: drop a comment that restates the pools.json commit
CMGS Nov 6, 2026
04b2b6d
pool: build a pool from its spec in one call
CMGS Nov 6, 2026
af140b5
pool: state the pool lock rule once on the type
CMGS Nov 6, 2026
00c11f4
pool: create the tun release retry map with the manager
CMGS Nov 6, 2026
f7d2e00
pool: one logger in resolveMoveIn
CMGS Nov 6, 2026
090036b
pool: journal the move record with new(expr)
CMGS Nov 6, 2026
3fd32d8
pool: filter failed archive marks in the reap keep loop
CMGS Nov 6, 2026
a5bed27
pool: read the hypervisor statm under the manager proc root
CMGS Nov 6, 2026
ce1a53b
pool: drop the removeStaleVM godoc that restates its signature
CMGS Nov 6, 2026
f1c3173
silkd: borrow request fields in verbs that only read them
CMGS Nov 6, 2026
b03e260
silkd: share take_pipe for every piped child stdio handle
CMGS Nov 6, 2026
187ffa9
review: declare server.rs type aliases above the types that use them
CMGS Nov 6, 2026
d0752b1
silkd: drop crate paths that an import already covers
CMGS Nov 6, 2026
d614421
silkd: cut comments that restate the code or its callee's doc
CMGS Nov 6, 2026
33b126f
silkd: name the watch event queue depth
CMGS Nov 6, 2026
e9cdf96
boot/init: zip record_serial's slots with ids like record_devices
CMGS Nov 6, 2026
b3f42a2
boot/init: name the cmdline flag parser for both flags it reads
CMGS Nov 6, 2026
99f7062
sdk/python: close a client's parked control connections
CMGS Nov 6, 2026
8d22700
sdk/openai: hand the claimed sandbox to the new session instead of a …
CMGS Nov 6, 2026
022abe9
sdk/python: keep module-private names underscored and the retry statu…
CMGS Nov 6, 2026
8443ec4
sdk/python: drop the endpoint scheme parameter only tests passed
CMGS Nov 6, 2026
ba58c98
sdk/python: leave the callback rule to _deliver's docstring
CMGS Nov 6, 2026
a7a7be0
sdk/python: time the e2e steps and port deadline on the monotonic clock
CMGS Nov 6, 2026
5d6f63a
sdk/langchain: derive the exec tool's cutoff sentence from CALL_TIMEOUT
CMGS Nov 6, 2026
2685b9c
sdk/python: raise the floor to 3.10 and lint under the root ruff config
CMGS Nov 6, 2026
88ee478
sdk/openai: share the faked-session setup and merge the failed-constr…
CMGS Nov 6, 2026
4c1fbe8
sdk/python: fold copy-paste tests into parametrized cases and shared …
CMGS Nov 6, 2026
ae31167
sdk/langchain: parametrize the guest-error tool tests
CMGS Nov 6, 2026
b47e6a2
pool: make the consume-vs-unfence epoch race deterministic
CMGS Nov 6, 2026
ff15d26
pool: run the claim-beside-repromote test on the synctest clock
CMGS Nov 6, 2026
7636ee5
pool: order the racing lookup behind the wake adoption with synctest
CMGS Nov 6, 2026
760fa67
pool: assert the kept claims of an evacuation unconditionally
CMGS Nov 6, 2026
ce24e4f
pool: mustPromote helper for the successful promotes
CMGS Nov 6, 2026
ab7b95e
pool: pair, mustFlush and hookRegistry helpers for the failover tests
CMGS Nov 6, 2026
218fa1b
pool: evacuatingCalls and counts helpers in the move tests
CMGS Nov 6, 2026
a06818a
pool: one refused-adoption helper
CMGS Nov 6, 2026
0eee22a
pool: fold the persist-failure rollback tests into a table and share …
CMGS Nov 6, 2026
ef6b205
pool: shared test helpers for claims, leases, pending removals, polic…
CMGS Nov 6, 2026
af0df5e
review: move misplaced quota, reload and replication test helpers nex…
CMGS Nov 6, 2026
10a0a4d
tunnet/udp: fail from the test goroutine and drop the blind sleep in …
CMGS Nov 6, 2026
268a4a9
store/s3: wait for the second Fetch to join by its stack, make the bu…
CMGS Nov 6, 2026
0b0a5a8
egress: wait for the handler before asserting no transfer or audit
CMGS Nov 6, 2026
fd36ac7
mcp: gate the release race on a channel, checked type assertions, sha…
CMGS Nov 6, 2026
d17342b
tunnet: bind the test router to the test context and replace the slee…
CMGS Nov 6, 2026
945952f
store: hold the probe handlers on channels, share FileSum, AllocatedB…
CMGS Nov 6, 2026
7524203
engine: drop the sleep-ordered probe test, the retry loop is covered …
CMGS Nov 6, 2026
2c37638
server: run the dead-client splice test under synctest
CMGS Nov 6, 2026
1bd3f18
silkd tests: sync on marker files, shared helpers in common, tempdir …
CMGS Nov 6, 2026
ee3e012
ci: fail the Caddy TLS step when its test skips
CMGS Nov 6, 2026
dd19c23
e2e: fold the volume end-to-end and wire-shape pairs into tables, acc…
CMGS Nov 6, 2026
fd5b086
sandboxd: table the pool flag tests, shared CA fixtures and guest roo…
CMGS Nov 6, 2026
4ef2758
engine: shared port conn fixture size, countOf helper, drop the deter…
CMGS Nov 6, 2026
19b3b4d
outbound: one dial assertion helper, nameserver address as a string, …
CMGS Nov 6, 2026
fab533d
server: shared guest and frame fixtures, await helper for channel wai…
CMGS Nov 6, 2026
ba15949
review: move guestFunc beside the other function vocabulary types
CMGS Nov 6, 2026
de0047b
store/dir: assertGone and assertPresent for the on-disk checks
CMGS Nov 6, 2026
6e15957
tunnet: one overlay fixture
CMGS Nov 6, 2026
915ae33
types: build the volume fixtures from the limit, drop the bare mutex …
CMGS Nov 6, 2026
9fcb7f8
sdk/go: shared server builders, table the redirect fallback tests, co…
CMGS Nov 6, 2026
1e52432
store/storetest: drop the helper doc comments
CMGS Nov 6, 2026
e515cd6
sdk/go, storetest: match the server's VolumePatch tags and range the …
CMGS Nov 6, 2026
e3faff8
types, sdk/go: keep omitzero on VolumePatch so an explicit empty list…
CMGS Nov 6, 2026
87829fe
sdk/go: re-send stream calls whose request is their whole input after…
CMGS Nov 6, 2026
41e7d52
sdk: refuse a comma-separated seed list instead of silently keeping i…
CMGS Nov 6, 2026
afc37d2
silkd: hang up a pty shell whose client disconnects instead of killin…
CMGS Nov 6, 2026
5618c0b
silkd: open a pty with close-on-exec set from birth
CMGS Nov 6, 2026
0bc30cb
sdk/go: drop the named results the move retry wrappers shadowed
CMGS Nov 6, 2026
82607e5
sandboxd: name the printf-style dir test helpers and fill the router …
CMGS Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/sandboxd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,12 @@ jobs:

- name: Test TLS edge with Caddy
working-directory: e2e
shell: bash
env:
CADDY_BIN: ${{ runner.temp }}/caddy
run: |
curl -fsSL --retry 3 https://github.com/caddyserver/caddy/releases/download/v2.11.4/caddy_2.11.4_linux_amd64.tar.gz -o "$RUNNER_TEMP/caddy.tar.gz"
printf '%s %s\n' '8220d1f013b6f27510247b2360c9e0ca9f018feebd82515f07635318b34ff9777ccc8fd0b6e6f2486ce3a33fe389fbb7db12d05baa474f4587509fb4f5ebf1c9' "$RUNNER_TEMP/caddy.tar.gz" | sha512sum -c -
tar -xzf "$RUNNER_TEMP/caddy.tar.gz" -C "$RUNNER_TEMP" caddy
GOWORK=off go test -race -count=1 -run TestCaddyTLSCluster -timeout 60s -v .
GOWORK=off go test -race -count=1 -run TestCaddyTLSCluster -timeout 60s -v . | tee "$RUNNER_TEMP/caddy-test.log"
! grep -- '--- SKIP' "$RUNNER_TEMP/caddy-test.log"
18 changes: 3 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,8 @@ for Go and Python. Warm claims are sub-millisecond; a pool miss clones from a
golden snapshot in tens of milliseconds; cold boot is ~0.2–0.4 s on bare metal.

**Documentation: [cocoonstack.github.io/sandbox](https://cocoonstack.github.io/sandbox/)**
(source in [`docs/`](docs/)).

## Architecture

```
SDK (Go/Python) sandboxd (per node) guest microVM
client.New/new ─ HTTP ─► claim: warm pool / golden clone Cloud Hypervisor
exec/files/… ─ HTTP upgrade (kept) ─► byte relay ─ vsock ─► silkd :2048
memberlist mesh: warm-count gossip,
MOVED-style redirect to the owning node
```

Cloud Hypervisor serves both network lanes. `net=none` has no NIC and uses
vsock-only I/O (hardened default); `net=egress` attaches a bridge/CNI NIC.
(source in [`docs/`](docs/)); the architecture overview is in
[`docs/index.md`](docs/index.md).

## Quick start

Expand Down Expand Up @@ -140,7 +128,7 @@ The cmdline keys `sandbox-init` consumes are in the
make help # every target; the ones below are the daily set
make lint test # Rust: boot/init + silkd (fmt --check, clippy -D warnings, tests)
make go-lint # Go: protocol/wire + sandboxd + sdk/go + e2e + mcp, GOOS linux AND darwin
make go-test # Go: go test -race across the Go modules
make go-test # Go: go test -race plus one pass of every benchmark, across the Go modules
make sh-lint # shellcheck every tracked shell script
make sandboxd # build dist/sandboxd
make bench # claim-tier + data-plane benchmarks; removes every sbx-* VM
Expand Down
6 changes: 3 additions & 3 deletions boot/init/src/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,9 @@ fn record_devices(ids: &[&str], found: &mut [Option<String>]) {
}

fn record_serial(ids: &[&str], found: &mut [Option<String>], serial: &str, device: &str) {
for (i, id) in ids.iter().enumerate() {
if found[i].is_none() && *id == serial && Path::new(device).exists() {
found[i] = Some(device.into());
for (slot, id) in found.iter_mut().zip(ids) {
if slot.is_none() && *id == serial && Path::new(device).exists() {
*slot = Some(device.into());
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions boot/init/src/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ pub fn parse(cmdline: &str) -> Result<BootCfg<'_>, String> {
}
}
"cocoon.hostname" if !val.is_empty() => cfg.hostname = Some(val),
"sandbox.debug" => cfg.debug = debug_token(val),
"sandbox.trace" => cfg.trace = debug_token(val),
"sandbox.debug" => cfg.debug = flag_token(val),
"sandbox.trace" => cfg.trace = flag_token(val),
"ip" => {
if let Some(param) = parse_ip_param(val) {
cfg.ips.push(param);
Expand All @@ -85,7 +85,7 @@ pub fn parse(cmdline: &str) -> Result<BootCfg<'_>, String> {
pub fn debug_requested(cmdline: &str) -> bool {
params(cmdline)
.rfind(|&(key, _)| key == "sandbox.debug")
.is_some_and(|(_, val)| debug_token(val))
.is_some_and(|(_, val)| flag_token(val))
}

/// Overlay mount data; index-based layer mountpoints keep a serial string out of lowerdir parsing.
Expand Down Expand Up @@ -118,7 +118,7 @@ fn params(cmdline: &str) -> impl DoubleEndedIterator<Item = (&str, &str)> {
.map(|tok| tok.split_once('=').unwrap_or((tok, "")))
}

fn debug_token(val: &str) -> bool {
fn flag_token(val: &str) -> bool {
val.is_empty() || val == "1"
}

Expand Down
11 changes: 6 additions & 5 deletions docs/cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ Give every node a `mesh` block:
join mesh: … connection refused`), so two nodes that name only each other
cannot cold-start together: list the node's own `bind` alongside its peers
and every node comes up in any order, joining whoever is already there
- `cluster_key` — optional base64 key (16/24/32 bytes) enabling gossip
encryption; all members must share it
- `cluster_key` — base64 key (16/24/32 bytes) enabling gossip encryption;
all members must share it. Required by `archive_failover`,
`checkpoint_peer_heal` and live moves

Two constraints, fine for a homogeneous cluster: all nodes share the same
`api_token` and tenant set (the SDK replays whichever token authorized a
Expand Down Expand Up @@ -167,7 +168,7 @@ cap; to take no claims, cordon the node.

| State | New claim, fork, branch | Existing claims | Root live move in | Adoption of archived claims | Refill and pool gossip | Survives restart and reboot |
|---|---|---|---|---|---|---|
| serving | yes, under `max_claims`, `max_running`, the tenant's `max_claims`, `disk_reserve` and `memory_limit`; a 429 for a claim without volumes redirects to a peer | yes; a wake is bound by `max_running` and `disk_reserve`, and an archived claim's refused wake moves to a peer with room | yes, under the same caps and `move_concurrency` | yes; no claim cap binds, `max_running` binds at the wake | yes | — |
| serving | yes, unless an [admission refusal](sandboxd-api.md#admission-refusals) applies; a 429 for a claim without volumes redirects to a peer | yes; a wake is bound by `max_running` and `disk_reserve`, and an archived claim's refused wake moves to a peer with room | yes, under the same caps and `move_concurrency` | yes; no claim cap binds, `max_running` binds at the wake | yes | — |
| cordoned | no: 429 `node cordoned`; peers stop redirecting here | yes | yes, under the same caps and `move_concurrency` | no; its own claims stay | no; warm VMs are destroyed | yes, as `<data_dir>/cordoned` |
| draining | no: 429 `node draining`; peers stop redirecting here | yes | no: admission and commit refuse | no | no; warm VMs are destroyed | no |
| draining with `evacuate=1` | no, as draining | yes until the claim's turn, then 503 until a peer holds it and 404 after; a claim that cannot move stays and serves | no | no | no | no: a restart stops the evacuation |
Expand Down Expand Up @@ -695,8 +696,8 @@ that broadcast keeps its copy until the checkpoint TTL ages it out. A healed
replica carries the source checkpoint's original `CreatedAt`, so it becomes
eligible for expiry at the same instant everywhere; each node then removes
it on its own hourly sweep, which is independently phased and retries on
failure. The window in which a deleted checkpoint stays branchable by an
id-holder is therefore normally `checkpoint_ttl_hours` plus the wait for the
failure. The window in which a deleted checkpoint stays branchable by its
tenant is therefore normally `checkpoint_ttl_hours` plus the wait for the
next hourly sweep; a sweep that fails retries on a later one, so persistent
sweep failure extends retention until one succeeds — the TTL is the eligibility
point, not a hard ceiling. This is why heal *requires* a nonzero, fleet-matching
Expand Down
Loading
Loading