Commit Graph
2574 Commits
Author SHA1 Message Date
ciregenzandClaude Opus 5 72c5effbff [eric] docs: measured competitive read of the browser-agent field
Numbers from this box, with the unverifiable ones marked as such.

The result that changes priorities: a deterministic accessibility-tree snapshot
(agent-browser, Rust CLI, no LLM) perceives in ~50ms median against our
BrowserFindComposer's 13,991ms median / 30,003ms p95 over 119 calls, and fills+verifies
onlinegdb in 326ms -- a site our agent cannot reach at all. Also fills w3schools (810ms)
and regex101 (31ms). That is a 40-450x perception gap with no model in the loop.

Also recorded: all nine claimed repos verified to exist with matching stars and
AGPL-compatible licences; browser-use benchmarked as an agent over 43 trials (we are
faster on every site we reach, 9.8s vs 35.7s median, but our arm ran with its fallback
disabled by dry-run so reach is not comparable); direct CDP evidence that deepl serves
our browser a Cloudflare challenge while browser-use's fresh-per-run profile is never
flagged; OpenCLI's API-first-with-browser-fallback design as the route to a learned path
that actually replays; and why Polar was not benchmarked (their ToS forbids using the
product to build a competing one, and their 98.0 concatenates two benchmarks and appears
on neither leaderboard).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 10:06:01 -07:00
ciregenzandClaude Opus 5 a982e3361f [eric] browser: an approval nobody can answer declines now instead of parking a turn for 5 minutes
The wait was a bare `timeout=300.0` at the call site: invisible, unchangeable, and
longer than WALL_BUDGET_S (180s) itself, so a single prompt could outlive the whole
run's budget.

Measured 2026-08-08. deepl bot-detected the browser profile, the agent correctly
refused to solve the challenge ("handing to the user, not solving it") and asked for
help via RequestHumanIntervention. Headless, nobody answered, so it burned the full
306s and then denied -- the identical verdict it can reach instantly. That single
wait consumed the entire 420s task budget and was the whole of what looked like a
"249s spawn stall" while profiling. Cron runs, scheduled agents, CI and benchmarks
all sit in exactly this position.

Two changes, neither of which weakens the gate:
- ws_manager.has_listener(session_id) reports whether ANY socket would receive the
  session's events, reading the same two lists send_to_session broadcasts to so it
  cannot drift from where messages actually go.
- p_request_browser_approval checks it BEFORE building a request, and declines with
  an honest reason when no UI is attached. The decision is unchanged (deny); only
  the five minutes of waiting for it are gone.

The timeout is now P_APPROVAL_TIMEOUT_S, overridable via OSW_APPROVAL_TIMEOUT_S for
automation contexts that want a different budget.

A human at the keyboard sees no change: with a socket attached the request is sent
and awaited exactly as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 09:28:53 -07:00
ciregenz 7a942fa9fa Merge remote-tracking branch 'origin/eric/dev' into eric/browser-merged 2026-08-08 09:22:33 -07:00
ciregenzandClaude Opus 5 1db0a5d04e [eric] harness: vs_bench grades zero-card trials from the backend's own dryrun-report
A trial whose card was reaped before the readback could not be verified either way,
and excluding those rows was too charitable: it flipped openswarm 60.9% -> 100% and
McNemar p=0.02 -> 1.0 purely on my read timing. The backend's own dryrun-report
(composer/textboxes/filled) is the same bar the card readback applies, so it stands
in when the card is gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 09:22:33 -07:00
ciregenzandClaude Opus 5 9f7035da6b [eric] harness: paired OpenSwarm-vs-browser-use benchmark with Wilson CIs and McNemar
Accumulating and resumable: a 95% CI narrow enough to separate two systems needs tens
of trials per site per arm, which is hours, so rows append to results/vs_raw.jsonl and
the report is computed from everything on disk. `vs_bench.py report` re-reads without
running anything.

