[eric] browser: nudge the agent toward the fast captured-API tier once per host

This commit is contained in:
ciregenz
2026-06-03 00:50:23 -07:00
parent 186c6cf72e
commit 21b225ded4
3 changed files with 55 additions and 1 deletions
@@ -328,6 +328,7 @@ async def run_browser_agent(
recent_tool_calls: list[tuple[str, str, str]] = []
loop_trigger_count = 0
card_gone_streak = 0 # consecutive "card is gone" results -> fail fast, don't spin
route_hinted_hosts: set[str] = set() # surface the fast network tier once per host
# Stagnation state: busy-but-stuck detection (no URL change + failures
# across a run of actions), distinct from the exact-repeat loop above.
@@ -1022,6 +1023,21 @@ async def run_browser_agent(
session.browser_domains.append(domain)
except Exception:
pass
# The fast tier is ~0% used because the agent never thinks to ask.
# Once per host, when safe GET routes have been captured, nudge it:
# reading via the API beats re-scraping, especially in a batch loop.
try:
_rc = int(result.get("routes_available") or 0)
_rhost = browser_skills.host_of(result.get("url") or last_seen_url)
if _rc > 0 and _rhost and _rhost not in route_hinted_hosts:
route_hinted_hosts.add(_rhost)
content_blocks = content_blocks + [{"type": "text", "text": (
f"\n\n💡 {_rc} of this site's own API endpoint(s) were captured. To READ "
"data (and especially to repeat a read for many items), BrowserReplayRoute "
"(or a replay_route step in BrowserRepeatFlow) is much faster and more "
"reliable than navigating + scraping. See BrowserListRoutes.")}]
except Exception:
pass
if is_loop:
loop_trigger_count += 1
repeat_count = sum(1 for c in recent_tool_calls if c == call_key)
+25
View File
@@ -902,6 +902,31 @@ def test_batch_replay_uses_the_fast_network_route_per_value(monkeypatch):
assert any("u=ada" in u for u in routes) and any("u=grace" in u for u in routes)
def test_captured_routes_are_surfaced_once_per_host(monkeypatch):
# Drives the dead network tier: when safe GET routes exist, the agent gets a
# ONE-TIME nudge per host toward BrowserReplayRoute, not on every navigation.
BH._browser_history.clear()
primary = FakeLLM([
Resp([_rp("go 1"), _tu("BrowserNavigate", url="https://docs.google.com/a")]),
Resp([_rp("go 2"), _tu("BrowserNavigate", url="https://docs.google.com/b")]),
Resp([Blk("text", "done")], stop_reason="end_turn"),
])
sent = _install(monkeypatch, primary, FakeAux())
orig = BA.ws_manager.send_browser_command
async def _with_routes(request_id, action, browser_id, params, tab_id=""):
if action == "navigate":
return {"text": f"Navigated to {params.get('url')}", "url": params.get("url"), "routes_available": 4}
return await orig(request_id, action, browser_id, params, tab_id)
monkeypatch.setattr(BA.ws_manager, "send_browser_command", _with_routes, raising=False)
asyncio.run(BA.run_browser_agent(task="browse", browser_id="b1", model="sonnet", initial_url=DOC_URL))
# messages are cumulative across calls, so count within ONE call's full
# conversation: the nudge must appear exactly once for docs.google.com (not per nav)
final_convo = json.dumps(primary.calls[-1]["messages"])
assert final_convo.count("API endpoint(s) were captured") == 1
def test_browser_wait_routes_through_smart_wait_and_returns_early(monkeypatch):
# BrowserWait must no longer be a blind sleep: it probes the page (evaluate)
# and returns as soon as it's settled, well under the requested cap.
+14 -1
View File
@@ -96,7 +96,20 @@ async function handleNavigate(wv: BrowserWebview, params: Record<string, any>):
} catch (err: any) {
if (!err?.message?.includes('ERR_ABORTED')) throw err;
}
return { text: `Navigated to ${url}`, url };
// Count the safe GET endpoints captured for this site so the backend can nudge
// the agent toward the fast network path (the audit found it's ~0% used).
let routesAvailable = 0;
try {
const bridge = (window as any).openswarm?.cdpRoutesGet as
| ((id: number, origin?: string) => Promise<any[]>) | undefined;
if (bridge) {
let origin = '';
try { origin = new URL(wv.getURL()).origin; } catch {}
const routes = (await bridge(wv.getWebContentsId(), origin)) || [];
routesAvailable = routes.filter((r) => r && r.safe).length;
}
} catch {}
return { text: `Navigated to ${url}`, url, routes_available: routesAvailable };
}
async function handleClick(wv: BrowserWebview, params: Record<string, any>): Promise<Record<string, any>> {