From 792ff9709c3f000a0f99f6a185b30a206e659a49 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sat, 25 Jul 2026 22:13:03 +0200 Subject: [PATCH] fix(agent): tell the model that navigating a page is not reading it browser_navigate returns the page title and a snapshot reference. It never returns the page text. Asked to open a URL and summarise it, the agent called browser_navigate, saw exit_code=0, answered "the page loaded successfully" and stopped -- having read nothing. It looks like a successful turn, and the tool log agrees, so nothing flags it. browser_snapshot has the same trap one level down. Its optional filename argument is documented as "Save snapshot to markdown file instead of returning it in the response". The model volunteered a filename and so received a path it had no way to open: 175 characters of reference where omitting the argument returns 1634 of content. Adds a note covering both. Navigate is never the last step, follow it with browser_snapshot or browser_find; call snapshot with no arguments; and prefer web_fetch when the task is only to read a page, since it does the whole thing in one call rather than three round trips -- which matters on local hardware where each round trip is a full generation. Injected only when browser tools are in that turn's selection, and suppressed with the other local-context blocks, so it costs nothing on unrelated requests. Verified present with browser tools in the set and absent without. Observed after the change: the agent that previously stopped at "the page loaded" went on to call browser_snapshot on its own. Co-Authored-By: Claude Opus 5 --- src/agent_loop.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/agent_loop.py b/src/agent_loop.py index 592ebaec1..ebf9ba41a 100644 --- a/src/agent_loop.py +++ b/src/agent_loop.py @@ -2412,6 +2412,29 @@ def _build_system_prompt( 'that open draft is the target: use update_document/edit_document on it instead of creating another document.' ) + # Browsing is a two-step tool and models routinely stop after step one. + # browser_navigate returns the page TITLE and a snapshot reference — never + # the page text. Observed: asked to open a URL and summarise it, the agent + # called browser_navigate, saw exit_code=0, reported "the page loaded + # successfully", and stopped, having read nothing. browser_snapshot has the + # same trap one level down: its optional `filename` argument saves the + # snapshot to a file INSTEAD of returning it, and models volunteer one. + if relevant_tools and not suppress_local_context and any( + "browser_" in str(_t) for _t in relevant_tools + ): + agent_prompt += ( + "\n\n🌐 READING A WEB PAGE: browser_navigate only OPENS the page — it returns the " + "title and a snapshot reference, never the page text. It is never the last step. " + "To read the content you must then call browser_snapshot (or browser_find to look " + "for specific text). Reporting that a page 'loaded successfully' is not an answer: " + "the user asked what is ON the page. " + "Call browser_snapshot with NO arguments. Do NOT pass 'filename' — that writes the " + "snapshot to a file INSTEAD of returning it, so you get a path you cannot read and " + "the page content is lost. " + "When you only need to read a page and not interact with it, web_fetch does it in a " + "single call and is preferred." + ) + # Inject relevant skills based on the user's last message. The # SkillsManager does a Jaccard token-match over published skills' # name + description + when_to_use + procedure, returning the top