Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,9 +497,11 @@ from hanging.** The contract, precisely:
- Tickets live for the tab's lifetime. There is no server, so there is nothing to expire against; a
reload retires every one of them.

The timeout is `GATE_TIMEOUT_MS`, currently 25 s. That number is a guess chosen to be survivable, not
a measurement — `blocking.ts` says so at the definition. Measure the real tolerance of the hosts you
target and set it comfortably under whatever you measure.
The timeout is `GATE_TIMEOUT_MS`, currently 8 s, and that number is a measurement. A 25 s gate lost a
`propose_hypotheses` call outright: the ChatGPT in-app browser cut the agent's control connection at
roughly 20 s, so the host gave up before the gate returned its ticket — the card rendered and the
human's decision was recorded, but the agent received neither. Re-measure before targeting a host with
a different tolerance, and keep the value comfortably under whatever you measure.

### 12. `ask_human_visual({ question, choices, hintAtMs?, ticket? })`

Expand Down
17 changes: 12 additions & 5 deletions traces/src/lib/webmcp/blocking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,22 @@ import type { Gate, GateResult } from '@/types/domain'
* if they aren't. The agent then retries with the ticket, which reads to the model as a normal
* polling loop rather than a broken page.
*
* Measure the real tolerance of the agents you target before trusting this number, and set
* GATE_TIMEOUT_MS comfortably under whatever you measure.
* The ChatGPT in-app browser has since been measured at roughly 20 s before it severs the connection,
* which is what `GATE_TIMEOUT_MS` is set against. Re-measure before targeting a different host.
*/

/**
* TODO: Day 1 — replace with the measured value from S1, minus a healthy margin.
* 25s is a guess chosen to be survivable rather than correct — it is not a measurement.
* Measured, not guessed. A 25 s gate lost a `propose_hypotheses` call outright: the ChatGPT in-app
* browser severed the agent's control connection at roughly 20 s, so the host gave up before the gate
* could hand back its ticket. The page had already rendered the card and the human's decision was
* still recorded — the agent just never received either, which is the one outcome the contract exists
* to prevent.
*
* 8 s sits far enough under that ceiling to survive a slower host, and costs nothing: a human who is
* already looking at the screen answers inside it, and one who isn't was always going to arrive by
* ticket. Raising this back up trades a guaranteed reply for a slightly shorter happy path.
*/
export const GATE_TIMEOUT_MS = 25_000
export const GATE_TIMEOUT_MS = 8_000

/** One tool call currently awaiting this question. An agent that polls produces several over time. */
type Waiter<T> = {
Expand Down
2 changes: 1 addition & 1 deletion traces/src/lib/webmcp/tools/blocking-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { snapshotFindingTool } from './snapshot-finding'
* failure path come back as a sentence rather than a hang.
*
* Every blocking case here settles through the store — a human answering — rather than by waiting out
* GATE_TIMEOUT_MS, which is 25 seconds and would make this suite unusable. The timeout path itself is
* GATE_TIMEOUT_MS, which would add its full duration to every test. The timeout path itself is
* already pinned in blocking.test.ts; what these tests add is that the *store* half is connected to it,
* because a gate nobody can answer and a gate nobody is watching look identical from the agent's side.
*/
Expand Down
Loading