Skip to main content

Lanes

A lane is an ordinary Overdeck conversation that a gauntlet orchestrator launches with pan lane start. It runs any harness, model and effort, in its own working directory, and it nests under the conversation that launched it on the Command Deck and the Agents page. A lane carries four facts on its row, written once at launch: its parent conversation, its run key, its lane key and its role. A gauntlet run is the set of lanes that share a run key. There is no run table and no stored lane status: every state below is computed when you read it. Any conversation can launch lanes, whatever its harness, because pan lane start is a shell command that reads $OVERDECK_CONVERSATION. The pan-gauntlet-loop skill uses lanes to fan out builders and critics.

Glossary

Commands

<lane> accepts a conversation id, a conversation name or conv-<name>. A lane on another machine has no id here: name it by its conversation name. --machine <label|environment-id|auto> runs the lane on that linked machine, or on the one Auto placement chooses for auto (--needs <tag,…> limits the choice). A lane on another machine is messaged with pan tell @<label>/<name>. See Lanes on other machines. Messages to a lane go through pan tell conv-<name> "<text>"; there is no lane verb for it. A lane on another machine is pan tell @<label>/<name> "<text>". pan tell refuses a critic or verifier lane that already filed its done report, because a critic is never re-tasked (--force overrides). The HTTP API behind the CLI is POST /api/lanes (with machine and needs), GET /api/lanes?run=&parent=&key=, GET /api/lanes/:name, POST /api/lanes/:name/stop and POST /api/lanes/:name/reap with { park, keep }.

Exit codes

wait and report use the pan worker exit codes: start, stop and reap exit 0 on success and 1 on any refusal. disk-check exits 0 when there is room, 4 when the floor refuses, and 1 on a usage or lookup error.

Lanes on other machines

--machine <label|environment-id|auto> runs the lane on a linked machine: your desktop, your laptop, or a linked Fly machine. auto lets Auto placement choose by load; --needs <tag,…> limits it to machines with those capability tags (for example --machine auto --needs docker for a critic that runs a stack). Naming this machine launches here as usual. The work is split between the two machines:
  • This machine (home) keeps every rule that needs the run’s lanes: the parent and launcher rules, the run, the critic link and its default --at, the iteration, one live lane per run, key and role (--replace stops a lane on any machine), and the model, harness and effort from this project’s gauntlet config. It then keeps a record of the remote lane under ~/.overdeck/remote-lanes/<name>/ and copies its reports into ~/.overdeck/agents/conv-<name>/reports/, so list, show, wait, stop and reap work from here.
  • The other machine (the owner) creates the lane’s directory under its own lanes root, in its own project for the same repository, writes the brief, and starts the conversation. It runs its own CPU check (--force overrides it) and checks the harness. pan lane report inside the lane works as it does here.
What to know:
  • list and show print the conversation name in ID for a remote lane, and the machine in MACHINE ((offline) while it cannot be reached). The JSON has id: null and machine: { environmentId, label, reachable, lastSeenAt }.
  • The start JSON has placement: { label, reason, rejections } when Auto chose the machine, and null otherwise; machine is null whenever the lane runs here.
  • wait keeps waiting while the machine is unreachable: a sleeping laptop delays a report, never fails the wait (it times out with exit 3, never exit 2).
  • A critic or verifier on another machine judges a commit that is on origin: the builder must push first, or the launch is refused with 400.
  • --reuse continues on the newest lane’s machine, so it takes no --machine.
  • Orchestrator lanes run on this machine only.
  • A Fly machine must be linked first with pan machines add-fly --project <key>, and runs Claude Code and Codex lanes only. It is destroyed after remote.fly.machine_idle_minutes (default 30) of idle conversations, so raise that for runs with long waits.
  • The Command Deck nests a remote lane under the orchestrator that launched it, and the Awareness rail counts it in the run card. The row also shows the lane’s report badge (DONE, BLOCKED, FAILED) and, for a critic or verifier, its verdict; a builder’s verdict link opens its newest critic on whichever machine it ran. The group summary counts remote lanes in reported too.
  • A request that arrives from another linked machine may not place a lane on a third one (403 no-chained-hops).

Who may launch what

