perf(web): stop paying a process-table scan on every agent-browser call
Summary
Every managed web call re-derived the daemon's owned-process record with a whole-process-table scan (ps -ax), for a browser fleet that only changes when open launches it, close ends it, record starts or stops the encoder, or a failure finds it died or restarted.
Plain successful commands now pay one batched same-pid identity probe. The gate still refreshes on any failure and on open/close/record, and stays fail-closed: an empty, undecodable, unreadable, or unanswered record falls back to the scan, so a fleet adopted from a predecessor daemon becomes owned through the record again. The probe reads the host off the event loop, so the daemon no longer blocks other requests, timers, and abort handling behind a spawnSync. 13 files.
agent-device open http://localhost:3000 --platform web
agent-device snapshot -i --platform web # no process-table scan
Refs #3503 — requirement 2 (classifying details.dispatched for web failures) is #3542.
Validation
Tested a1fa5ae70. pnpm check:affected --run passed on that SHA (format, lint, typecheck, layering, fallow, build, related vitest; 868 files / 7,157 tests); coverage and device lanes are GitHub-authoritative.
Refs-mode per-op latency through the CLI against one local page, 5 interleaved runs per side, median of per-run medians (base 46dae0f2e → head 5446b9c67, load average 10–20):
| op | base | head |
|---|---|---|
snapshot -i | 624 | 315 |
fill @ref | 630 | 317 |
click @ref | 652 | 318 |
screenshot | 585 | 347 |
| mean/op | 1131 | 856 |
The saving scales with the process table: measured directly, ps -ax costs 378 ms median on a 2,563-row table against 4–15 ms for the probe, and it hit its 1,500 ms budget under parallel-agent load. At load average 90–230 the same ops measured 1.3–1.9 s/op faster (7cb2c3d21, this change before its review fixes). wait text polls (~3.4 s) and open is Chrome-startup-bound, so both stay backend-bound; close clears the record without scanning and is unchanged. close clearing and a failed close not clearing are asserted; the reaper and leak oracle read the same record shape.
The head after 5446b9c67 adds no measured hot-path cost — its only extra scans are one per record boundary, in code paths the table above does not time — so those numbers still describe the measured ops.
Findings applied: probe moved off the event loop, oversized test file split, unused re-export removed, unreadable record store fails closed to a scan (86b161d81), windowsPlatform no longer leaks win32 to its worker (6943e284c), and record added to the lifecycle set (2ed0820fa): the pinned agent-browser 0.27.1 mints an ffmpeg child of the agent-browser daemon on record start, and that encoder outlives the CLI call which started it, so it sits outside Chrome's tree and only a scan reached by a lifecycle boundary can put it in the record the startup reaper signals — a probe over already-recorded pids cannot learn of a process the fleet minted after the last scan.
That head also adds a gate test over a real record store, the real probe, and real ps answers: a run of plain commands leaves the recorded fleet in the file untouched and mints nothing, and a fleet the host no longer names sends every later plain command back to the scan (a1fa5ae70 keeps those cases from passing without a subject).