Paired by construction: both arms run the same site back-to-back in one iteration, so
a site that changes mid-day (deepl started redirecting to a locale path during this
work) moves BOTH arms rather than one. Discordant pairs are tested with McNemar exact,
which is the right test for paired binary outcomes at small discordant counts, and
proportions use Wilson rather than the normal approximation -- at 20/20 the naive
interval is [100%, 100%], a certainty no 20-trial sample carries.

Three confounds found while building it, each of which had produced a wrong number:

1. The stop directive cannot be shared. "Do NOT submit" trips OpenSwarm's is_readonly()
   and the send-script declines outright ("read-only directive in user request"), so
   our arm measured 0/2 while browser-use scored 2/2 on the identical prompt. That is
   the prompt disabling one side, not a capability gap. OpenSwarm now gets the bare task
   and is held to reach-only by the backend's own OSW_SENDSCRIPT_DRYRUN=1; browser-use
   has no dry-run mode, so its constraint stays in the prompt. Verified at the
   classifier: is_readonly(BU)=True, is_readonly(OSW)=False.
2. Verification must be scoped to the cards a trial CREATED. A dashboard accumulates one
   card per run and they persist in the saved layout; reading "all cards" checked seven
   stale pages, and a regex101 run graded 0/1 because its own card had been reaped while
   leftovers answered instead.
3. browser-use's readback goes through ITS session (get_tabs + get_or_create_cdp_session)
   rather than a second websocket. Every earlier verifier bug -- hardcoded port, ws:// vs
   http://, page-vs-webview target filtering -- lived in hand-rolled target discovery.

The browser is deliberately NOT held constant. browser-use cannot screenshot an Electron
webview and falls back to the app's page target on 100% of steps (49/49 measured), so a
shared browser blinds it; each side runs in the browser it was built for and that is
recorded as a caveat rather than hidden.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 00:21:28 -07:00
ciregenz bb906d81b8 [eric] apps: a frozen app runtime is thawed before it is reaped, so a parked ghost stops surviving every TERM we send it v1.7.5 2026-08-07 21:48:58 -07:00
ciregenz b537a4ed63 [eric] apps: the ghost reaper reads working directories too, so an orphaned app backend whose argv never names the workspace stops being invisible 2026-08-07 21:36:51 -07:00
ciregenzandClaude Opus 5 56c113462a [eric] harness: OSW_CDP_PORT exposes Electron's CDP so a rival agent can share our browser
Added to compare browser-use against us with the BROWSER held constant, and the
result is that it cannot be held constant, which is worth writing down.

What works: with the port exposed, browser-use attaches, sees the browser cards
(they are CDP targets of type 'webview'), navigates one, and completes a task that
verifies independently. Cards persist across restarts because they live in the saved
dashboard layout, so there is something to attach to.

What does not: browser-use's screenshot path cannot capture a webview and falls back
to the app's own 'page' target -- 49 fallbacks in 49 agent steps, i.e. it drove the
web page while LOOKING at the OpenSwarm UI for every single step. A comparison run
that way handicaps the rival for an infrastructural reason and measures neither
agent, so the shared-browser numbers are not publishable as a fair head-to-head.

Two hazards found the hard way, both worth knowing before anyone repeats this:
- browser-use filters CDP targets to page/tab, so 'webview' is invisible to it, and
  Electron refuses Target.createTarget ("Not supported") so it cannot open its own.
  Pointed at Electron unrestricted it therefore grabs the app's page target and
  navigates the OpenSwarm UI away, killing the renderer: the next sweep failed
  infra_no_renderer 1/1 because of exactly that.
- An external agent must never be allowed to kill the shared session. Doing so after
  trial 1 made trials 2-15 fail ConnectError, which a naive summary published as
  "browser-use REACH 4/15" -- ten rows of my own teardown scored against the agent.

Off by default: an open debugging port is a local attack surface, and this only
exists for measurement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 21:14:58 -07:00
ciregenz 01e295ac5c [eric] agents: parked CLIs die at 10 minutes and the warm cap drops to 8, cutting the measured 1.3GB idle ceiling roughly in half 2026-08-07 21:10:44 -07:00
ciregenz ec9941821c [eric] backend: respawn on unexpected exit (bounded, backoff), so a crash no longer leaves a dead shell and orphaned app runtimes 2026-08-07 21:02:08 -07:00
ciregenz 08694d18b8 [eric] apps: the ghost reaper matches workspace paths case-insensitively, so a packaged app's openswarm vs OpenSwarm casing no longer spares orphans 2026-08-07 21:00:44 -07:00
ciregenz ed8e7d6304 [eric] updater: OPENSWARM_NO_UPDATE=1 stops a local packaged build from downgrading itself mid-smoke 2026-08-07 20:30:05 -07:00
ciregenz 2bd42f7f35 [eric] tests: the sync-test contract tests now pin the progress-based wait, not the deleted fixed budget 2026-08-07 20:27:10 -07:00
ciregenz 8e9baee839 [eric] workflows: at most 3 run at once, extra due fires queue for a slot and give up honestly, so 30 saved workflows can never become 30 live agents 2026-08-07 20:26:27 -07:00
ciregenz 256e922886 [eric] canvas: browsers and apps share one 10-renderer ceiling, so the two caps can no longer stack to 14 2026-08-07 20:10:59 -07:00
ciregenz dfed363db7 [eric] apps: the scaffold ships a durable store and the skill forbids memory-only state, since the runtime dies on a timer 2026-08-07 20:01:06 -07:00
ciregenz dec16d78db [eric] apps: the ghost sweep fails closed, a bad ps scan or a mid-spawn race can no longer read working apps as ghosts 2026-08-07 19:54:11 -07:00
ciregenz f32bcafc01 [eric] apps: idle pool holds 6 frozen runtimes instead of 3, affordable now that the TTL bounds their lifetime 2026-08-07 19:51:25 -07:00
ciregenz 913f597656 [eric] apps: ghosts get swept every 10 minutes not just at boot, and a frozen idle runtime dies after 15 minutes instead of squatting RAM forever 2026-08-07 19:47:16 -07:00
ciregenz a276a5d64d [eric] canvas: a browser leaving the rail lands on its snapshot and attaches the guest after the motion, killing the browser-only jank 2026-08-07 19:28:17 -07:00
ciregenz 8c8a9fe120 [eric] canvas: a card leaving the minimized rail lands in its tile in one frame, instead of flying in from a home nobody saw 2026-08-07 19:22:19 -07:00
ciregenz ab875d5894 [eric] canvas: the tiling settle waits for the camera to demonstrably rest, not for a guessed delay 2026-08-07 19:13:57 -07:00
ciregenz c8df096520 [eric] canvas: revert the top inset, the tiled window already sits above the drag strip and did not need it 2026-08-07 19:09:24 -07:00
ciregenz c8dd05ddf4 [eric] canvas: tiling insets below the top drag strip, so a fullscreen window stops losing its top edge to window-drag 2026-08-07 19:00:58 -07:00
ciregenz 863a8274f9 [eric] canvas: stop re-solving a tile origin that was already right, which is what put fullscreen a few px off 2026-08-07 18:57:12 -07:00
ciregenz 85e50f029a [eric] canvas: a browser card too small to read goes to sleep on zoom-out, not just stays asleep on zoom-in 2026-08-07 18:43:42 -07:00
ciregenz 31a45b003a [eric] canvas: a tiling settle waits for the camera to stop moving, and re-measures once the rail lets the card go 2026-08-07 18:25:08 -07:00
ciregenz dc1896c91f [eric] canvas: tiling diagnostics log one flat line, since devtools ate the nested arrays on copy 2026-08-07 18:17:53 -07:00
ciregenz 088a31973b [eric] canvas: temporary tiling diagnostics, so the minimized-to-fullscreen offset can be read off numbers 2026-08-07 18:12:58 -07:00
ciregenz 901d9ff070 [eric] browser: back, forward and reload lose their tooltips, which only ever landed on top of the page 2026-08-07 18:10:25 -07:00
ciregenz df85b17100 [eric] canvas: fullscreen from the minimized rail lands square, instead of taking its origin from the -100000 park 2026-08-07 18:02:21 -07:00
ciregenz 0417e70b72 [eric] memory: the profile is about the person, not the project, and saying nothing is the default 2026-08-07 17:53:40 -07:00
ciregenz 0c83bb8f78 [eric] canvas: an app window owns its wheel whether or not you clicked in first; workflow tests wait on progress, not a clock 2026-08-07 17:37:30 -07:00
ciregenz 9b54b39dde [eric] workflows: a test that runs long hands back what it has instead of telling the agent to poll; memory stops re-deriving facts it already stored 2026-08-07 17:33:20 -07:00
ciregenz bde92de062 [eric] prompt: reading the web starts with WebSearch/WebFetch and only escalates to the browser when they come back thin 2026-08-07 17:18:30 -07:00
ciregenz 050a02fc52 [eric] workflows: a new workflow runs on the model you actually configured, not Anthropic's API lane 2026-08-07 17:06:48 -07:00
ciregenz b16c31b8c2 [eric] canvas: scroll over an unfocused card drives the canvas again, and a scroll inside one never chains out of it 2026-08-07 16:52:55 -07:00
ciregenz 6d8ecc6fd4 [eric] canvas: the host window allows visual zoom, so macOS actually delivers a trackpad pinch to the dashboard (ENG-186) 2026-08-07 16:24:07 -07:00
ciregenz b37509894d [eric] tests: two suites imported a .js that never existed, so 13 assertions had never run once 2026-08-07 16:06:57 -07:00
ciregenz 9b0ca9f2fc [eric] canvas: double-click to frame everything keeps a readable floor instead of bottoming out at 15% 2026-08-07 16:04:42 -07:00
ciregenz 5b63c98854 [eric] dictation: the cursor wins, so a transcript lands where you clicked, not where you started (reverts ENG-176) 2026-08-07 16:01:05 -07:00
ciregenz d1d8597513 [eric] help: the 1.7.5 notes describe the build that exists, not the one that was planned 2026-08-07 15:52:28 -07:00
ciregenz 69797ba88a [eric] workflows: pausing one clears its queued missed fires, so a switched-off workflow leaves nothing behind 2026-08-07 15:47:31 -07:00
ciregenz 4293904214 [eric] canvas: the stock wash is one flat colour, so a default install has no texture left to tear 2026-08-07 15:41:07 -07:00
ciregenz 5485021b7b [eric] workflows: a stalled label lane can no longer hold a step edit open, which is what bricked the editor 2026-08-07 15:41:07 -07:00
ciregenz c0117d817b [eric] voice: reap fn-watchers stranded by a crash, so a dead session can't keep tapping the keyboard 2026-08-07 15:31:09 -07:00
ciregenz ec0d706a80 [eric] canvas: cut the evictable full-window texture 98MB to 43MB, so the wash has far less left to drop as a band 2026-08-07 15:23:43 -07:00
ciregenz f018eefdb9 [eric] workflows: a deleted workflow stays deleted, so the immortal 45-minute ghost can't write itself back to life 2026-08-07 15:05:42 -07:00
ciregenz e53d01f9ef [eric] canvas: wash and grain paint on the viewport itself, so there is no second layer to lose 2026-08-07 14:45:43 -07:00
ciregenz 8364864f9f [eric] canvas: the wash is a real gradient, not a 119KB image the GPU can drop 2026-08-07 14:41:08 -07:00