* feat: add boxlite SDK as harness dependency * test: add fake SimpleBox fixtures for BoxLite warm pool tests * feat: add deterministic sandbox_id and warm pool fields to BoxliteProvider * feat: pass deterministic sandbox_id to SimpleBox name * feat: warm pool lifecycle — park on release, reclaim on acquire - release(): parks VMs in _warm_pool with timestamp instead of closing - _reclaim_warm_pool(): health checks warm boxes via echo ok - acquire(): tries warm pool reclaim before creating new boxes - Deterministic sandbox_id ensures thread isolation * feat(boxlite): idle reaper, replica enforcement, warm-pool shutdown/reset - Task 6: idle reaper daemon thread destroys expired warm-pool boxes - Task 7: replica enforcement evicts oldest warm-pool box when at capacity - Task 8: shutdown() stops idle checker first, destroys all boxes (active+warm); reset() clears warm pool Tests: 17 passed (4 new: idle reaper, replica enforcement, shutdown, reset) * fix(boxlite): harden warm pool lifecycle races * docs(boxlite): document warm pool configuration * fix(sandbox): log warning when evicting oldest warm box is failed Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(boxlite): stop provider lifecycle on reset * fix(boxlite): make runtime an optional dependency * test(boxlite): remove unused warm pool lookup * docs(boxlite): clarify optional runtime support * refactor: extract shared WarmPoolLifecycleMixin for sandbox warm-pool lifecycle Introduce deerflow.community.warm_pool_lifecycle.WarmPoolLifecycleMixin owning idle-checker loop, warm-pool expiry, oldest-warm eviction, replica counting, and soft-cap logging. Move AioSandboxProvider and BoxliteProvider onto the mixin; keep AIO active-idle cleanup local and delegate only warm-pool expiry to the shared helper. BoxliteProvider also gains: - Prefixed box names (deer-flow-boxlite-*) for startup orphan reconciliation - _reconcile_orphans() adopting surviving boxes from a prior process - Pinned timeout forwarding: command timeout now bounds both BoxLite SDK exec(timeout=...) and the loop bridge .result(timeout) - reset() reworked as lightweight registry clear (boxes -> warm pool, no close, no idle-reaper stop, no loop close) so reset_sandbox_provider() config switches are safe; shutdown() remains the teardown path Backward-compatible: AIO DEFAULT_IDLE_TIMEOUT/DEFAULT_REPLICAS/ IDLE_CHECK_INTERVAL stay importable; Boxlite IDLE_CHECK_INTERVAL stays monkeypatchable. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
BoxLite backend
Runs each DeerFlow sandbox as a BoxLite micro-VM — a daemonless, OCI-native VM with its own kernel (libkrun/KVM on Linux, Hypervisor.framework on macOS). Motivated by the resource/cold-start pain with the default AIO Docker sandbox in #3439 and #3213; discussion in #3936.
Configuration
sandbox:
use: deerflow.community.boxlite:BoxliteProvider
image: python:3.12-slim # any OCI image, run unchanged (default: python:3.12-slim)
memory_mib: 1024 # per-box memory cap (optional)
cpus: 2 # per-box vCPUs (optional)
replicas: 3 # active + warm VM cap per gateway process (default: 3)
idle_timeout: 600 # warm VM idle seconds before stop; 0 disables reaping
environment: # injected into every command
PYTHONUNBUFFERED: "1"
Install the optional runtime before selecting this provider:
pip install "deerflow-harness[boxlite]"
The boxlite package is an optional DeerFlow harness extra, not part of the
default install. It is also limited to the host platforms and architectures
where BoxLite publishes wheels and can boot micro-VMs. Unsupported development
hosts, such as Windows, should use another sandbox provider or run DeerFlow from
a supported Linux/macOS environment.
Host requirement: BoxLite boots micro-VMs, so a Linux host needs KVM — i.e. nested virtualization when DeerFlow runs inside a cloud VM. macOS uses Hypervisor.framework. This is the main deployment constraint to weigh vs. the container-based providers.
Design
DeerFlow's Sandbox contract is synchronous; BoxLite's SDK is async-native and
its box handles are event-loop-affine. The provider owns one private asyncio
loop on a daemon thread and marshals every coroutine onto it via
run_coroutine_threadsafe. BoxLite boxes are named deterministically from
user_id:thread_id, released into an in-process warm pool after each agent turn,
and reclaimed by the same thread on the next acquire.
| File | Role |
|---|---|
provider.py |
SandboxProvider lifecycle + the private-loop bridge |
box.py |
Sandbox adapter; execute_command + file ops |
Contract coverage
The full Sandbox surface is implemented. File operations run as shell commands
inside the box and reuse deerflow.sandbox.search, mirroring e2b_sandbox:
execute_command—sh -lc, with per-call env and timeout.read_file/write_file/update_file—catand chunkedbase64(binary-safe, no arg-size limit).download_file— 100 MB cap, restricted to the/mnt/user-dataprefix.list_dir/glob/grep—find/grepwith busybox-portable flags; results filtered/capped in Python.
The provider creates /mnt/user-data/{workspace,uploads,outputs} and
/mnt/skills on box start so those virtual paths resolve natively.
Warm-pool capacity is governed by sandbox.replicas across active + warm VMs.
sandbox.idle_timeout controls how long released warm VMs stay running; 0
disables idle reaping. Active boxes are never evicted to satisfy the cap.
Status
Verified end-to-end against a live box (provider resolution → execute_command
→ file ops) on macOS/HVF. Linux/KVM validation and benchmarks vs. the AIO
sandbox are tracked in
#3936.