Skip to content

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 to maxConcurrent, then yields
  • enqueueCommandInLane<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 depth
  • active_tasks_per_lane — in-flight count
  • wait_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=1 for 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
Feedback