ADLC
Toolkit

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
FlagDescription
--dry-runCompute 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-containerOperator-local override: run env-scrub-only when no OS sandbox backend is present (repo config can never set this).
--jsonMachine-readable output: one result document on stdout with fleetRunId, reason, review, and the policy echo.
--no-pr / --no-completeOperator-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-envOperator-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-onlyOperator-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-mirrorOperator-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 allowlistOperator-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

0: every dispatched ticket merged (or a clean dry-run).1: operational error (bad config, dirty tree, lock held, missing gate, no sandbox).2: at least one ticket failed, was blocked, or was paused by the pre-strike command / the wall clock (see 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 0

Go deeper

packages/fleet · design spec

On this page