…spoken
A turn ended two seconds after silence, measured from the first reply of any
kind and never re-measured. One flag was set on the first substantive event and
never cleared, so everything after it ran on the two-second timeout — including
a tool call, where silence is what a running call looks like. An agent that
said "placing the call now" and then placed it was cut off from its own result.
The flag is replaced by a question asked of every event: was the last thing to
arrive the agent speaking? Text, a document, a form, a result, a restriction —
those start the two-second clock. Anything else clears it and the wait stays
long. `session:tool_status` moves out of that set for the same reason it caused
the bug: it reports what the agent is doing, not what it says.
## Where the rule stops
Nothing else ends a turn, so a turn whose last event is neither speech nor a
settled state waits indefinitely. Three plain turns recorded against a live
server each ended on `session:text`, and a turn that ran a task ended on a
settled state, but nothing guarantees either. `turn_timeout` bounds a turn in
wall-clock seconds and keeps whatever arrived before the deadline; it defaults
to none, so no existing caller changes behaviour. A test states the boundary
rather than leaving it to be discovered.
## Session states
`credits_exhausted` and `task_paused` now end a turn. Both stop on the account
rather than on the agent, so nothing further arrives from the session itself.
`task_stale` is dropped: the server has no such state. Staleness is `is_stale`
on the session object, read over REST — the two were conflated.
## Tests
The flow tests closed a turn by sending `session:input_state`, which left the
supported scope in the previous change and no longer ends anything. They now
close on a settled `session:state`, which is explicit rather than timing-based.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
A turn ended two seconds after silence, measured from the first reply of any kind and never re-measured. The check is now asked of every event: was the last thing to arrive the agent speaking?
This also adds turn_timeout, a wall-clock bound on one turn, and corrects the set of session states that end one.
The defect
received_agent_response was set by the first substantive event and never cleared. From that point the whole turn ran on the two-second timeout, including the part where the agent is working rather than talking — and silence is exactly what a running tool call looks like.
agent: "Placing the call now." -> flag set, wait drops to 2s session:tool_status -> flag unchanged (call runs, 30s of silence) -> 2s elapses, turn endsThe caller was cut off from the result of the operation it had just been told about.
The rule
session:tool_status moves out of the first set for the same reason it caused the defect: it reports what the agent is doing, not what it says.
Where the rule stops
Nothing else ends a turn, so a turn whose last event is neither speech nor a settled state waits indefinitely.
Three plain turns recorded against a live server each ended on session:text, and a turn that ran a task ended on a settled state. Neither is guaranteed. turn_timeout bounds a turn in wall-clock seconds and keeps whatever arrived before the deadline:
It defaults to None, so no existing caller changes behaviour. A test states this boundary rather than leaving it to be discovered.
The name deliberately describes the bound rather than the mechanism, so it survives a change of mechanism. The two internal timeouts stay internal.
Session states
credits_exhausted and task_paused now end a turn. Both stop on the account rather than on the agent, so nothing further arrives from the session itself.
task_stale is dropped. The server has no such state — its session states are init, chat, task_ready, task_processing, task_interrupted (deprecated), task_finished, task_cancelled, task_paused and credits_exhausted. Staleness is is_stale on the session object over REST; the two were conflated.
Tests
The flow tests closed a turn by sending session:input_state, which left the supported scope in #3 and no longer ends anything. They now close on a settled session:state — explicit rather than timing-based, and no longer dependent on an unsupported event.
Four tests cover the new rule: a turn that ends on speech, a running tool that is not mistaken for a finished turn, a session stopped on its balance, and the unbounded case with and without turn_timeout.
Verification
🤖 Generated with Claude Code