New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@adlc/fleet

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@adlc/fleet

Parallel, gated ADLC ticket orchestration across supported agent harnesses.

Source
npmnpm
Version
1.11.0
Version published
Weekly downloads
2.4K
-29.95%
Maintainers
1
Weekly downloads
 
Created
Source

@adlc/fleet

Parallel ticket orchestration on the ADLC. The fleet reads the ready tickets from .adlc/tickets.json, dispatches each to a sandboxed headless Claude Code worker in an isolated git worktree, runs the deterministic gates, prosecutes the change with a blocking cross-model review, and merges it into a per-run integration branch — with deterministic control flow (ADLC D0: control flow is code, judgment is models). No LLM call decides ordering, retries, or merges.

Design contract: docs/specs/fleet-orchestration.md · decisions & threat model: docs/adr/0010-adlc-fleet-orchestration.md.

What it does

  • Plan source is .adlc/tickets.json itself (via @adlc/core) — not a new format. completed:true tickets are filtered (repo invariant #104) but still satisfy edges. Scope-overlapping tickets are serialized; a single writer holds a partition at a time.
  • Sandboxed repo-command plane — init, build, and test commands run inside an OS sandbox (network denied, reads/writes bounded to the worktree, synthetic HOME). The fleet fails closed if no sandbox backend is available.
  • Model plane — the claude -p worker runs with network egress and its own auth so it can function, and is wrapped in a filesystem sandbox: its writes are bounded to the ticket's worktree plus the state directories its harness declares. Network and filesystem are independent axes; K2 only ever required the network one to differ (#395). Reads are not bounded there — a documented residual, see the security section.
  • Deterministic pre-merge gates — build/test, rails-guard, a ticket-local scope check, and a closed protected-control-file integrity scan (covering the .adlc/tickets.json trust root).
  • Blocking cross-model prosecution — every ticket must clear an adversarial-review pass before merge; it fails closed if no provider is reachable.
  • Integration branch, never base — finished tickets merge sequentially into fleet/run-<runId>; the fleet opens at most one PR to base and never pushes.
  • Auto-completes on a passing merge gate — once a ticket's post-merge gate passes, the fleet marks it completed:true on the integration branch via the same adlc ticket complete (planComplete + apply) path a human would use, so the add-only annotation and its manifest evidence ride the single PR. Idempotent (an already-completed ticket is a no-op) and gated (a gate-failed ticket stays open).
  • Two-strike failure policy informed by flail-detector (fails open).

Usage

fleet run --dry-run                 # preview the plan; no worktrees, no workers
fleet run --dry-run --json          # machine-readable plan
fleet run [--concurrency N] [--tickets T1,T2] [--base main]
fleet status [--json]               # show the current run's per-ticket state
fleet unlock                        # release a stale run lock (guarded)

Dry-run smoke (AC5)

Against this repo's real backlog:

node packages/fleet/bin/fleet.mjs run --dry-run

exits 0, prints the ready set + first dispatch batch + tickets waiting on deps, excludes completed:true tickets, and creates no worktrees (git worktree list is unchanged).

Live run — prerequisites

A live fleet run (no --dry-run) requires:

  • an OS sandbox backend on the host (bwrap/unshare on Linux, sandbox-exec on macOS), or an operator-local --i-am-in-a-disposable-container asserting the whole run is already isolated (a committed repo config can never set this);
  • the ADLC Claude Code plugin installed so the in-session rail hook arms inside worker worktrees (ADLC_P4_ENFORCEMENT=1 + ADLC_TICKET);
  • a Claude Code binary (claude) on PATH;
  • a trusted adversarial-review binary resolvable on the orchestrator's PATH (or a pinned fleet.reviewBin) — the blocking prosecution runs it by name against a trusted PATH, never npx from the worker worktree, so a worker cannot shadow the prosecutor. If it is not resolvable, prosecution fails closed and no ticket merges;
  • fleet.gate configured under the fleet key of .adlc/config.json.

The fleet refuses to dispatch when the sandbox precondition is unmet (fail closed).

Worker harnesses (adapters)

The scheduler is harness-blind: it talks to workers through a WorkerAdapter seam (lib/adapters/), so which coding agent builds a ticket is a config choice, not a code change. Registered adapters: claude-code (default), codex, agy (Google Antigravity), opencode, pi, cursor. Each is a pure I/O shim that spawns its harness in headless mode on the model plane (network egress + its own auth, filesystem writes bounded — K2 + #395). Select one with the operator-local --adapter flag (default claude-code).

Each adapter also declares homeState — the directories and files its harness writes under HOME. That declaration IS the model-plane write grant: an adapter that declares nothing gets nothing outside the worktree, so a new harness fails loudly rather than inheriting a blanket grant. If a harness needs a path its declaration has not caught up with, widen it with the operator-local --model-plane-writable <path> (repeatable); a fleet.modelPlaneWritable in repo config is ignored with a warning, since letting the candidate tree widen the boundary that contains it would be the boundary disabling itself.

The harness is operator-local, not repo config (adversarial-review K1/A2): only claude-code installs a per-worktree permission allowlist, so a repo-committed fleet.adapter/fleet.adapterCommand could silently move unattended workers onto a less-contained harness. Choosing the harness (and any binary override) is therefore an operator trust decision — set via --adapter / --adapter-command / --adapter-args; a value in .adlc/config.json is ignored with a warning.

The model and its credential are operator-local too (operating-stack §4/§10). fleet.model and fleet.modelAuthKey in .adlc/config.json are now ignored with a warning; use --model and --model-auth-key.

  • model used to be treated as "non-executable data", which was safe only by accident: claude-code, codex, and opencode all silently dropped it, so it reached nothing but the agy adapter. Now that every adapter forces its model onto the command line, a repo-committed value would let a candidate tree choose the model that builds and judges it.
  • modelAuthKey is more dangerous still: it names the ONE variable exempted from modelPlaneEnv's secret stripping, so a repo value chose which host secret entered a code-capable worker. A candidate could name ADLC_MANIFEST_KEY and read the key that signs cross-model attestations. ADLC_MANIFEST_KEY is additionally on a hard denylist — no modelAuthKey unlocks it, not even an operator's.

Only adapterStdin (a prompt-transport shape, not a supply or credential choice) is still read from repo config.

What the model-plane sandbox does and does not close (#395)

The worker's permission allowlist contains repo-authored fleet.gate commands (they come from the candidate tree's .adlc/config.json) and the builder charter tells the worker to run them. That makes a gate command ordinary candidate code running on the model plane.

Closed: it cannot write outside the ticket worktree. Not the operator-local quartermaster registry, not ~/.claude/settings.json or ~/.claude/hooks/, not the installed toolkit, not another repo's worktree. It is also no longer told where any of those are — every ambient ADLC_* variable is withheld from the model-plane environment, and what the charter needs (ADLC_TICKET, ADLC_P4_ENFORCEMENT) is injected explicitly.

Not closed: model-plane reads are unbounded, and egress is open, so a read-and-exfiltrate path remains. Bounding reads means enumerating where every harness keeps auth on every platform — a macOS Keychain item is not a file at all — and is not attempted. Running a fleet over a repo you do not trust remains outside the design's assumptions.

No new failure mode when there is no backend. The model plane reuses the run-wide sandbox decision: a host with no bwrap/sandbox-exec already refuses to dispatch at preflight, and --i-am-in-a-disposable-container already asserts the whole run is contained. There is no model-plane-only opt-out.

Each adapter ships a grounded default invocation (agy --print is verified against antigravity-booster; codex exec, opencode run, cursor-agent -p, and the pi headless form are documented defaults with the confidence noted in each adapter's header). Because harness CLIs evolve, the command and args are overridable via the operator-local --adapter-command / --adapter-args CLI flags (and --model) — so a CLI change is a one-line fix, and an unknown --adapter fails closed at run start. With a quartermaster registry engaged the argv overrides are refused outright, because the registry picks an adapter per ticket and one global binary cannot serve them all. Live end-to-end behavior per harness should be verified against the installed CLI.

Supply — the quartermaster registry (optional)

When an operator-local channel registry exists, it — not --adapter/--model — decides the {adapter, model, transport} for each ticket, routed by @adlc/quartermaster from the ticket's category and CPM float. The layer engages when ADLC_QUARTERMASTER_REGISTRY is set or $XDG_CONFIG_HOME/adlc/quartermaster.json exists; with neither, fleet behaves exactly as documented above.

Once engaged it is fail-closed: a missing, disabled, or invalid registry aborts before dispatch, and --adapter-command/--adapter-args are refused (one global binary cannot serve a per-ticket adapter choice, and a wholesale argv override would discard the forced model). fleet run --dry-run prints the resolved seat and the argv the adapter itself renders, in text or --json.

Nothing in the repo under review participates: a registry-shaped file inside the tree is ignored with a notice, and a configured path that is relative or inside the repo disables loading rather than reading it. Schema and the documented limits: docs/integrations/quartermaster-registry.md.

Configuration (.adlc/config.json)

{
  "fleet": {
    "gate": { "build": "npm run build --workspaces --if-present", "test": "npm test" },
    "init": "npm install",
    "adapterStdin": false,
    "concurrency": 2,
    "base": "main",
    "timeoutMinutes": 30,
    "prosecuteFailOn": "medium",
    "allowedCommands": ["npm test", "npm run build:*", "node --test *"]
  }
}

Exit codes

  • 0 — every dispatched ticket merged (or a clean dry-run).
  • 1 — operational error (bad config, dirty tree, lock held, missing gate, sandbox precondition unmet).
  • 2 — at least one ticket failed or was blocked.

ADLC phase

P4 (parallel build) + P5 (per-ticket prosecution). The fleet is the orchestrator that makes ADLC P4 parallelism executable; it consumes the ticket DAG certified by merge-forecast (D2) and routes each ticket through the gates.

Core gaps

None required core changes. The fleet delegates all ticket primitives (loadTickets, topoSort, scopesOverlap, globMatch, inScope) to @adlc/core and adds no third-party runtime dependency.

Keywords

adlc

FAQs

Package last updated on 24 Aug 2026

Related posts