Essay
Slot-pool dispatcher: replacing wave-barrier latency with 1-in-1-out parallel
Why dispatching N tickets as a wave and waiting for the slowest one to finish is the wrong mental model — and what 1-in-1-out slot management actually looks like in production.
The naive mental model for parallel ticket execution is the wave: dispatch N tickets simultaneously, wait for all N to finish, dispatch the next batch. Clean. Easy to reason about. Also wrong.
The problem with wave dispatch is that you’re always waiting for the slowest ticket. If 7 of your 8 executors finish in 3 minutes and the 8th takes 12 minutes, your effective throughput for that wave is 12 minutes — not 3. The fast executors sit idle while the slow one blocks the next wave from starting.
The correct model is a slot pool.
How the slot pool works
You have a fixed number of slots — say, 8. Each slot can hold one running executor. When a slot opens (because its executor finished), you immediately pull the next ticket from the priority queue and start a new executor in that slot. No barriers. No waiting for other slots to drain. 1-in-1-out.
The priority queue is ordered by ticket importance — _global tickets first (they serve all brand executors), then brand-specific tickets by urgency and dependency chain. File-overlap detection prevents two executors from picking up tickets that touch the same files simultaneously, which would produce merge conflicts.
The result: throughput is bounded by the average ticket execution time, not the maximum. Slow tickets don’t block fast ones. The slot pool is always at capacity as long as the queue has work.
What this looks like in production
During the 18-day burst, I was running 8 slots. Average ticket execution time was roughly 4–6 minutes for S-complexity tickets, 10–15 minutes for M-complexity. The queue rarely drained to zero — there was always more work than capacity, which meant slots were almost never idle.
The practical throughput: something like 8–12 tickets merged per hour during peak operation. Not because each ticket was fast, but because the pipeline was always full.
The rebase cliff is the main failure mode of this approach. If executor A merges to main while executor B is mid-execution on a branch that diverges from A’s base, executor B’s FF-merge will fail. The mitigation is to detect this early (before the executor finishes) and rebase before pushing. I built an automated rebase step into the pre-push sequence that detects when the executor’s base is no longer at origin/main and rebases automatically.
This doesn’t eliminate the problem — it just makes recovery automatic rather than manual.
Wave dispatch has one legitimate use case
Wave dispatch is still the right call when you have a set of tickets that genuinely need to be serialized — where ticket B can’t run until ticket A is merged, because B depends on A’s output. In that case, you dispatch A, wait for it to finish, then dispatch B. That’s a wave of one.
The mistake is generalizing the serial case to all dispatch. Most tickets are independent. File-overlap detection is sufficient to prevent conflicts between independent tickets. Running them in parallel is always better than serializing them unless there’s a genuine dependency.
The meta-lesson about parallelism
The intuition that “parallel is complicated and serial is safe” is correct for human teams and wrong for agent systems. Human teams have coordination overhead — communication, context transfer, merge coordination — that scales with parallelism. Agent systems don’t have most of that overhead. Their merge conflicts are deterministic and detectable. Their context transfer is a file read. Their communication is a ticket spec.
Run things in parallel by default. Serialize only when there’s a dependency. Use a slot pool, not waves.
The slot-pool dispatcher is implemented in scripts/session_state.py and documented in wiki/_global/orchestration-protocol.md in the Master OS.