Lane-Based Execution Queueing¶
Organize agent task execution into named, isolated queues — each draining independently with configurable concurrency — to eliminate the three core hazards of concurrent agent systems.
The problem¶
Concurrent agent systems face three failure modes that a single shared queue cannot prevent:
- Interleaving — simultaneous stdout/stdin writes corrupt terminal output
- Race conditions — unsynchronized shared state produces inconsistent results
- Deadlocks — cross-task dependencies with no structured resolution
Lane-based queueing addresses all three. Each class of work gets its own isolated execution context.
Lane structure¶
Each lane is an independent queue with a concurrency cap. A minimal TypeScript representation from the Clawdbot reference implementation:
type LaneState = {
queue: Task[];
active: number;
maxConcurrent: number;
draining: boolean;
};
Two operations run each lane:
drainLane()— pumps tasks from the queue up tomaxConcurrent, then yieldsenqueueCommandInLane<T>()— schedules work and returns a promise the caller can await
Lanes never share state or communicate directly. Isolation is structural, not enforced by locks.
Lane taxonomy¶
A typical multi-agent platform uses four lane types (nibzard/awesome-agentic-patterns):
| Lane | maxConcurrent |
Purpose |
|---|---|---|
main |
1 | Serial CLI default — one task at a time |
cron |
1 | Scheduled tasks — isolated from interactive work |
subagent |
N | Spawned agent work — parallelizable |
session:<id> |
1 | Per-user auto-reply — hierarchical ID |
The session:<id> pattern gives each user isolation without cross-session interference. Use stable identifiers. Unstable IDs spawn new lanes without limit, and memory grows without bound.
Hierarchical composition¶
Nested lanes (a subagent lane spawning session lanes) prevent deadlocks through structured completion: inner lanes complete before outer lanes proceed. This replaces ad-hoc locking with a deterministic hierarchy. Direct cross-lane dependencies outside this hierarchy will deadlock.
graph TD
A[main lane] --> B[subagent lane]
B --> C[session:user1]
B --> D[session:user2]
C -->|completes| B
D -->|completes| B
B -->|completes| A
Observability¶
You can monitor each lane on its own. The signals to watch per lane (nibzard/awesome-agentic-patterns):
queue_size_per_lane— backlog depthactive_tasks_per_lane— in-flight countwait_time_p95— tail latency per work class
Per-lane metrics let you diagnose starvation (a busy subagent lane blocking cron tasks) without correlating across a unified queue.
Trade-offs¶
| Trade-off | Detail |
|---|---|
| Memory overhead | Idle lanes hold allocated state |
| Concurrency tuning | Each lane's maxConcurrent must be sized to available resources — over-parallelization exhausts file handles and memory |
| Starvation risk | High-priority lanes with unbounded throughput can starve low-priority ones without explicit priority controls |
This pattern draws on established foundations: Actor Model isolation (Hewitt, Bishop & Steiger, 1973), Work-Stealing scheduling (Blumofe & Leiserson, JACM 1999), and queue-based routing in Sidekiq, BullMQ, and Airflow pools. Per-queue concurrency without dedicated worker processes is a long-standing pain point in those systems — lane isolation makes the constraint explicit rather than emergent.
Relation to worktree isolation¶
Worktree isolation works at the process and filesystem level. Each agent gets its own working directory. Lane-based queueing works at the task-scheduling level. Each class of work gets its own queue. The two complement each other: worktrees prevent filesystem conflicts between parallel agents, and lanes prevent scheduling conflicts between concurrent tasks within a platform. See Worktree Isolation and /batch and Worktrees for Claude Code's built-in worktree orchestration.
Key Takeaways¶
- Name lanes by workload class (
main,cron,subagent,session:<id>), not by caller identity - Set
maxConcurrent=1for any lane where ordering or serial execution matters - Enforce hierarchical composition — inner lanes complete before outer lanes proceed
- Expose per-lane metrics; a unified queue makes starvation invisible
- Avoid dynamic lane IDs — stable identifiers prevent memory leaks