Running several AI-agent sessions at once is the fastest way to burn a day of work — or to corrupt three branches and dilute every review. This kit's answer is cockpit + isolated lanes (Principle #3): one coordinating session owns the shared narrative and the merges, while each unit of parallel work runs in its own isolated lane. This page explains what a lane is, when parallelism is safe, and the end-to-end workflow.
Engine: scripts/dev_session.sh (launch/list/remove
lanes) and scripts/reconcile_sessions.sh
(drive every lane to a terminal state). The in-session front-end is the
shared parallel workflow. These links use
the template default paths.engines: scripts; a namespaced adopter substitutes its
configured directory, such as scripts/devkit.
A lane is one unit of parallel work, fully isolated:
- its own git worktree on a fresh branch off
origin/<base>— so two lanes never share a working tree or step on each other's checkout; - its own state sandbox — a
DEVKIT_STATE_ROOTpointed at a per-lane directory (with a.devkit_state_rootmarker file for tool calls that don't inherit the shell), so concurrent writes tostate/cache/and other scratch state can't clobber each other. Seescripts/lib/state_paths/.
The cockpit is your main session. It owns the two narrative files
(docs/handoff.md, docs/friction-log.md), the review pass, and the terminal merge
decision. Lanes never touch the narrative files — they carry their handoff in their
pull request description, the one channel that's reviewed and visible across every
lane.
This is the one rule that matters: two lanes are safe together only when no source file is edited by both.
The state sandbox makes concurrent scratch-state writes safe. It does nothing for two branches editing the same source file — that's a merge conflict plus a diluted review at PR time, and no amount of sandboxing prevents it. So before launching a batch, map each candidate's file footprint and only run truly disjoint work concurrently. Footprint-mapping is a separate, deliberate step from isolation — the sandbox prevents state collisions, not source merge conflicts.
Rule of thumb: parallelize independent features/areas; keep anything that touches a shared module, schema, or config in the same lane and run it sequentially.
flowchart TD
CP["cockpit<br/>owns handoff · friction-log · merges"] --> Plan["parallel plan<br/>cluster candidates by file footprint<br/>· at most one per disjoint cluster"]
Plan --> L1["lane A<br/>worktree + branch + sandbox"]
Plan --> L2["lane B<br/>worktree + branch + sandbox"]
Plan --> L3["lane C<br/>worktree + branch + sandbox"]
L1 --> D1["draft PR · green"]
L2 --> D2["draft PR · green"]
L3 --> D3["draft PR · green"]
D1 --> R["cockpit reconciles<br/>list --watch + reconcile_sessions.sh"]
D2 --> R
D3 --> R
R --> M["review + merge<br/>self-merge or operator-merge<br/>per lane's pre-assigned class"]
Don't spin up lanes ticket-by-ticket. Compose the batch deliberately:
- Orient —
scripts/dev_session.sh list+git worktree listshow the file territory already claimed by in-flight lanes; exclude those footprints. - Cluster by footprint — group candidate tickets by the files each touches (read the ticket and grep the code; don't infer from the title). Pick at most one per cluster; the rest go sequential.
- Stale-premise pre-flight — drop any candidate whose fix may already be shipped (a checklist item matching a recently merged PR, a "Done" state that was a bot auto-complete). Verify against live code before recommending it.
- Scope outward-safe — a lane that would push to an external system or send a notification is scoped to its in-repo half; the outward step stays an operator action after merge.
- Assign an effort tier and a merge class per lane — decide up front how much reasoning each lane gets (cheap → top) and whether it may self-merge once green or must hand back for operator sign-off. Deciding the merge boundary at plan time stops a batch stalling on ad-hoc "can I merge this?" calls.
The exact per-step commands, plus the effort-tier and merge-class tables, are in
workflows/parallel.md
— this doc keeps only the reasoning behind each step.
Each new creates the worktree + branch + sandbox, persists the merge class, and
hands off to the lane: a copy-paste line for an interactive operator, or (--headless)
a sticky on-disk marker plus a JSON descriptor for an unattended launcher — the
mechanism a background sub-agent's stateless tool calls need to find their sandbox
without a surviving shell export. Omitting --merge-class fails safe to operator.
The exact command, every flag, and the headless JSON descriptor are in
workflows/parallel.md
(interactive) and
workflows/parallel-headless.md
(unattended); for task-oriented recipes, see parallel-howto.md.
A lane's job ends at draft-PR-green, not at merge — bound by the same lane
contract every launch mechanism injects verbatim into the lane's prompt (draft PR,
active CI polling, never touching the narrative files, branch hygiene). Fetch it
yourself with dev_session.sh print-contract, or read it in
workflows/parallel-headless.md
— this doc intentionally doesn't restate it, so the two copies can't drift apart.
scripts/dev_session.sh list --watch # re-render every 30sThe live board marks every row that changed since the last frame with a leading * —
a CI flip, a new commit, the DIRTY count moving, a PR-state change — and surfaces a
silently-dead lane that stops moving. Use it as the cockpit's ambient board while a
batch runs; full behavior (piped output, --max-iters, per-call timeouts) is in
workflows/parallel.md.
Before the cockpit writes anything to the shared handoff, drive every launched lane
to a terminal state — merged, parked-with-reason, or still-open — with
scripts/reconcile_sessions.sh. An aggregate "everything's done" is not evidence a
specific lane actually shipped; reconcile per lane. Then review and merge each per its
pre-assigned merge class: a self-merge lane goes through the deterministic wrapper
(re-polls CI/review/merge readiness at act time and refuses missing or operator
metadata); an operator lane is merged only after explicit cockpit sign-off. The exact
pr-watch / merge / rm command sequence lives in
workflows/parallel.md
and parallel-howto.md — this doc
keeps only the reconcile-before-merge reasoning.
The risk read from planning also sets each lane's reasoning budget (Principle #7): a mechanical sweep gets a cheap tier; the one lane whose decision is expensive to get wrong gets the top tier. The tier travels with the lane as an explicit field, not an assumption the lane has to infer — a cheap-tier agent handed a subtle-invariant task will confidently ship the wrong thing.
You have four open tickets. Planning clusters them by footprint:
| Ticket | Touches | Cluster |
|---|---|---|
| Add rate-limit to the auth endpoint | auth/ |
A |
| Rename the metrics module | metrics/ (repo-wide import sweep) |
B |
| Fix a typo in the CLI help | cli/help.py |
C |
| Tighten the auth token TTL | auth/ |
A |
Two of them share auth/ (cluster A) — so you run one of them now and defer the
other. You launch three disjoint lanes:
scripts/dev_session.sh new auth-ratelimit --headless --merge-class operator # cluster A · top tier
scripts/dev_session.sh new metrics-rename --headless --merge-class self # cluster B · cheap tier
scripts/dev_session.sh new cli-help-typo --headless --merge-class self # cluster C · cheap tierEach lane works to a draft-green PR while you watch list --watch from the cockpit.
The two cheap self-merge lanes land through dev_session.sh merge; the auth rate-limit
lane (security-adjacent) hands back for operator review. You reconcile all three,
merge, and only then update docs/handoff.md with what shipped — from the cockpit,
once.
parallel-howto.md— the task-oriented companion: step-by-step recipes per use case, and what actually happens when you run eachparallelverb.PRINCIPLES.md#3 (cockpit + isolated lanes), #4 (merge classes), #7 (effort tiering).agentic-dev-kit/workflows/parallel.md— the shared workflow, andagentic-dev-kit/workflows/parallel-headless.mdfor unattended-lane launch mechanics.docs/getting-started.md— the single-session loop this sits on top of.