Background
RunManager currently evaluates wallClockMs inside applyBudget(), which is called only after an AgentEvent arrives from the runtime stream. If the async iterator remains pending before the first event—or between two events—the run can stay running indefinitely and never reach its wall-clock safeguard.
This is a reliability gap in the runtime contract added for #17: a wall-clock budget should bound elapsed time, including time spent waiting for the next streamed event.
Expected behavior
- An enabled
wallClockMs budget expires even when the runtime emits no new events.
- Expiration cancels the matching runtime run exactly once.
- The persisted run ends with
status=cancelled, stopReason=budget_exhausted, and stopDetail identifying wallClockMs with used >= limit.
- Any assistant partial output and completed tool calls emitted before the stream stalls remain persisted.
- Natural completion and user cancellation clear the deadline.
- A stale deadline from an earlier run cannot affect a later run.
Suggested scope
- Add an explicit deadline to the
RunManager lifecycle and share one idempotent safeguard-stop path with event-driven budget checks.
- Add deterministic tests for a stream stalled before its first event, a stream stalled after partial output, and deadline cleanup / run isolation.
- Keep the public Core, IPC, and UI contracts unchanged.
Non-goals
- Rendering
stopReason / stopDetail in the UI.
- Adding or wiring retry-storm signals.
Related to #17.
Background
RunManagercurrently evaluateswallClockMsinsideapplyBudget(), which is called only after anAgentEventarrives from the runtime stream. If the async iterator remains pending before the first event—or between two events—the run can stayrunningindefinitely and never reach its wall-clock safeguard.This is a reliability gap in the runtime contract added for #17: a wall-clock budget should bound elapsed time, including time spent waiting for the next streamed event.
Expected behavior
wallClockMsbudget expires even when the runtime emits no new events.status=cancelled,stopReason=budget_exhausted, andstopDetailidentifyingwallClockMswithused >= limit.Suggested scope
RunManagerlifecycle and share one idempotent safeguard-stop path with event-driven budget checks.Non-goals
stopReason/stopDetailin the UI.Related to #17.