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 <noreply@anthropic.com>
This commit is contained in:
Matthieu 2026-07-25 22:13:03 +02:00
parent d8a2059df8
commit 792ff9709c

View file

@ -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