feat(tools): browse_web — drive a real browser via AgentCore Browser - #1010
Merged
Conversation
Adds a `browse_web` tool backed by the AgentCore Browser resource that PlatformStack already deploys but nothing consumed: the runtime role has the browser IAM actions and `BROWSER_ID` in env, and the only reader was a startup log line. Zero new dependencies. `websockets==16.0` is already in the image, so the CDP layer is hand-rolled rather than adding Playwright and its bundled Node driver to the inference-api container for auto-waiting and a selector engine we mostly don't use — JS evaluated in the page does the same job. `cdp_client.py` is the seam if richer interaction is ever needed. Cost posture, since a browsing transcript is the classic unbounded per-turn payload: every action's output is capped before it reaches the model, and a screenshot is only ever taken when the model explicitly asks — vision tokens are the most expensive thing this tool can emit, so it is never automatic. Seeded `enabledByDefault=False` and gated by `BROWSER_TOOL_ENABLED`. Session lifecycle follows the "never cache session state on an agent instance" rule: the browser session *id* lives on Strands `agent.state` (as `app_context_dispatch` does), while the live socket is process-local keyed by that id — so a second cached agent for the same conversation reconnects to the same remote session instead of starting a second one. `add_init_script` (CDP `Page.addScriptToEvaluateOnNewDocument`) is included and probed but unused: it is the hook a future WebMCP driver would need, per docs/specs/webmcp-host-spa-tools.md. Not verified against live AWS — the dev-ai SSO token was expired and the device grant needs an interactive session. `scripts/probe_agentcore_browser.py` runs the five checks that unit tests cannot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adding browse_web to the bootstrap seed broke three assertions in TestSeedDefaultTools that hardcoded "9 tools". The counts now come from len(DEFAULT_TOOLS), so the invariant under test is the real one — every entry in the seed list gets created or skipped — instead of a literal that has to be bumped by hand whenever a tool is added. Also pins browse_web's seeded fields the way the other tools are pinned, including enabledByDefault=False: that flag is the cost guard, not a default worth drifting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A
browse_webtool backed by the AgentCore Browser. Actions:navigate,extract_text,extract_links,click,type,evaluate,screenshot,live_view,close. The browser session persists across calls within a conversation.The infra was already deployed and idle —
AgentCoreBrowserConstructprovisions theCfnBrowserCustom, the runtime role has the browser IAM actions scoped to it, andBROWSER_IDis in the container env. The only thing reading it was a startup log line (inference_api/main.py:61). No CDK change needed.Zero new dependencies
websockets==16.0is already in the image, so the CDP layer is hand-rolled (cdp_client.py, ~230 lines) instead of adding Playwright and its bundled Node driver to the inference-api container. What Playwright would buy is auto-waiting and a selector engine we mostly don't use — JS evaluated in the page does the same job, and it's what AWS's own guidance recommends for extraction.cdp_client.pyis the seam if frames / real input events / file chooser are ever needed.Cost posture
A browsing transcript is the textbook unbounded per-turn payload, so:
MAX_TEXT_CHARS,MAX_EVAL_CHARS,MAX_LINKS, all env-tunable) with a truncation note that tells the model to useevaluateinsteadextract_text/evaluatefirstenabledByDefault=False, gated byBROWSER_TOOL_ENABLED(default on, kill switch)Session lifecycle
Follows the "one session can be served by more than one agent" rule: the browser session id lives on Strands
agent.state(same pattern asapp_context_dispatch), while the live socket is a process-local lookup keyed by that id. A second cached agent — or a container that restarted — reads the same id and reconnects to the same remote session rather than starting and billing a second browser.WebMCP hook
add_init_script(CDPPage.addScriptToEvaluateOnNewDocument) is implemented and covered by the probe but unused. It's the exact hook a future WebMCP driver needs, perdocs/specs/webmcp-host-spa-tools.md(#1004) — included now so the seam exists, not because anything calls it.Testing
21 unit tests, no AWS: the CDP layer runs against a fake websocket that speaks the protocol (id framing,
Target.*browser-scoped vs page commandssessionId-scoped, page exceptions surfacing asCdpError, idempotent close), and the tool layer against a fake CDP session (truncation budgets, missing-selector errors, SSRF-ish URL refusals, kill switch). Full suite: 1476 passed.Not verified against live AWS. The dev-ai SSO token was expired and the device grant needs an interactive session, so the CDP handshake against AgentCore's automation endpoint is unproven.
backend/scripts/probe_agentcore_browser.pyruns the five checks unit tests can't:The riskiest assumption it checks is that the automation endpoint exposes a browser-level CDP surface where
Target.getTargets/attachToTargetwork. If AgentCore hands back an already-page-scoped socket,_attach_to_pageis where it fails and the fix is local to that method.To use it in dev
Re-run the bootstrap seeder (idempotent — skips tools that already exist) to create the catalog row, then grant
browse_webto a role and enable it per user.🤖 Generated with Claude Code