The door judges the effective launcher: the launching conversation, or, when that is a successor, the row reached by following parent links until a lane or a root. So a handed-off orchestrator keeps its standing.
  • --run is required from a root; a lane launched by an orchestrator lane inherits that lane’s run.
  • A builder, critic or verifier lane launches nothing.
  • A new builder iteration cuts its branch from the previous iteration’s branch (the one its newest report recorded); --from overrides the base. A branch that reported done is frozen.
  • --reuse launches a fresh conversation in the directory of the latest earlier lane with the same run, key and role, keeping its iteration; that lane must not be live.
  • One live lane per run, key and role: a second launch is refused unless --replace, which stops the live lane first. A lane that is still starting (not ended, created under 3 minutes ago) counts as live.
  • --effort accepts low, medium or high.
  • With no --model, the door uses the role’s model from the project’s gauntlet config; with none there, the launch is refused. There is no hardcoded model fallback.

Derived state

ACTIVITY in pan lane list and activity in the API follow this table. The first match wins. Git facts (branch, head, commits ahead of upstream, dirty) are read for git-backed lanes that are not archived. This table is implemented once, as laneActivityOf in @overdeck/contracts; pan lane list, GET /api/lanes and the Awareness rail all use it.

In the Awareness rail

The Command Deck’s Awareness rail shows a gauntlet run as one card, grouped by project and run key. Lane conversations do not get cards of their own.
  • Counts line: role counts, then activity counts, only when non-zero, for example 6 builders · 6 working or 6 builders · 1 needs you · 5 working.
  • Latest event: the newest lane report, failed start, end or launch, for example alpha critic i2 reported done · verdict pass. The card’s time is that event’s time, not transcript activity, so a busy run does not keep jumping to the top.
  • Click opens the conversation that launched the run.
  • Expand lists the lanes, each critic or verifier under the builder it judges, and reads GET /api/lanes?run=<run> once for git facts and archived lanes. It does not poll.
Nothing is stored for the card: it is derived from the conversation rows on every read.

Reports

A lane ends its work with pan lane report --file <markdown>:
  • --status done (default): the brief is complete. A builder may report done only from a clean tree whose branch has an upstream holding every commit; --allow-unpushed waives the upstream check, never the clean-tree check.
  • --status blocked: the lane needs a decision. Start the report with RULING, SPEND, ONE-WAY or BLOCKED.
  • --status failed: the brief cannot be done.
pan lane report outside a lane (including in a successor of one) fails with not inside a lane.

Reap

pan lane reap <lane> never kills a process and never discards work:
  1. It stops a live lane through the conversation stop door, and refuses if the harness is still alive after 30 s.
  2. It refuses a directory outside the project’s lanes root.
  3. It refuses, listing each PID and command line, while any process has its working directory inside the lane directory. Stop those processes through whatever started them.
  4. It refuses a dirty worktree unless --park. With --park it saves the uncommitted work (tracked changes and untracked files) to ~/.overdeck/agents/conv-<name>/parked-<timestamp>.patch, verifies that the patch reproduces it, and only then cleans the tree.
  5. It warns about commits not on the upstream; the branch is always kept.
  6. It runs git worktree remove (never --force) and git worktree prune. A play directory is removed only when it holds no .git.
  7. It archives the lane’s conversation so a finished run leaves the Command Deck. --keep leaves the ended conversation listed instead. If archiving fails, the reap still succeeds and prints reaped, but archiving failed: <error>; archive conv <id> from the Command Deck.
A refused reap archives nothing. The brief, the reports and any parked patch in ~/.overdeck/agents/conv-<name>/ survive the reap, and archived lanes stay in pan lane list and GET /api/lanes with activity: stopped, so a run’s ledger stays complete. Reap builders before the orchestrator lane that launched them: an archived orchestrator lane leaves its unreaped builders as orphans. A lane is reapable — a candidate for pan lane reap right now — when a newer iteration of the same run, key and role exists, no row of its own iteration is working, needs-you or starting, and either some row of that iteration filed a report or none is idle. Orchestrator lanes are never reapable: archiving one orphans the builders it launched. Being reapable is a candidate, not a promise — the reap itself can still refuse (a live process in the directory, a dirty tree without --park). pan lane list --run <run> --reapable lists the candidates; reap each with pan lane reap <name> (--park for a dirty tree) before launching more lanes in the run. pan lane wait prints the same names on stderr right after a report arrives, so the loop is: wait, reap whatever it names, launch the next iteration.

Disk floor

