A delegation keeps its wait inside the round
What went wrong
A round runs under one wall-clock cap, AiStreamingLimits.MaxStreamingDuration (30 minutes in
production). When the cap fires, the round ends as an error. delegate_to_agent is exempt from the
per-tool timeout, because a delegation legitimately runs for minutes. Before this change the call had
no idea how much of the round was left.
So a round that started a delegation late was cut mid-tool-call when the cap fired. The delegation itself was healthy. What the round lost was the call's result, and the round ended in an error instead of an answer.
Measured on the control instance, response cell
Hosting/Triage/_Thread/bug-systemorph-meshweaver-plugins-2819/1a003c7b (2026-10-05): the round
spent about nine minutes before its first model call and then two model calls of 731 s and 736 s. Its
last delegate_to_agent, to a valid agent id, was 51 seconds old when the cap fired. Nothing hung.
Which bound is the authority
The round cap. It is the outer bound of a round and it is not changed here. Raising it would only move the cut. The exemption from the per-tool timeout also stays, because a delegation does run for minutes. What changes is that the delegation now knows the round's deadline and keeps its wait inside it.
The fix
When the round arms its cap, it records the deadline on the chat's execution context (
ThreadExecutionContext.RoundDeadline). The deadline is taken before the cap is armed, so it never falls after the cut.The delegation tool reads that deadline through a
DelegationRoundBudget. The budget also carries a hand-over reserve: the part of the round the call leaves for the round to finish after it returns. The reserve isAiStreamingLimits.DelegationHandOverReserve; when that is not set it is the first-update bound of a model call (EffectiveFirstUpdateTimeout, 5 minutes in production). The round needs one more model call after the delegation, so it uses that call's own bound instead of a new number.Refuse. When no more than the reserve is left,
delegate_to_agentdoes not start anything. It answers at once with a failed tool result:Error: delegation to Worker was not started — this round has 4 min 0 s of its time budget left, which is not enough to wait for a delegation. Answer with what you have now; the delegation can be started in the next round.Hand over. A started call stops waiting when only the reserve is left. It answers with the sub-thread's path. The sub-thread is not stopped: nothing in the hand-over touches it, so its work goes on and its summary lands on its own node. The result is not a failure, because the delegation is still running:
Delegation to Worker is still running in sub-thread …/_Thread/…. This round has reached the last 5 min 0 s of its time budget, so this call stopped waiting; the sub-thread was not stopped and its summary will be on that thread when it finishes. …If the launch had not yet reported its start, the result is a failed one and says the sub-thread may still appear (
list_sub_threadsshows it).A round with no known deadline waits as it always did.
Tests
DelegationRoundBudgetTest (virtual time, HistoricalScheduler):
ADelegationTheRoundHasNoTimeLeftFor_IsNotStarted: 4 minutes left, 5 minutes reserve. The call answers at once and nothing is launched.ARunningDelegation_HandsOverBeforeTheRoundCap_WithoutStoppingTheSubThread: 10 minutes left, 5 minutes reserve. One second before the hand-over the call is still waiting; at the hand-over it answers with the sub-thread's path, and the sub-thread has no cancel request.WithNoKnownDeadline_TheCallWaitsForTheDelegationis the control: an hour of virtual time changes nothing, and the delegation's ownFailedevent still settles the call.
Negative control: with the budget ignored inside the tool, the first two tests fail and the control still passes.
What this does not establish
- Whether the model, given the hand-over text, reads the sub-thread's summary in the next round. The tool says where it is; no round picks it up automatically.
- The nine-minute start stall in the 2026-10-05 round (
Agent initialization stalled) is a separate symptom and is not explained here.
Related: A delegation that never started answers the model · A delegation waits on its own sub-thread.