Coppice now accepts the E2B lifecycle shape on create:
timeoutMs plus
lifecycle: { onTimeout: “pause”, autoResume: true }.
The reaper pauses idle jail-backed sandboxes and bhyve SSH guests
instead of killing them, and envd/files/commands activity resumes a
paused sandbox before dispatching work.
Wire shape
Create payload:
{
"templateID": "python",
"timeoutMs": 600000,
"lifecycle": {
"onTimeout": "pause",
"autoResume": true
}
}
Accepted aliases:
timeoutMs/timeout_msfor milliseconds.timeout/timeoutSeconds/timeout_secondsfor seconds.lifecycle.onTimeout/lifecycle.on_timeout, or top-levelonTimeout.lifecycle.autoResume/lifecycle.auto_resume, or top-levelautoResume. The value may be the older boolean form or E2B SDK 2.20’s{ "enabled": true }object.- top-level
autoPause: trueas shorthand foronTimeout: "pause".
autoResume=true is valid only with onTimeout="pause". The default
stays kill-on-timeout, preserving existing TTL behavior for callers that
do not opt into persistence.
What counts as activity
The shared activity hook lives in
e2b-compat/src/state.rs::touch_sandbox_activity. It is called from:
POST /executeon the envd port.sandbox.commands.*/ Connect-RPC command routes.sandbox.files.*/ filesystem routes.
When a matching sandbox is paused and has autoResume=true, the hook
calls the owning backend’s resume, marks the sandbox running, bumps
coppice_sandboxes_resumed_total, refreshes lastActiveAt, and starts
a new timeout window.
The timeout window after auto-resume follows E2B’s compatibility rule: at least five minutes after wake-up, or the original timeout if it was longer. Normal running activity refreshes the caller’s original timeout.
Reaper behavior
e2b-compat/src/reaper.rs now decides per sandbox:
onTimeout="kill": call the existing teardown path and incrementcoppice_sandboxes_reaped_total.onTimeout="pause": call backend pause, set state topaused, clearendAt, and incrementcoppice_sandboxes_paused_total.- pause failure: leave the sandbox running and back off for 60 seconds so unsupported backends do not get falsely marked paused.
Jail pause/resume is implemented with SIGSTOP/SIGCONT over jail
processes. Pool-backed bhyve SSH guests and host-console guests now use
the same signal model through coppice-bhyve-pool-ctl.sh pause/resume
or console-pause/console-resume. A paused bhyve pool entry stays
owned by the sandbox and counts as coppice_bhyve_pool_in_use, so the
warm-pool reconciler will not hand it to another caller.
Receipt
The smoke rig is:
mise run lifecycle:auto-suspend-resume-honor
It creates a fresh python jail, waits for idle pause, sends an envd
/execute request with Host: 49999-<sandbox-id>.coppice.lan, and
asserts:
GET /sandboxes/:idreachesstate="paused".coppice_sandboxes_paused_totaladvances.- envd traffic prints
auto_resume_ok. GET /sandboxes/:idreturns tostate="running".coppice_sandboxes_resumed_totaladvances.
Receipt files land in benchmarks/results/auto-suspend-resume/.
The bhyve-specific gateway smoke is:
mise run lifecycle:bhyve-pause-resume-honor
It creates a fresh python-bhyve sandbox, proves /exec, pauses via
POST /sandboxes/:id/pause, checks state="paused" and pause metrics,
resumes via POST /sandboxes/:id/resume, then proves /exec still
works. Receipts land in benchmarks/results/bhyve-pause-resume/.
Cross-refs
- competitor-gaps — the broader-market row this closes across the jail and bhyve SSH paths.
- e2b-compat — the SDK surface.
- bhyve-backend — why bhyve pause/resume is owner-preserving rather than a pool checkout side effect.