Here's a piece of arithmetic almost everyone running AI agents gets wrong.
You have a supervisor process that checks on a worker agent every 5 minutes. The worker finishes a task at some random moment. How long does it sit idle before the supervisor notices?
Two and a half minutes, right? Half the interval. That's the textbook answer, it's what queueing theory says about uniformly distributed arrivals, and it's the number people plug in when they pick a polling interval.
We measured it. The real answer is five minutes. The full interval, every single time.
That's not a rounding error. It means every capacity estimate built on the half-interval assumption is optimistic by 100% — and it's systematically wrong in exactly the situation where operators are most likely to rely on it.
Here's what we found, and what it means if you're running agents in production.
What We Actually Tested
We built a deliberately boring comparison. One worker agent, one fixed task, one fixed scoring rubric. The only thing that changed was how the supervisor decided when to wake up and hand over the next instruction:
- Poll arm — supervisor wakes on a timer every 300 seconds, checks if the worker is idle, delivers if so.
- Event arm — worker signals the moment it finishes; supervisor wakes on the signal.
Same task both ways: build a small three-page web app against 45 automatically-scored requirements, revealed one at a time so the worker never sees the whole list. Same time limit. Same everything else.
Crucially, this was pre-registered — hypotheses, methods, and the analysis plan were written down and frozen before any data existed. We'll come back to why that matters, including where our own process failed.
The Results
| What we measured | Polling | Event-driven | Difference |
|---|---|---|---|
| Idle time per cycle | 323 s | 31 s | 10.4× |
| Idle time, once warmed up | 301 s | 15 s | 19.4× |
| Work cycles completed | 7 | 19 | 2.7× |
| Final rubric score | 47% | 73% | 1.6× |
| Commits produced | 8.5 | 21 | 2.5× |
| Requirements per hour | 9.9 | 74.5 | 7.5× |
Two runs per arm. The within-arm consistency was tight — the two polling runs came in at 299 s and 347 s, the two event runs at 31 s and 32 s. This isn't a noisy measurement.
The headline: the event-driven supervisor got 7.5× more work done per hour. Not because it made the worker smarter. Because it deleted the waiting.
The Finding That Actually Transfers
The 10× number is the sales pitch. This next part is the useful engineering.
We predicted the polling arm would average about 150 seconds of idle time per cycle — half the interval, the textbook answer. It came in at 301 seconds. Our prediction was wrong by a factor of two, and we want to be clear that it was wrong in a direction that happened to flatter our conclusion. So we went and worked out why.
The half-interval formula assumes the worker finishes at a random moment within the interval. That's true when the work arrives from outside your system. It is not true in a supervised loop, because the supervisor's own delivery is what starts the worker's next turn.
Follow the sequence:
- Supervisor wakes on a tick and delivers the next instruction.
- Worker starts immediately, works, and stops ~15 seconds later.
- Supervisor is now asleep. It doesn't wake until the next tick — a nearly full interval away.
- Repeat.
The loop phase-locks. The worker doesn't land randomly between two ticks; it reliably just misses one. In the polling runs, 7 out of 7 cycles landed between 270 and 308 seconds against a 300-second interval. Every one.
A polling supervisor driving a worker whose turns are short relative to the interval pays roughly the full interval per cycle, not half of it.
This is a general property of closed-loop polling with short work units. It's not specific to our task or our tooling. And the practical consequence is blunt: at a 5-minute poll interval, work that an event-driven loop finishes in 16 minutes takes 45 minutes.
If you have ever sized a polling interval by reasoning "average wait is half the interval, so 5 minutes is fine" — that estimate was optimistic by 2×, and it gets worse the faster your worker is.
Wait — Did Faster Just Mean Sloppier?
Reasonable objection. The event arm scored 73% on the rubric versus 47% for polling. Maybe waking the worker more often produced better work rather than just more of it — or maybe it was rushing, and the rubric was measuring something else.
We pre-registered a check for exactly this, and it came back clean in both directions.
Score per turn was statistically equivalent between arms — a gap of 8.7%, well inside the 25% band we'd committed to in advance. And a cleaner measure sealed it: requirements satisfied per turn was exactly 1.0 in all four runs, with zero repeated requirement IDs. Not a single turn in either arm was wasted, redone, or interrupted mid-thought.
So the mechanism is purely mechanical. The event arm fit 19 turns into 967 seconds where polling fit 7 into 2,705. Same quality of work per turn. Vastly more turns.
The gain is idleness removed, not work rushed. That distinction matters, because it means the result should hold for your workload too — you're not buying speed by trading away correctness.
The Trap in Event-Driven Loops
Event-driven won decisively, but it shipped with a defect worth knowing about before you build one — because it cost us 315 seconds on the first cycle of every single event run.
Here's the event log:
11:07:45 worker_stop
11:07:45 woke event ← signal fired in the same second 11:07:45 classify BUSY ← still mid-render → skipped 11:12:45 woke timeout ← 300s backstop; nothing else could re-trigger 11:12:46 classify IDLE → delivered
The signal arrived on time. The supervisor woke on time. Then it looked at the worker, saw a UI still finishing its render, classified it BUSY, and discarded the wake.
That's the bug: BUSY is transient, but it was treated as durable. And here's the asymmetry that makes it dangerous — once an event-driven supervisor throws away a wake, nothing can re-trigger it. The worker is already idle. It will never signal again. The loop is dead until a timeout backstop rescues it.
A polling loop has no equivalent failure. Its next tick arrives regardless of what the last one concluded. Polling is worse on average and more forgiving at the edges.
The rule: in an edge-triggered loop, a discarded wake is a liability. On any transient-negative classification, schedule a re-check — never return to the blocking wait.
The fix was small: catch the BUSY, wait a short settle delay (10 seconds), re-check. Confirmed in a follow-up run — first cycle dropped from 315 seconds to 30, with no fall-through to the backstop.
Which also means the 10.4× figure we're reporting understates the real gap. It includes a startup penalty we've since eliminated. We're reporting it anyway, because it's what the sealed experiment measured.
What To Do With This
Four practical takeaways, in order of how much money they'll save you:
1. If your workers signal completion, use the signal. Every mainstream agent runtime can emit a completion hook. If yours can, polling is leaving roughly 90% of your wall-clock throughput unclaimed. This is the single highest-leverage change in the list.
2. If you must poll, stop using half the interval in your estimates. Budget the full interval per cycle. Then notice that the penalty scales with how short your work units are — a supervisor polling every 5 minutes over 15-second tasks is idle 95% of the time. Shrink the interval or shrink the loop.
3. Build the backstop anyway. Event-driven supervision is faster and more fragile. A periodic timeout that fires regardless of event state is what turned our worst-case bug into a 315-second delay instead of a permanent hang. Keep it even when events are working.
4. Never treat a "not ready" signal as final. Re-check after a short delay. This one line of defensive logic was worth 300 seconds per run in our data.
The Part Most Vendors Would Leave Out
We'd rather you trust the parts of this that hold up than believe all of it.
The sample is small. Two runs per arm, pre-registered as underpowered. We report no p-values and no confidence intervals, because computing them on n=2 would imply a precision the design can't support. The direction of a 10–20× effect with tight within-arm replication is not in doubt. The exact magnitude is.
Our own pre-registration seal was broken, and we found it. We hash-seal the frozen analysis plan specifically to prove we didn't invent hypotheses after seeing results. When we audited it, the recorded hash matched no file that has ever existed — the plan had been amended in place before it was ever committed to version control, so version control never captured the thing the hash described. The seal has never verified, at any commit.
We could have quietly re-hashed the current file. Instead: the broken hash stays in the record marked unrecoverable, and this study's hypotheses should be treated as pre-specified on our assertion and the narrative record, not on cryptographic evidence.
The lesson generalizes past our lab: a hash computed before the content is under version control pins nothing. If you're building audit trails for AI systems — and increasingly you have to — seal only committed content, and treat a freeze as a commit hash, not a timestamp in prose. We now verify every seal in the same session it's created. The tool that caught this found it on its first run.
Two dependent variables were structurally coupled, and we didn't pre-register that they were. One requirement revealed per turn means "rubric score" is largely a re-expression of "cycle count." We flagged it in the write-up rather than presenting them as two independent confirmations.
None of that changes the headline. All of it changes how much weight the decimal places deserve.
The Bottom Line
If you're orchestrating AI agents and your supervisor is on a timer, you are probably paying the full poll interval in dead time on every cycle — not half of it, as the standard estimate suggests. On a 5-minute interval with fast workers, that's roughly a 7× throughput tax for no gain in quality.
The fix is architectural, not a matter of tuning: let the worker tell you it's done. Keep a timeout backstop. Never trust a transient "busy."
Running multi-agent workflows and not sure where your throughput is going? Idle time in orchestration loops is invisible in most dashboards — it looks like the agent is working when it's actually waiting. We help teams instrument and re-architect agent pipelines so the compute you're paying for is compute that's running.