From 706a93e0e90a8d7ab5649a02e671b047fe93ca82 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Thu, 16 Jul 2026 16:02:41 -0700 Subject: [PATCH] [eric] browser: prefill [ so the plan aux can't waffle in prose (caught live) + read-script skips the settle-retry on results lists --- backend/apps/agents/browser/browser_agent.py | 1 + backend/apps/agents/browser/browser_plan_dispatch.py | 10 +++++++--- backend/apps/agents/browser/browser_read_script.py | 10 +++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/backend/apps/agents/browser/browser_agent.py b/backend/apps/agents/browser/browser_agent.py index 0ff980b0..fcb2bd45 100644 --- a/backend/apps/agents/browser/browser_agent.py +++ b/backend/apps/agents/browser/browser_agent.py @@ -1430,6 +1430,7 @@ async def run_browser_agent( p_aux_c, p_aux_m = await p_get_aux_client() p_read_answer = await asyncio.wait_for(browser_read_script.run_read_script( p_aux_c, p_aux_m, task, browser_id, tab_id, execute_browser_tool, + current_url=current_url, ), timeout=25.0) except Exception as p_re: logger.info(f"[browser-readscript] outer skip ({p_re})") diff --git a/backend/apps/agents/browser/browser_plan_dispatch.py b/backend/apps/agents/browser/browser_plan_dispatch.py index 4fcfa790..0f3e411d 100644 --- a/backend/apps/agents/browser/browser_plan_dispatch.py +++ b/backend/apps/agents/browser/browser_plan_dispatch.py @@ -97,11 +97,15 @@ async def run_plan_dispatch( aux_model, _ = await resolve_aux_model(settings, preferred_tier="haiku", primary_api=primary_api) client = get_anthropic_client_for_model(settings, aux_model) - reply = safe_resp_text(await asyncio.wait_for( + # Assistant prefill "[" makes prose unwritable: the aux was narrating the ambiguity instead of emitting the chosen click (caught live via the empty-plan reply log). + reply = "[" + safe_resp_text(await asyncio.wait_for( client.messages.create( model=aux_model, max_tokens=600, temperature=0, system=P_SYSTEM, - messages=[{"role": "user", "content": ( - f"Task: {task[:1200]}\n\nInteractive elements:\n{state_text[:P_STATE_CAP]}")}], + messages=[ + {"role": "user", "content": ( + f"Task: {task[:1200]}\n\nInteractive elements:\n{state_text[:P_STATE_CAP]}")}, + {"role": "assistant", "content": "["}, + ], ), timeout=P_AUX_TIMEOUT_S)) steps = parse_plan(reply) if not steps: diff --git a/backend/apps/agents/browser/browser_read_script.py b/backend/apps/agents/browser/browser_read_script.py index e802f4bb..9945c89a 100644 --- a/backend/apps/agents/browser/browser_read_script.py +++ b/backend/apps/agents/browser/browser_read_script.py @@ -11,6 +11,8 @@ import os import time from typing import Awaitable, Callable, Dict, Optional +from backend.apps.agents.browser.browser_prestage import RESULTS_URL_RE + logger = logging.getLogger(__name__) ToolRunner = Callable[[str, Dict, str, str], Awaitable[Dict]] @@ -53,13 +55,15 @@ def is_answer(reply: str) -> Optional[str]: async def run_read_script( aux_client, aux_model, task: str, browser_id: str, tab_id: str, - execute_tool: ToolRunner, + execute_tool: ToolRunner, current_url: str = "", ) -> Optional[str]: """The answer to a read task from the staged page, or None (= run the loop). Never raises; never acts on the page beyond reading it.""" t0 = time.monotonic() if aux_client is None or not aux_model: return None + # On a results LIST the miss is structural (the answer lives one click deeper), not hydration; the settle-retry would just re-decline ~3s later. + p_retries = 0 if RESULTS_URL_RE.search(current_url or "") else P_INSUFFICIENT_RETRIES try: from backend.apps.agents.core.aux_llm import safe_resp_text @@ -73,7 +77,7 @@ async def run_read_script( await asyncio.sleep(P_THIN_SETTLE_S) return "" - for ask in range(1 + P_INSUFFICIENT_RETRIES): + for ask in range(1 + p_retries): page = await p_page_text() if len(page) < P_MIN_PAGE_CHARS: logger.info(f"[browser-readscript] page too thin ({len(page)} chars); loop runs") @@ -89,7 +93,7 @@ async def run_read_script( if answer is not None: logger.info(f"[browser-readscript] answered from the staged page in {ms}ms (ask {ask + 1})") return answer - if ask < P_INSUFFICIENT_RETRIES: + if ask < p_retries: await asyncio.sleep(P_INSUFFICIENT_SETTLE_S) logger.info(f"[browser-readscript] insufficient in {int((time.monotonic() - t0) * 1000)}ms; loop runs") return None