fleet
Parallel ticket orchestration. Dispatches ready tickets to sandboxed Claude Code workers in isolated worktrees, gates and prosecutes each, and merges to a per-run integration branch with deterministic control flow.
fleet
ADLC phase: P4 Build + P5 Prosecute · Gate: a ticket merges only after its build/test, rails, scope, protected-path, and blocking cross-model prosecution gates all pass, and nothing is decided by a model.
What it defends against
ADLC P4 parallelism was doctrine without an executor: the ticket DAG says which
work can fan out, but running it meant one interactive session per ticket. fleet
makes the fan-out real. It reads the ready tickets from .adlc/tickets.json (the
existing contract: completed tickets filtered, scope-overlapping tickets
serialized, single writer per partition), dispatches each to a sandboxed
headless Claude Code worker in an isolated git worktree, and walks it through the
deterministic gates before a blocking cross-model adversarial-review pass
must clear it. Control flow is code, judgment is models: no LLM call decides
ordering, retries, or merges.
Containment is two-plane: the repo-command surface (init/build/test) runs inside an
OS sandbox (network-denied, reads/writes bounded to the worktree, synthetic HOME),
while the claude -p worker runs on a separate model plane so it can still reach
its provider. Finished tickets merge sequentially into a per-run
fleet/run-<runId> integration branch, never into base. The fleet never pushes.
Usage
adlc fleet run [--concurrency <N>] [--dry-run] [--tickets <T1,T2>] [--base <branch>] [--json]
[--i-am-in-a-disposable-container]
[--no-pr] [--no-complete] [--dead-end-file <path>] [--max-strikes <n>]
[--wall-clock-minutes <m>] [--charter-file <path>]
[--pre-strike-argv <json-array> --pre-strike-env <json-object>]
[--model-plane-read host|bounded] [--model-plane-read-only <abs,abs,...>]
[--model-plane-git shared|mirror] [--model-plane-git-mirror <abs bare repo>]
[--model-plane-egress open|allowlist] [--worker-deps <abs node_modules>]
adlc fleet status [--json]
adlc fleet unlock| Flag | Description |
|---|---|
--dry-run | Compute and print the ready set + dispatch plan; create no worktrees, dispatch no workers. |
--concurrency <N> | Max workers in flight (default from config, else 2). |
--tickets <ids> | Restrict the run to a comma-separated ticket subset. |
--base <branch> | Base branch the integration branch is cut from (default main). |
--i-am-in-a-disposable-container | Operator-local override: run env-scrub-only when no OS sandbox backend is present (repo config can never set this). |
--json | Machine-readable output: one result document on stdout with fleetRunId, reason, review, and the policy echo. |
--no-pr / --no-complete | Operator-local: leave the integration branch to the caller / leave ticket completion to the caller. |
--dead-end-file <path> | Operator-local: the file's fenced content seeds strike 1's dead-end material (a previous round's failure). |
--max-strikes <n> / --wall-clock-minutes <m> | Operator-local: strike cap (1..50, default 2) and an external deadline for the whole run — expiry kills the strike's process group, records wall-clock, exits 2, stays resumable. |
--charter-file <path> | Operator-local: appended to the builder prompt after the Constraints block. |
--pre-strike-argv + --pre-strike-env | Operator-local: an argv array (absolute argv[0], no shell) run with exactly the given env before every strike; non-zero → quota-paused, resumable by an identical re-invocation (no --resume flag exists). |
--model-plane-read bounded + --model-plane-read-only | Operator-local: bounded model-plane reads (worktree + synthetic home + the fixed system roots the host has + allowlist; single-file binds for executables) with a private tmpfs /tmp. |
--model-plane-git mirror + --model-plane-git-mirror | Operator-local: the worker's worktree is cut from a caller-supplied bare mirror and fetched back by compare-and-swap; failure → mirror-fetch-failed. |
--model-plane-egress allowlist | Operator-local: --unshare-net plus a CONNECT proxy whose only targets are the adapter's declared model hosts. |
--worker-deps <abs> | Operator-local: plain copy of a caller-built dependency tree before every strike; init never runs. |
Configuration lives under the fleet key of .adlc/config.json (gate, init,
concurrency, base, prosecuteFailOn, reviewBin, reviewProvider,
reviewMaxBytes, allowedCommands). A live
run requires an OS sandbox backend, the ADLC rail hook, a claude worker, and a
trusted adversarial-review binary; it fails closed otherwise. Every extension
flag above is operator-local: a value under fleet in the committed config is
warned and ignored.
The reason set
Under --json every non-zero exit carries a machine-readable reason, which is
authoritative for callers (the numeric exit code is unchanged). Ticket outcomes
are a closed set: quota-paused, lock-held, wall-clock, strikes-exhausted,
ticket-blocked, flail, review-unavailable, mirror-fetch-failed. Run-level
failures outside it (quarantined, pr-open-failed, preflight,
resume-refused, dispatch-refused) mean a human is needed; a dispatch-refused ticket is left paused (strike handed back) and resumes once the sandbox policy is fixed.
Exit codes
reason).Example
$ adlc fleet run --dry-run
fleet dry-run — integration branch fleet/run-<runId>, concurrency 2, base main
ready now (3): T1, T6, T12
first batch (cap 2): T1, T6
waiting on deps (4): T2, T3, T7, T9
completed (excluded): 8
no worktrees created, no workers dispatched (dry-run).
exit 0Go deeper
runner
Asserts that each ADLC phase's required manifest evidence is present, and fails closed when reviewed content changes.
autopilot
Quota-gated local issue-to-PR loop. Picks the next GitHub issue, shapes an ADLC ticket, dispatches one sandboxed fleet run per issue, runs the outer gates, attests with a cross-model review and opens the PR. P6 (merge) stays human.