The sandbox daemon's surface is complete for request/response agent use, but everything a live console (or any subscriber) needs is missing: nothing is observable without polling, long output cannot stream, and several state fields never leave the daemon. These asks came out of building the sandbox console surface in iii-hq/workers#762 (plus #766/#767), each was hit in practice, and three now have a second confirmed consumer from live agent sessions.
Ranked by unblocked value:
- Lifecycle events on the bus — emit
created / ready / stopped / reaped (with reason) as a subscribable trigger. Today the reaper kills VMs invisibly. Workers#762 ships a watcher that polls sandbox::list server-side and re-emits change-only snapshots purely to fake this; that module deletes itself the day the daemon emits its own events.
- Streaming exec — stdout/stderr chunk channels on
sandbox::exec, plus a truncated flag on ExecResponse (the 1 MiB cap is currently silent). Consumers: live output tail in the console, and shell's sandbox-targeted background jobs, which today can only capture output at completion because they sit on one buffered exec.
- Exec cancel — no way to abort a running exec short of stopping the whole sandbox. A hung foreground command holds one of 4 slots for its full timeout. Confirmed twice: the console can only watch, and
shell::kill on a sandbox job flips its record but cannot reach the in-VM process.
- Idle visibility on
sandbox::list — idle_secs + idle_timeout_secs (or a computed reap_in_secs) per sandbox. The console ships a dormant TTL countdown that lights up when these fields appear. Client-side approximation is impossible: a viewer cannot see other actors' fs activity, which bumps the idle clock.
sandbox::describe — cpus, memory_mb, network on/off, wall-clock created_at. cpus/memory are dropped after boot today (not retained on the daemon's own state), and created_at is a monotonic Instant that cannot serialize.
- Real pull progress — the create-phase events exist but fire after the pull with
progress_bytes: 0; cold creates are 5 to 30 seconds of dead air. Byte-level progress during the pull enables an honest boot checklist.
- Honor
stop.wait — the field is accepted and ignored; callers cannot block on teardown.
- Config echo — effective
max_concurrent_sandboxes, image allowlist, and defaults are not queryable; exec_slots_free is the only limit that leaks out.
- Docs drift — README's
sandbox::list response documents fields that do not exist (status, created_at, last_exec_at), FsMatch is documented as {path, line_no, byte_offset, line} vs the actual {path, line, content}, and iii.worker.yaml says "14 triggers" vs the actual 16.
The sandbox daemon's surface is complete for request/response agent use, but everything a live console (or any subscriber) needs is missing: nothing is observable without polling, long output cannot stream, and several state fields never leave the daemon. These asks came out of building the sandbox console surface in iii-hq/workers#762 (plus #766/#767), each was hit in practice, and three now have a second confirmed consumer from live agent sessions.
Ranked by unblocked value:
created/ready/stopped/reaped(with reason) as a subscribable trigger. Today the reaper kills VMs invisibly. Workers#762 ships a watcher that pollssandbox::listserver-side and re-emits change-only snapshots purely to fake this; that module deletes itself the day the daemon emits its own events.sandbox::exec, plus atruncatedflag onExecResponse(the 1 MiB cap is currently silent). Consumers: live output tail in the console, and shell's sandbox-targeted background jobs, which today can only capture output at completion because they sit on one buffered exec.shell::killon a sandbox job flips its record but cannot reach the in-VM process.sandbox::list—idle_secs+idle_timeout_secs(or a computedreap_in_secs) per sandbox. The console ships a dormant TTL countdown that lights up when these fields appear. Client-side approximation is impossible: a viewer cannot see other actors' fs activity, which bumps the idle clock.sandbox::describe— cpus, memory_mb, network on/off, wall-clock created_at. cpus/memory are dropped after boot today (not retained on the daemon's own state), and created_at is a monotonic Instant that cannot serialize.progress_bytes: 0; cold creates are 5 to 30 seconds of dead air. Byte-level progress during the pull enables an honest boot checklist.stop.wait— the field is accepted and ignored; callers cannot block on teardown.max_concurrent_sandboxes, image allowlist, and defaults are not queryable;exec_slots_freeis the only limit that leaks out.sandbox::listresponse documents fields that do not exist (status,created_at,last_exec_at),FsMatchis documented as{path, line_no, byte_offset, line}vs the actual{path, line, content}, andiii.worker.yamlsays "14 triggers" vs the actual 16.