Skip to content

feat(cubesandbox): lifecycle + ephemeral + snapshot capability (Phase 2, stacked on #137) - #138

Open
JackWeiw wants to merge 2 commits into
feat/cubesandbox-provider-execfrom
feat/cubesandbox-provider-lifecycle
Open

feat(cubesandbox): lifecycle + ephemeral + snapshot capability (Phase 2, stacked on #137)#138
JackWeiw wants to merge 2 commits into
feat/cubesandbox-provider-execfrom
feat/cubesandbox-provider-lifecycle

Conversation

@JackWeiw

@JackWeiw JackWeiw commented Sep 3, 2026

Copy link
Copy Markdown
Owner

What

Phase 2 of the cubesandbox provider, stacked on #137 (Phase 1 exec-only). This lifts the provider from exec-only to the full replay capability surface, mirroring aenv's lifecycle shape over the native CubeSandbox pause/resume (the Cloud Hypervisor fork writes a memory snapshot the resume restores from -- the same memory-reuse oversubscription shape as aenv).

Scope (Phase 2 — full capability)

CubesandboxProvider gains the three replay Protocols:

  • LifecycleCapablepause = native state.cube_sandbox.pause(wait=True) (synchronous: the snapshot must be stable before the runner accounts the pause); resume = state.cube_sandbox = Sandbox.connect(inst.id) (cube's connect auto-resumes a paused sandbox -- the deprecated standalone resume() is replaced by connect -- and returns a fresh handle, swapped in like the manager's _attach).
  • EphemeralCapablecreate_one / kill_one (trajectory mode). Mirrors aenv verbatim: the path is provider-agnostic, routing through the base manager seams (_new_state / _create_single / _ready_checker / _apply_ready / _handle_of / _kill_one / _slot_templates) the cube manager already supplies. create_one forwards metadata + template; stamps _slot_templates so _to_instance stamps the resolved template. kill_one is finally-safe (missing state returns).
  • SnapshotSizeCapablesnapshot_sizes returns None for now. The cube SDK's SnapshotInfo exposes only snapshot_id + names (no size fields), so per-pause size collection can't be satisfied from the control plane yet. None keeps the provider SnapshotSizeCapable (probed right after pause) while signalling "no data" -- the snapshot_size series event is skipped, not crashed. A follow-on can stat the cube snapshot dir on the host like aenv's scan_snapshot_sizes.

default_replay_mode flips exec_onlylifecycle (cube has native pause/resume WITH a memory snapshot). vmm_type stays None (vm_monitor deferred).

A try/except Sandbox import is added to the provider module (resume uses Sandbox.connect); the mock mirrors aenv's, keeping it importable without the SDK.

bench.py

The lifecycle/trajectory validation error messages now guide users to --provider aenv or --provider cubesandbox (cube is now LifecycleCapable + EphemeralCapable, so the aenv-only guidance was stale).

Tests

The 3 Phase 1 identity assertions (exec_only / not-Lifecycle / not-Ephemeral) are replaced with Phase 2 versions (lifecycle / IS-Lifecycle / IS-Ephemeral / IS-Snapshot) plus 11 behavioral tests:

  • pause forwards wait=True + missing-handle raises
  • resume swaps the handle via connect + missing-state raises
  • snapshot_sizes returns None
  • create_one runs the full manager seams (metadata/template forwarding, ready probe, _slot_templates stamping, sdk-failure raises)
  • kill_one calls kill + marks dead + missing-state noop

All 47 cube tests + the full kernel/provider suite pass.

Verification

python -m pytest src/bench_core/tests src/env_provider/tests   # full suite green
python -m ruff check src/env_provider/cubesandbox src/bench_core/bench.py
python -m ruff format --check src/env_provider/cubesandbox
python -m pre_commit run --files <phase-2 files>              # all hooks pass

Stacking

When #137 merges, this PR retargets to the next base up.

…hase 2)

Phase 2 lifts the cubesandbox provider from exec-only to the full replay
capability surface, mirroring aenv's lifecycle shape over the native
CubeSandbox SDK.

CubesandboxProvider (__init__.py):
- pause: native state.cube_sandbox.pause(wait=True) -- synchronous (the
  Cloud Hypervisor fork writes a memory snapshot the resume restores from;
  wait=True keeps it stable before the runner accounts the pause).
- resume: state.cube_sandbox = Sandbox.connect(inst.id) -- connect
  auto-resumes a paused sandbox (the deprecated standalone resume() is
  replaced by connect) and returns a fresh handle, swapped in like the
  manager's _attach.
- snapshot_sizes: returns None for now. The cube SDK's SnapshotInfo exposes
  only snapshot_id + names (no size fields), so per-pause size collection
  can't be satisfied from the control plane yet. None keeps the provider
  SnapshotSizeCapable (probed right after pause) while signalling "no data"
  -- the snapshot_size series event is skipped, not crashed. A follow-on
  can stat the cube snapshot dir on the host like aenv's scan_snapshot_sizes.
- create_one / kill_one: trajectory-mode ephemeral lifecycle. Mirrors aenv
  verbatim -- the path is provider-agnostic, routing through the base
  manager seams (_new_state / _create_single / _ready_checker / _apply_ready
  / _handle_of / _kill_one / _slot_templates) the cube manager already
  supplies. create_one forwards metadata + template; stamps _slot_templates
  so _to_instance stamps the resolved template. kill_one is finally-safe
  (missing state returns, present state kill + is_alive=False).
- default_replay_mode flipped exec_only -> lifecycle (cube has native
  pause/resume WITH a memory snapshot -- the same memory-reuse
  oversubscription shape as aenv).
- Added a try/except Sandbox import (resume uses Sandbox.connect); mock
  mirrors aenv's, keeping the module importable without the SDK.

bench.py: the lifecycle/trajectory validation error messages now guide
users to "--provider aenv or --provider cubesandbox" (cube is now
LifecycleCapable + EphemeralCapable, so the aenv-only guidance was stale).

Tests: the 3 Phase 1 identity assertions (exec_only / not-Lifecycle /
not-Ephemeral) are replaced with Phase 2 versions (lifecycle / IS-Lifecycle
/ IS-Ephemeral / IS-Snapshot) plus 11 behavioral tests: pause forwards
wait=True + missing-handle raises; resume swaps the handle via connect +
missing-state raises; snapshot_sizes returns None; create_one runs the
full manager seams (metadata/template forwarding, ready probe, _slot_templates
stamping, sdk-failure raises); kill_one calls kill + marks dead + missing
state noop. All 47 cube tests + the full kernel/provider suite pass.

vm_monitor integration (cube-hypervisor vs cloud-hypervisor VMM name) and
real snapshot-size host-stat collection stay deferred.
… + usage §8

Records the now-merged Phase 1 + Phase 2 surface (lifecycle + ephemeral +
snapshot) in the project guide and the CN replay mode guide:

CLAUDE.md:
- Scenarios: add CubeSandbox (Cloud Hypervisor KVM microVM, lifecycle-capable).
- Architecture diagram + provider-leaves: list aenv + cubesandbox (the CLI
  choices line and leaf row were stale, missing aenv).
- Provider impls: describe aenv (subclasses E2BProvider, lifecycle/ephemeral/
  snapshot) and cubesandbox (native pause/connect-resume, snapshot_sizes=None).
- Key Packages table: src/env_provider/ now lists e2b/aenv/docker/cubesandbox/
  fake + the Protocols.
- monitor: cubesandbox -> skipped (vmm_type None, cube-hypervisor vs
  cloud-hypervisor unresolved).
- Replay modes: lifecycle/trajectory note aenv + cubesandbox as capable;
  exec_only stays e2b/docker/fake; cubesandbox snapshot_sizes=None skips the
  snapshot_size event.
- Entry points + phase ladder: --provider adds cubesandbox; Tier 2 lists
  e2b/aenv/cubesandbox.
- Install note: cubesandbox is an optional-dependency extra (like e2b/docker).
- Adding-a-provider: cubesandbox is the worked example for a full lifecycle
  backend; notes the structural Protocols + the optional-dependency step.

docs/bench-core-usage-zh.md §8:
- Intro + mode table: aenv/cubesandbox interchangeable for lifecycle &
  trajectory; cubesandbox snapshot_sizes=None note.
- Example command: --provider cubesandbox as a drop-in for lifecycle.
- CLI provider list: {fake,e2b,docker,aenv,cubesandbox}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant