From 01ec866c2a95b7947dc75ac5bef4a0421e20d54d Mon Sep 17 00:00:00 2001 From: ciregenz Date: Sat, 6 Jun 2026 12:12:38 -0700 Subject: [PATCH] [eric] browser: OSW_BROWSER_NO_LEVERS bench flag to A/B the speed levers on/off --- backend/apps/agents/browser/browser_agent.py | 11 +++--- backend/apps/agents/browser/browser_schema.py | 36 +++++++++++++------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/backend/apps/agents/browser/browser_agent.py b/backend/apps/agents/browser/browser_agent.py index 907e2215..916ff57f 100644 --- a/backend/apps/agents/browser/browser_agent.py +++ b/backend/apps/agents/browser/browser_agent.py @@ -63,6 +63,7 @@ from backend.apps.agents.browser import browser_wait from backend.apps.agents.browser import browser_schema from backend.apps.agents.browser.browser_schema import ( _ACTION_TOOLS_REQUIRING_REPORT, + _LEVERS_ON, ACTION_MAP, BROWSER_TOOLS_SCHEMA, MAX_TURNS, @@ -802,7 +803,7 @@ async def run_browser_agent( # wording), but a similar verified route may exist; hand it to the model as # advisory text so it follows a known path instead of re-exploring. route_hint_keys: list[tuple] = [] - if not replay_prefix_note: + if _LEVERS_ON and not replay_prefix_note: _h_skill, _h_score = browser_skills.find_similar_skill(replay_host, skill_key_task) if _h_skill: _hint, route_hint_keys = browser_skills.render_route_hint(_h_skill, skill_key_task, _h_score) @@ -816,7 +817,7 @@ async def run_browser_agent( # Pre-nav landed on a results page (the cold entry case): scan it NOW so the # model's very first turn can pick a candidate instead of read-then-decide. _start_url = (current_url or initial_url or "").split("#")[0] - if _start_url and _RESULTS_URL_RE.search(_start_url): + if _LEVERS_ON and _start_url and _RESULTS_URL_RE.search(_start_url): auto_scanned_urls.add(_start_url) _scan_json, _sc_ms = await _scan_results(task) if _scan_json: @@ -1442,7 +1443,7 @@ async def run_browser_agent( # per URL, only on the tight throwaway-dismiss vocabulary that # never sits on a task-needed control, so it can't close anything # required. After closing, re-list so the model sees the page beneath. - if tu.name in _AUTO_STATE_TOOLS and "error" not in result: + if _LEVERS_ON and tu.name in _AUTO_STATE_TOOLS and "error" not in result: _pop_url = (result.get("url") or last_seen_url or "").split("#")[0] if _pop_url and _pop_url not in dismissed_popup_urls: _close = interstitial_dismiss_target("\n".join(attached_state_seen)) @@ -1464,7 +1465,7 @@ async def run_browser_agent( # costs a read-then-decide turn pair; the cheap aux model reads it # now so the pick happens on this same turn. Capped, per-URL, # fail-silent (a miss just means the old two-turn dance). - if (tu.name in _AUTO_STATE_TOOLS and "error" not in result + if (_LEVERS_ON and tu.name in _AUTO_STATE_TOOLS and "error" not in result and auto_scan_count < _AUTO_SCAN_MAX_PER_RUN): _scan_url = (result.get("url") or last_seen_url or "").split("#")[0] if _scan_url and _scan_url not in auto_scanned_urls and _RESULTS_URL_RE.search(_scan_url): @@ -1513,7 +1514,7 @@ async def run_browser_agent( if replay_prefix_note: result["text"] = f"{result.get('text') or ''}{replay_prefix_note}" replay_prefix_note = "" - elif not route_hint_keys: + elif _LEVERS_ON and not route_hint_keys: _h_skill, _h_score = browser_skills.find_similar_skill(cur_host, skill_key_task) if _h_skill: _hint, route_hint_keys = browser_skills.render_route_hint(_h_skill, skill_key_task, _h_score) diff --git a/backend/apps/agents/browser/browser_schema.py b/backend/apps/agents/browser/browser_schema.py index 4d37a35b..f4ac20f7 100644 --- a/backend/apps/agents/browser/browser_schema.py +++ b/backend/apps/agents/browser/browser_schema.py @@ -6,6 +6,30 @@ prompt, and the turn/report invariants. Exceeds the 300-LOC soft ceiling on purpose because it is one cohesive data blob, not multiple responsibilities. """ +import os + +# Bench-only A/B switch: OSW_BROWSER_NO_LEVERS=1 reverts the prompt to its +# pre-speed-lever behavior (narration allowed beside actions, no merge-verify +# shortcut) so a controlled run can measure whether the levers move the needle. +# Default (unset) = levers ON, the shipped behavior. +_LEVERS_ON = os.environ.get("OSW_BROWSER_NO_LEVERS", "") not in ("1", "true", "TRUE") + +_THINK_SHORTER = ( + "Do NOT also write a free-text sentence next to your action tools: your ReportProgress " + "fields ARE your thinking, and a separate prose explanation just repeats them and slows " + "the turn (it is shown to the user twice). The ONLY time to write a plain message is your " + "FINAL turn, when the task is done and you call no action tool: that message is your " + "answer to the user (the OUTCOME line). Every other turn: ReportProgress + tools, no prose.\n" +) if _LEVERS_ON else "" + +_MERGE_VERIFY = ( + "When that `expect` CONFIRMS (the result says 'Confirmed: ...'), that IS your " + "verification: go STRAIGHT to your final OUTCOME line and cite it. Do NOT spend an " + "extra screenshot or read turn to re-check what the confirmation already proved, that " + "is a wasted round-trip. Only take a separate verification step when `expect` came " + "back 'NOT confirmed' or you forgot to pass one.\n" +) if _LEVERS_ON else "" + MODEL_MAP = { "sonnet": "claude-sonnet-4-6", "opus": "claude-opus-4-6", @@ -621,11 +645,7 @@ SYSTEM_PROMPT = ( "needs (the exact selector, index, or value). Each token you write is generated one at a " "time and is the main thing that slows a turn, so write the fewest that still carry the " "plan forward. Only write working_memory when you learn something NEW this turn; else 'none'.\n" - "Do NOT also write a free-text sentence next to your action tools: your ReportProgress " - "fields ARE your thinking, and a separate prose explanation just repeats them and slows " - "the turn (it is shown to the user twice). The ONLY time to write a plain message is your " - "FINAL turn, when the task is done and you call no action tool: that message is your " - "answer to the user (the OUTCOME line). Every other turn: ReportProgress + tools, no prose.\n" + + _THINK_SHORTER + "Emit ReportProgress and your action tool(s) together in the same response. " "If you skip ReportProgress, your action tools will be REJECTED with an error " "and you will have to retry. This is not optional. Read-only tools " @@ -642,11 +662,7 @@ SYSTEM_PROMPT = ( "last one in the thread), pass `expect` set to proof it landed, and NEVER fire it a " "second time unless you have verified the first did NOT go through. This is how you " "avoid both ghost-successes and double-sends.\n" - "When that `expect` CONFIRMS (the result says 'Confirmed: ...'), that IS your " - "verification: go STRAIGHT to your final OUTCOME line and cite it. Do NOT spend an " - "extra screenshot or read turn to re-check what the confirmation already proved, that " - "is a wasted round-trip. Only take a separate verification step when `expect` came " - "back 'NOT confirmed' or you forgot to pass one.\n\n" + + _MERGE_VERIFY + "\n" "## Loop awareness\n" "If you see a tool result containing 'LOOP DETECTED' or '⚠️', it means you "