The spoolctl queue
By default run executes cases synchronously and needs no external service. The
queue path is opt-in: it delegates only runner execution to
spoolctl, and evalctl keeps ownership of
everything else.
Enabling it
evalctl run demo --queue spoolctl --slots 4 --acknowledge-unsandboxed-runner --json
The queue path still executes local runner code, so the invoker’s acknowledgment
(--acknowledge-unsandboxed-runner or EVALCTL_ACKNOWLEDGE_UNSANDBOXED_RUNNER=1)
is required here too, or the run refuses with exit 2.
Requires spoolctl >= 0.4.11 (queue contract v2). If spoolctl is absent or
incompatible, a queued
run fails explicitly (E_SPOOLCTL_UNAVAILABLE / E_SPOOLCTL_INCOMPATIBLE, exit
3). Non-queued runs never touch spoolctl.
What each side owns
Even under --queue spoolctl, evalctl still:
- prepares per-case workspaces,
- normalizes stdout and stderr,
- captures workspace diffs,
- scores cases,
- writes the terminal markers.
spoolctl only runs the runner command. This keeps the run directory and its scoring evalctl-owned and reproducible regardless of how the runner was executed.
The queue database
The queue DB is per-run, at .spoolctl.db. In this release, evalctl starts a
single ephemeral spoolctl work --drain worker per queued run, with at-most-once
execution by default.
Because the DB is per-run, an externally managed cross-machine worker fleet would
require a shared filesystem — general hosted-worker fleets are not part of
this release. The .spoolctl.db is a durability sidecar: it is operational state
and is not required for reports or artifact replay.
When to use it
Reach for the queue when you want bounded, worker-driven runner execution.
Otherwise the synchronous path — optionally with --jobs N for bounded
in-process parallelism — is complete on its own.