From 014de0ec21936a7a2fd64db9d151e73c79bb44ad Mon Sep 17 00:00:00 2001
From: Patrick Barattin
Date: Sun, 11 Oct 2026 20:25:38 +0200
Subject: refactor: remove duplicate browser prompt instructions (#35)
Remove browser instruction text repeated across tool descriptions, parameter schemas, tool
guidelines and the global browser prompt. Tool names, schemas, handlers and concurrency are
unchanged; shared recovery and ownership guidance is now available before and after the browser
connects, and the prompt names which tools are serialized versus observations.
Co-Authored-By: Claude Fable 5.1
---
CHANGELOG.md | 4 ++
CONTRIBUTING.md | 2 +-
README.md | 8 +++
src/domains/click.ts | 5 --
src/domains/console.ts | 2 -
src/domains/files.ts | 2 +-
src/domains/form-batch.ts | 1 -
src/domains/form.ts | 10 +--
src/domains/history.ts | 6 +-
src/domains/js.ts | 6 +-
src/domains/keyboard.ts | 9 +--
src/domains/navigate.ts | 3 -
src/domains/network.ts | 4 +-
src/domains/page.ts | 8 +--
src/domains/readpage/read-page.ts | 1 -
src/domains/screenshot.ts | 4 --
src/domains/scroll.ts | 1 -
src/domains/search/web-search.ts | 1 -
src/domains/setup.ts | 2 -
src/domains/snapshot.ts | 5 +-
src/domains/tabs.ts | 10 +--
src/domains/viewport.ts | 1 -
src/index.ts | 11 +--
src/prompt.ts | 145 ++++++--------------------------------
test/util/prompt-guidance-test.ts | 72 +++++++++++++++++++
25 files changed, 122 insertions(+), 201 deletions(-)
create mode 100644 test/util/prompt-guidance-test.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f0778b..aaf0a11 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to pi-browser-harness will be documented in this file.
## Unreleased
+### Changed
+
+- **Browser guidance no longer repeats tool declarations.** The browser prompt now contains only cross-tool coordination, available before and after connection, and tool guidelines omit usage and argument details already present in descriptions and schemas. Tool names, schemas, handlers, and safety warnings are unchanged.
+
### Added
- **Per-tab extra HTTP request headers.** `browser_set_headers` applies headers to subsequent document, subresource, fetch, and XHR requests on the current owned tab through CDP, while `browser_clear_headers` removes them. Header values are not echoed in tool output, and guidance requires clearing them before switching tabs or navigating to another origin.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index aa73342..8436e4f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -87,7 +87,7 @@ Use the normal browser workflow when testing: screenshot → act → screenshot
- No unchecked casts. `npm run boundaries` fails the build on `as any`, `as {`, `as unknown as`, `@ts-ignore`, and non-null assertions in `src/`. A green `tsc` is not evidence of type safety.
- Prefer no comments. Explain a non-obvious convention in the commit message or PR description, not inline.
- Prefer small, focused changes over broad rewrites.
-- Keep tool descriptions, parameter descriptions, and prompt guidelines clear and user-facing.
+- Keep tool descriptions and parameter schemas clear and user-facing. Use prompt guidelines only for decisions and caveats not already covered there. Keep `src/prompt.ts` limited to cross-tool coordination; leave recipes in the README and skill.
- Preserve the existing error-handling style: return useful, actionable messages to the agent instead of leaking low-level details when possible.
- Avoid adding dependencies unless they are necessary for the browser-control experience.
- Keep README and CHANGELOG updates in the same PR when behavior changes.
diff --git a/README.md b/README.md
index fb7d072..a1fd9b2 100644
--- a/README.md
+++ b/README.md
@@ -353,6 +353,14 @@ A long-lived daemon owns the CDP connection, so it outlives individual pi sessio
you only see Chrome's "Allow Remote Debugging" prompt once. `browser_run_script` executes in the
harness process with direct daemon and Node access.
+### Prompt guidance
+
+Tool descriptions and parameter schemas own per-tool usage. Tool guidelines add only decisions
+and caveats not already covered there. The browser prompt carries cross-tool rules:
+tab isolation, concurrency, checking mutations, and stale-ref recovery. These rules are available
+before and after connection. Longer recipes remain in this README and the bundled skill
+rather than being copied into the browser prompt.
+
---
## Troubleshooting
diff --git a/src/domains/click.ts b/src/domains/click.ts
index 79bb5ee..47d6dc5 100644
--- a/src/domains/click.ts
+++ b/src/domains/click.ts
@@ -58,11 +58,6 @@ export const clickTool = defineBrowserTool({
"Click an element. PREFERRED: pass `ref` (e.g. 'e12') from browser_snapshot — it re-resolves the element's position at click time, so it works even after the page re-renders and moves things. Fallback: pass viewport CSS-pixel `x`/`y`. Compositor-level click works through iframes, shadow DOM, and cross-origin content. After clicking, a compact diff of page changes is appended.",
promptSnippet: "Click an element by ref (preferred) or pixel coordinates",
promptGuidelines: [
- "PREFER `ref` from browser_snapshot (the outline shows '[eN]' for every interactive element) — it survives re-renders, unlike coordinates which go stale after a save/edit reflows the page.",
- "Fallback: pass (x, y) from the snapshot's '@(x,y)' hint when there's no ref.",
- "A 'ref is stale' error means the page changed — re-run browser_snapshot to get fresh refs.",
- "After clicking, read the appended page-changes diff to confirm the action landed before moving on (no separate snapshot needed for a quick check).",
- "Coordinates are viewport CSS pixels (not device pixels). Compositor-level clicks pass through iframes, shadow DOM, and cross-origin content.",
"If a click doesn't register, run pi with --browser-debug-clicks (or set BH_DEBUG_CLICKS=1) to get annotated screenshots. For React/Vue components ignoring clicks, try browser_dispatch_key.",
],
parameters: ClickArgs,
diff --git a/src/domains/console.ts b/src/domains/console.ts
index 7809f77..799fe2e 100644
--- a/src/domains/console.ts
+++ b/src/domains/console.ts
@@ -158,8 +158,6 @@ export const consoleTool = defineBrowserTool({
"Read JS errors and console messages from the active tab. Diagnostic — use when an action looks broken or silent.",
promptSnippet: "Read JS errors / console output on the current tab",
promptGuidelines: [
- "Use after a click, submit, or navigate produced no visible change — errors often explain it.",
- "Pass `sinceSeq` from the previous call's `nextCursor` to see only new messages after an action.",
"Buffer is reset on tab switch (capacity 500 records). bufferOverflowed:true means older entries were dropped.",
"Don't use as a default observation tool — prefer browser_snapshot for page structure.",
],
diff --git a/src/domains/files.ts b/src/domains/files.ts
index 89fdaf9..9a3a705 100644
--- a/src/domains/files.ts
+++ b/src/domains/files.ts
@@ -104,7 +104,7 @@ export const uploadFileTool = defineBrowserTool({
description: "Set files on a file via CDP, with a JS-DataTransfer fallback for stubborn pages. PREFERRED: pass `ref` from browser_snapshot; fallback: a CSS `selector`.",
promptSnippet: "Upload a file to a file input by ref (preferred) or selector",
promptGuidelines: [
- "PREFER `ref` from browser_snapshot over a CSS selector — survives re-renders. (File inputs are often hidden; snapshot still surfaces them.)",
+ "File inputs are often hidden; browser_snapshot still surfaces them.",
"File path must be absolute and readable.",
],
parameters: UploadArgs,
diff --git a/src/domains/form-batch.ts b/src/domains/form-batch.ts
index 567108b..3ec8d26 100644
--- a/src/domains/form-batch.ts
+++ b/src/domains/form-batch.ts
@@ -28,7 +28,6 @@ export const fillFormTool = defineBrowserTool({
promptSnippet: "Fill many form fields at once by ref — React-safe, self-confirming",
promptGuidelines: [
"Preferred for forms: snapshot once, then fill all fields in a single browser_fill_form call.",
- "Each field: { ref, value }. value is a string for text/select/contenteditable, boolean for checkbox/radio.",
"The result summarizes how many fields filled cleanly and flags any value mismatches or per-field errors.",
"Re-run browser_snapshot afterwards to verify the form state if needed.",
],
diff --git a/src/domains/form.ts b/src/domains/form.ts
index 577eb5d..1673738 100644
--- a/src/domains/form.ts
+++ b/src/domains/form.ts
@@ -27,15 +27,12 @@ export const fillTool = defineBrowserTool({
"Fill a form field (input/textarea/contenteditable), or tick a checkbox/radio by passing 'true'/'false'. PREFERRED: pass `ref` (e.g. 'e7') from browser_snapshot — survives re-renders. Fallback: a CSS `selector`. Writes through the native value setter and fires bubbling 'input'/'change' events, so React/Vue/Angular controlled components and rich-text editors update correctly — unlike browser_type. Refuses a disabled field rather than writing a value the page will ignore. Returns the field's value after writing, plus a compact diff of page changes.",
promptSnippet: "Fill a form field by ref (preferred) or selector (works with React/Vue controlled inputs)",
promptGuidelines: [
- "PREFER `ref` from browser_snapshot (the '[eN]' handle) over a guessed CSS selector — refs survive re-renders and don't require you to invent a selector.",
"Pass the desired value; no browser_click is needed first.",
"For a checkbox or radio pass 'true' or 'false' — it toggles `checked`, not the submit value. browser_set_checked does the same thing with a boolean.",
- "A disabled field is refused: the error says so, rather than reporting a write the page never accepted.",
- "A 'ref is stale' error means the page changed — re-run browser_snapshot to get fresh refs.",
"Use browser_type instead only for keystroke-sensitive widgets (autocomplete, masked/segmented inputs).",
"Fill sets the value but does not focus or submit. To submit a tag/autocomplete input, follow with browser_dispatch_key({ ref, key: 'Enter' }) — not browser_press_key, which targets the focused element and may miss this field.",
"For a custom (div-based) dropdown, browser_fill won't validate against its option list — open it, run browser_snapshot, then browser_click the option's ref.",
- "The result reports the field's value after writing and an appended page-changes diff — confirm both match what you intended before moving on.",
+ "Confirm the field value browser_fill returns matches what you intended.",
],
parameters: FillArgs,
concurrency: "serialized",
@@ -118,8 +115,6 @@ export const focusTool = defineBrowserTool({
"Focus an element via the DOM .focus() method — deterministic, no coordinate accuracy needed. PREFERRED: pass `ref` from browser_snapshot; fallback: a CSS `selector`. Use before browser_type when a click might miss the field.",
promptSnippet: "Focus an element by ref (preferred) or selector",
promptGuidelines: [
- "PREFER `ref` from browser_snapshot over a CSS selector — survives re-renders.",
- "Use before browser_type to guarantee the right field is focused without relying on click coordinates.",
"For simply setting a value, prefer browser_fill (it doesn't need a separate focus step).",
],
parameters: FocusArgs,
@@ -196,10 +191,8 @@ export const selectOptionTool = defineBrowserTool({
"Select an option in a native