Problem
At high fan-out, TTLS (time to last step) falls off a cliff: durabench parallel sweeps measure ~15–20s TTLS at 256 branches vs ~1.3s at 64, on every SDK version (node engine, iad1). Trace decomposition of a representative run (wrun_41KZR0MW890GWZK34RD4Y1JBDT) shows the dominant cause is O(N²) event-log read traffic:
- Every queued step's consumer, after completing its step, loads the run's full event log to answer "are all parallel steps done?" (the last completer replays inline). At N branches that is N loads of an ~4N-event log.
- Measured server-side:
GET /api/v4/runs/:runId/events p90 = 7.9s during the burst; the saturation drags the consumer prologue's runs.get to p90 = 5.1s, smearing branch starts over ~10–15s.
- Not throttling: zero 425/429 on the run.
Proposal
Let the step-terminal write answer the question the events.list is asking. The World's events.create for step_completed / step_failed returns an optional count of the run's non-terminal steps after the write (or a boolean "none pending"); the runtime then:
- skips the events.list entirely when
pending > 0 (return, another handler finishes later);
- only the true last completer (
pending === 0) proceeds — and only IT loads the log for the inline replay.
That converts O(N) full-log loads per fan-out into exactly one.
World interface impact
Additive and backward-compatible, following the sinceCursor / inline-delta precedent:
CreateEventParams: opt-in request flag (e.g. pendingStepCount: true) so backends that must do extra work only do it when asked.
EventResult: optional pendingStepCount?: number on step-terminal writes. Absent → runtime falls back to today's events.list check, so old/unsupporting Worlds are unaffected.
- world-local / world-postgres: cheap direct count. The Vercel backend can maintain a pending counter on the run row (it already patches the step row in the same write) — tracked separately server-side.
Measurement
Baseline and after: durabench parallel sweep at branches {64, 256, 1024}, plus the pnpm phases decomposition (event-log-loads phase should collapse). Expected: the 256-branch TTLS cliff drops toward the queue-transit floor.
Problem
At high fan-out, TTLS (time to last step) falls off a cliff: durabench
parallelsweeps measure ~15–20s TTLS at 256 branches vs ~1.3s at 64, on every SDK version (node engine, iad1). Trace decomposition of a representative run (wrun_41KZR0MW890GWZK34RD4Y1JBDT) shows the dominant cause is O(N²) event-log read traffic:GET /api/v4/runs/:runId/eventsp90 = 7.9s during the burst; the saturation drags the consumer prologue'sruns.getto p90 = 5.1s, smearing branch starts over ~10–15s.Proposal
Let the step-terminal write answer the question the events.list is asking. The World's
events.createforstep_completed/step_failedreturns an optional count of the run's non-terminal steps after the write (or a boolean "none pending"); the runtime then:pending > 0(return, another handler finishes later);pending === 0) proceeds — and only IT loads the log for the inline replay.That converts O(N) full-log loads per fan-out into exactly one.
World interface impact
Additive and backward-compatible, following the
sinceCursor/ inline-delta precedent:CreateEventParams: opt-in request flag (e.g.pendingStepCount: true) so backends that must do extra work only do it when asked.EventResult: optionalpendingStepCount?: numberon step-terminal writes. Absent → runtime falls back to today's events.list check, so old/unsupporting Worlds are unaffected.Measurement
Baseline and after: durabench
parallelsweep at branches {64, 256, 1024}, plus thepnpm phasesdecomposition (event-log-loads phase should collapse). Expected: the 256-branch TTLS cliff drops toward the queue-transit floor.