A lane launch is admitted against the same 10 GiB safety floor as a workspace start (PAN-4961), at the project’s lanes root: below the floor, start refuses with HTTP 507, code: disk-low, and the Disk low — new workspace starts are held text naming free, reserved and needed GiB. --force does not bypass it — it still bypasses only the CPU check. A lane that creates a new worktree (a builder, critic, verifier or orchestrator without --reuse) reserves the project’s workspace-start estimate with a 2 GiB minimum; --reuse and play lanes reserve 0 bytes but are still checked. A launched lane’s reservation keeps counting for 15 minutes after the door returns, while it checks out and installs, so a burst of launches cannot each pass the floor before any of them has written. A lane on another machine (--machine) is checked by its owner, against the owner’s own lanes root; home runs no disk check for it. The lane door sees only lanes: a worktree you create yourself with git worktree add, or through Agent-tool worktree isolation, is invisible to it and to pan lane reap. Run pan lane disk-check --project <key> before every launch wave and before creating one of these worktrees yourself; with --path <dir> it checks the filesystem holding that directory instead (the current directory by default). It exits 0 with a Disk ok: … line when there is room, 4 with the refusal on stderr when there is not, and with --project it also lists directories under the lanes root that no lane row claims — not lanes, so pan lane reap cannot remove them; remove each with git worktree remove <dir> once its work is merged or saved.

Critics and builders

A critic or verifier lane records the builder row it judges, once, at launch.
  • --for <builder key> names the builder. It is required for a critic and optional for a verifier (a verifier sometimes judges a whole run rather than one builder). --key defaults to --for; a different --key is refused, as is a --for with no builder of that key in the run.
  • Without --at, the critic is checked out at the head of the newest done report of the judged builder iteration. When that iteration has no done report, the launch is refused: builder <key> i<n> has no done report; pass --at <sha>.
  • A critic or verifier files exactly one verdict, with its done report:
    The verdict is WOWED, IMPRESSED, NOT_YET, PASS or DEFECTS. The defect count defaults to the length of the verdict file’s defects array. A done report without a verdict, --verdict on another role or on a blocked report, and a second done report from the same critic are refused.
  • Pairing is by iteration. The critics of builder iteration n are the critic and verifier lanes of the same run and key whose judged row has iteration n, so a --reuse respawn shares them. A builder of iteration n ≥ 2 is told the verdict it answers: the newest critic of iteration n − 1 created before it, named by conversation id, verdict and verdict file.
  • pan lane show --run hotel --key 663 prints the chain, for example i1 built → critic c1: NOT_YET (7 defects) → i2 built → critic c2: WOWED, then one line per lane: #<id> <role> i<n> <activity> <verdict> <verdict file>. pan lane show <lane> finds the run and key from any lane of the chain.
  • pan lane list shows FOR (<key> i<n>, the judged builder iteration) and VERDICT (a critic’s verdict, or a builder’s newest critic verdict, with the defect count, as in NOT_YET 7).
  • Blindness is preserved. The link changes nothing a critic is told: its lane contract and brief contain neither the builder’s conversation name nor any text of a builder report. Only the commit it judges comes from the builder.
On the Command Deck and the Agents page a critic nests under the builder it judges, with a verdict badge; a builder shows its newest critic verdict as a link to that critic.

Handoffs and succession

pan handoff and pan fork record the same parent link on the successor, so a handoff chain renders as a tree. When an orchestrator hands itself off, the lanes it launched stay nested under it, and the successor nests beside them. The successor shows continues ← #<id>, keeps launching in the same run with the same --run, and sees every lane of the run with pan lane list --run <key>. A successor is never a lane: continuing a lane’s own work in a fresh conversation is pan lane start --reuse, not pan handoff. pan handoff --fresh is refused (400) when the source is a lane or any conversation in a lane’s successor chain: a --fresh successor has no parent link and shows no continues ← link, which would silently drop the lane standing (run/key/role) that a lane only carries through that link. Use a normal handoff, or pan lane start --reuse, to continue a lane’s work.

Configuration

Per-project defaults live in the optional gauntlet block of ~/.overdeck/projects.yaml (see Projects):
The lanes root defaults to a sibling of the project checkout named <project dir>-lanes. It must be under your home directory, outside /tmp, and must not contain the project checkout. The base ref defaults to origin/<default branch>, fetched before each git-backed launch.

Workers or lanes?

Use pan worker run for issue-linked delegation inside the managed pipeline: a worker belongs to an issue and reports back to the agent that started it. Use pan lane start for gauntlet fan-out outside the pipeline: a lane is a conversation, so it runs any harness, nests on the Command Deck, and has a transcript view and a composer.