Event-Driven Waiting
An agent that has dispatched background work — a subagent, a test suite, a build, a remote job — has to find out when it finishes. The obvious implementation is to ask repeatedly: wait with a short timeout, get "not done yet", wait again. Event-driven waiting replaces that loop with a single call that blocks until the work actually completes, or until a long deadline expires.
The difference is not the waiting. It is that every poll is a turn: the model is re-invoked, the entire conversation is re-sent, and a result saying "still running" is appended to the transcript forever. A poll that learns nothing still costs a full context read, and its useless result makes every subsequent turn slightly more expensive.
Principle
Block on the completion event, not on a clock. One call with a long deadline replaces N short ones, and the token cost of waiting drops from proportional to the duration to constant.
Why Polling Is Quadratic
With a context of size C and a poll interval short enough to produce N checks, polling costs roughly N × C input tokens, and C itself grows with each "not done yet" result appended. Blocking costs one C. Instrumentation of an agent harness found that 67% of wait_agent calls returned nothing but a timeout, and that switching to a single long wait reduced the call count to about 1/90th of the polling version for the same work.
The waste scales with how well the work is going: a subagent that runs for ten minutes because the task is large is precisely the case where a 5-second poll interval burns 120 turns.
Techniques
Block Until Done
Prefer a wait primitive that returns on the completion event with a generous deadline over one that returns on a short timeout. The deadline is a safety net for a hung job, not the expected control flow — so size it to "this job is broken", not to "this job might be finished".
Wait on the Whole Batch
When several children are in flight, wait for all of them in one call rather than looping over them individually. N children should cost one wait, not N.
Let the Harness Wake You
Where the runtime re-invokes the agent when background work completes, polling is pure loss: the notification is already coming. The correct action after dispatch is to do unrelated work or to stop, never to spin.
Fill the Gap With Real Work
If the agent has independent work that does not depend on the pending result, doing it during the wait converts dead turns into useful ones. This only holds for genuinely independent work; speculative work that the pending result may invalidate is a different trade.
Long Fallback, Not Short Heartbeat
When a fallback check is genuinely needed — external state the harness cannot observe — set the interval from how fast that state actually changes. A CI run that takes eight minutes deserves one check at eight minutes, not ninety-six at five seconds.
Trade-offs
| Short-interval polling | Blocking wait | |
|---|---|---|
| Turns spent waiting | One per interval | One |
| Transcript growth | One dead result per poll | One |
| Responsiveness to completion | Up to one interval late | Immediate |
| Failure mode | Cost, silently | A hung job holds the deadline |
The one real argument for polling — noticing a job that will never finish — is served better by a correctly sized deadline on the blocking call.
Anti-patterns
- Polling on a short interval "to show progress"; the progress is visible to nobody and costs a context read each time.
- Scheduling a wake-up to check on work the harness will notify you about anyway.
- Looping over in-flight children one at a time instead of waiting on the set.
- Setting the deadline to the expected duration, so a job that runs slightly long produces a spurious timeout and a second wait.
- Re-dispatching work because a poll timed out, ending up with two copies running.
See Also
- Operation Batching — the same accounting: what matters is the number of turns, not the size of any single one.
- Subagents as Context Firewalls — waiting is the second half of dispatch; a batch dispatched in parallel should be awaited in one call.
- Context Isolation via Subagents — the delegation whose completion is being awaited.
- Output Spooling to Disk — the other way dead bytes become permanently resident: timeout results and log dumps both never leave the transcript.
- Stateful Tracking in Agent Loops — knowing what is already in flight is what prevents a re-dispatch after a spurious timeout.
- Compaction — what a transcript full of dead poll results eventually forces.
Tools
- Claude Code background tasks — background work re-invokes the agent on completion, so no polling call is needed at all.
- GitHub Actions
gh run watch— blocks until a workflow run finishes instead of repeatedly querying its status. - LangGraph — interrupts and checkpoints resume a graph on an event rather than by re-entering to check state.