Lanes
Lanes are parallel work streams that organize work by domain and prevent overload through WIP (Work-in-Progress) limits. At the kernel level, lanes also define scope boundaries — the scope intersection algorithm uses lane code_paths as one of four permission levels that determine what an agent can access.
Why Lanes?
Section titled “Why Lanes?”Without lanes:
- Everything competes for attention
- Context switching kills productivity
- Bottlenecks form unpredictably
With lanes:
- Work is organized by domain
- Each lane has focused attention
- Optional WIP limits prevent overload where you want them
Lane Lifecycle
Section titled “Lane Lifecycle”Lane design is an explicit lifecycle process:
unconfigured -> draft -> locked
lumenflow init bootstraps tooling and sets lane lifecycle to unconfigured. Before creating delivery WUs, run:
Check current state any time:
Defining Lanes
Section titled “Defining Lanes”Lanes use a Parent: Sublane naming convention. Create and edit them through the lifecycle
commands so kernel scopes and delivery code paths remain synchronized:
lane:edit writes both places a lane is described — the workspace lane scopes and the delivery
code paths — under one validation and one commit. If either rejects the edit, neither is changed.
Parent Lane Categories
Section titled “Parent Lane Categories”| Parent | Purpose |
|---|---|
| Framework | Core libraries, CLI, memory, agents |
| Operations | Infrastructure, CI/CD, tooling |
| Content | Documentation, guides |
| Experience | UI components, pages |
WIP Limits
Section titled “WIP Limits”WIP (Work-in-Progress) limits control how many active WUs a lane can have simultaneously. Lane WIP is unlimited by default. A lane that sets neither wip_limit nor lock_policy takes no lane lock, runs no WIP check, and accepts any number of concurrent claims.
Why this is safe: overlap on ordinary source is advisory at claim and is resolved at wu:rebase or the wu:done auto-rebase, which re-gates the rebased tree. Host load is bounded by gate admission and the memory start guard, not by lanes.
The effective policy is resolved in one place:
- An explicit
lock_policywins. - Otherwise an explicit
wip_limitis enforced withlock_policy: all: a counting lock withwip_limitslots. - Otherwise
lock_policy: none(unlimited).
An explicit lock_policy: all or active with no wip_limit enforces a limit of 1.
To make a lane serial, or to cap it, set a limit. --wip-justification is an optional note recording why the lane sets an explicit limit:
Existing configs that set wip_limit or lock_policy keep their enforcement. lumenflow init, lane:setup, lane:create and lane:suggest no longer write wip_limit: 1.
Parallel work across lanes: Each lane operates independently, so work in several lanes proceeds concurrently whatever their limits.
Lock Policy
Section titled “Lock Policy”The lock_policy field controls how blocked WUs affect lane availability:
| Policy | Behavior |
|---|---|
all | Default when wip_limit is set. in_progress and blocked WUs count toward WIP and retain the lane lock. |
active | Only in_progress WUs count toward WIP. Blocking a WU releases its lane lock and WIP occupancy. |
none | Default when neither wip_limit nor lock_policy is set. Lane-lock acquisition and WIP occupancy checks are disabled. |
Claim Refusals
Section titled “Claim Refusals”Atomic lane locking, WIP occupancy, and code-path overlap queues are separate controls. A lane-lock
refusal names the lock owner and current lock_policy; it is not evidence that the WIP preflight
made the same decision.
--forceaffects only the WIP preflight. It cannot overwrite an existing atomic lane lock, so rerunning the same claim with--forcedoes not resolve a lock refusal.--wait-for-lanequeues WUs only behind in-progress WUs whosecode_pathsoverlap an unmergeable path (lockfiles, generateddist, ledgers, baselines;software_delivery.gates.unmergeable_paths). Ordinary-source overlap is advisory. It runs before lock acquisition and does not wait for an already-held lane lock.- Under the
allpolicy, blocking the owner does not free the lane. Underactive, a legitimate lifecycle block releases both the lock and WIP occupancy. Undernone, no lock should be acquired.
Inspect the owner with pnpm wu:status --id <wu-id>. If the owner is genuinely abandoned, inspect
the lock with pnpm wu:unlock-lane --lane "<lane>" --status, then follow the command it prints. The
audited recovery surface requires a reason:
When to Use Each Policy
Section titled “When to Use Each Policy”none(default when no limit is set): Unlimited concurrent claimsall(default whenwip_limitis set): High-conflict lanes where blocked work may resume soonactive: Low-conflict lanes like documentation where blocked work is unlikely to cause merge conflicts
Pilot Recommendation
Section titled “Pilot Recommendation”Start with lock_policy: active on low-conflict lanes like Content: Documentation:
- Documentation WUs are low-conflict (different files, few dependencies)
- Blocked docs WUs rarely resume immediately
- Easy rollback: change
activeback toallin config
Rollback
Section titled “Rollback”If lock_policy: active causes issues:
- Review the current definition with
pnpm lane:list --jsonand update the governed lane policy through the workspace configuration process. - Inspect
pnpm wu:unlock-lane --lane "<lane>" --status. - For a genuinely stale lock, run
pnpm wu:unlock-lane --lane "<lane>" --reason "Lock policy rollback cleanup".
Lane Lock Lifecycle
Section titled “Lane Lock Lifecycle”Lane locks are file-based counting locks that honour wip_limit. A lane with wip_limit: N owns N lock slots: <lane-kebab>.lock is slot 1 and <lane-kebab>.slot-<k>.lock are slots 2 to N, in the workspace lock directory. Each slot holds one WU and is created atomically, so N WUs can hold the lane at once and claim N+1 is refused with a diagnostic that names every holder. When N claims race for the last slot, exactly one wins. A wip_limit: 1 lane uses a single lock file, exactly as before. For lanes with lock_policy: none (including every lane that sets neither wip_limit nor lock_policy), lock checking is bypassed entirely. wu:unlock-lane --status and --list show every holder with its WU, session and timestamp; stale takeover, wu:unlock-lane and release all work per slot. The lock lifecycle has three key phases:
Acquisition
Section titled “Acquisition”When wu:claim runs, it takes the first free slot by creating its lock file atomically using the wx (write-exclusive) flag. The lock file contains metadata including the WU ID, timestamp, agent session, and the PID of the claiming process.
Persistence
Section titled “Persistence”Because wu:claim is a short-lived CLI process, the PID stored in the lock becomes invalid as soon as the claim completes. This is expected behavior — the lock persists on disk regardless of whether the original process is still running. The lock remains valid until explicitly released by wu:done or forcibly cleared.
Release
Section titled “Release”Locks are released in three ways:
- Normal release:
wu:doneremoves the lock after merging - Stale zombie auto-clear: If a lock is both stale (older than 2 hours) AND the PID is no longer running, subsequent
wu:claimcalls auto-clear it. This handles genuinely abandoned locks from crashed processes. - Manual unlock: inspect
pnpm wu:unlock-lane --lane "<lane>" --statusand the owner withpnpm wu:status --id <wu-id>before using the audited unlock. A recent dead claim PID is expected and is not sufficient to remove the lock without--force.
Zombie Detection Semantics
Section titled “Zombie Detection Semantics”A “zombie lock” is one where the PID that created it is no longer running. However, a dead PID alone does NOT trigger auto-clearing. Since wu:claim exits immediately after creating the worktree, every lock will have a dead PID shortly after creation.
Auto-clearing requires BOTH conditions:
- The lock PID is no longer running (zombie)
- The lock is older than 2 hours (stale)
This prevents a second wu:claim from stealing a lane that was legitimately claimed by a recently exited wu:claim process.
Dead-Claim Exclusion from Lane WIP (Multi-Host)
Section titled “Dead-Claim Exclusion from Lane WIP (Multi-Host)”A WU counted toward a lane’s WIP tally whose owner is gone would otherwise occupy the lane
forever and silently block legitimate claims. LumenFlow runs on 1..N boxes and repos with git
origin as the shared substrate — a claim’s worktree or local session file existing on THIS host
proves nothing about a claim made on another host, or a cloud/branch-pr claim with no worktree at
all. The lane-WIP check therefore never uses local-only evidence to decide a claim is dead, and
never uses a lane branch or the tracked heartbeat ledger either —
a worktree claim never pushes its lane branch or writes to origin/main’s ledger until wu:done,
so both are permanently absent for every live claim.
Instead, wu:claim publishes a dedicated liveness marker —
refs/lumenflow/claims/<wu-id> — on origin, and the governed dispatch heartbeat seam
republishes it at the same 5-minute cadence the heartbeat ledger already uses. wu:done and
wu:release delete it on cleanup. The delete is best-effort, so a failed one leaves the ref behind;
wu:prune --execute removes the refs of done WUs in one push and reports the refs it considered,
removed and kept (a dry-run lists them without removing).
A command run from the main checkout keeps that liveness fresh even when the claim’s record lives
only on a split integration target (a push-only claim whose metadata target is not origin/main):
the heartbeat routes to the claim’s own linked worktree, identified only by git worktree list (a
registered worktree inside the main checkout whose own work unit record names the claim, with its
claimed branch and pinned working branch equal to the registered branch and its worktree path equal
to the registered path; a generation pinned in the main checkout must match). No match keeps the
“no local claimed_integration_target binding” refusal; more than one refuses as ambiguous. The
current-holder proof still decides every write, and a heartbeat left by an earlier holder session
never stands in for a resumed holder’s liveness.
Only when a lane’s plain lane/status WIP count is already at or over its wip_limit does
wu:claim fetch every occupant’s liveness ref (one network round trip for the whole lane, never
one per WU) and check it against the configured threshold. A WU is excluded from the tally only
when its liveness ref EXISTS on origin and is older than the threshold — a fresh ref, an absent
ref (never published, or already deleted), or an unreachable origin all keep the claim counted
as live. When a WU is excluded this way, wu:claim’s lane-occupancy output names it and the
reason.
The staleness threshold defaults to 240 minutes and is configurable:
Why You Might Set a Limit
Section titled “Why You Might Set a Limit”Scope Boundaries
Section titled “Scope Boundaries”Lane code_paths serve a dual purpose: they organize work AND enforce security boundaries through scope intersection.
When a tool call executes, the kernel computes:
The lane’s code_paths act as the second narrowing layer. An agent working in Services: API (paths: services/api/**) cannot write to apps/web/** — the intersection is empty, and the kernel denies the call.
Lane Selection
Section titled “Lane Selection”When claiming a WU, specify the full Parent: Sublane format:
The WU spec should already have a lane assignment:
Suggestions via Paths
Section titled “Suggestions via Paths”LumenFlow suggests lanes based on the authoritative lane definitions in
workspace.yaml:
If a WU’s code_paths match configured lane code_paths, the lane is suggested
automatically by wu:infer-lane.
Multiple Lanes
Section titled “Multiple Lanes”A WU should belong to one lane. If work spans multiple domains:
- Split the WU – One WU per lane
- Choose primary – Assign to the most affected lane
Example: Feature Spanning UI + Core
Section titled “Example: Feature Spanning UI + Core”Instead of one WU: ❌ “Add user preferences (UI + API + DB)”
Split into:
✅ <wu-id>: “Add preferences API” (lane: Framework: Core)
✅ <wu-id>: “Add preferences UI” (lane: Experience: UI)
✅ <wu-id>: “Add preferences schema” (lane: Operations: Infrastructure)
Visualizing Lanes
Section titled “Visualizing Lanes”The status file shows lane state:
Note: the “Add dark mode toggle” work unit cannot be claimed because the Experience: UI lane already has “Add preferences UI” in progress.
Next Steps
Section titled “Next Steps”- Scope Intersection — How lane paths become permission boundaries
- Policy Engine — Lane-level policy rules
- Gates — Quality checks per lane
- Team Workflow — Lane strategies for teams