Skip to content

feat(agents): cdp-config/v1 hands an agent the CDP endpoints it drives - #440

Merged
earakely-scale merged 3 commits into
mainfrom
edgararakelyan/fd-4948-m81-cdp-configv1-in-the-agent-sdk
Nov 2, 2026
Merged

earakely-scale merged 3 commits into
mainfrom
edgararakelyan/fd-4948-m81-cdp-configv1-in-the-agent-sdk

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

What and why

An agent that drives a browser itself, such as Browser Use (#378) or Stagehand, needs a CDP address from the task, and should fail if the task gives none rather than reaching for a browser of its own. This adds the agent side of that: cdp-config/v1, built the way mcp-config/v1 is. It is M8.1 of the website browser design (FD-4948).

@a2a_agent(identity=..., extensions=(MCP_CONFIG_V1, CDP_CONFIG_V1))
class BrowserAgent(AgentEnvAgent):
    async def run(self, request):
        endpoint = request.cdp_endpoint()        # or request.cdp_endpoint("chromium-buyer")
        browser = await playwright.chromium.connect_over_cdp(endpoint["http_url"])
  • CDP_CONFIG_V1 at /ext/cdp-config, with no card params. The SDK implements all three operations:

    Operation Request What it does
    add POST {name, http_url, ws_url, headers?, metadata?} Adds the endpoint. Adding a name again replaces it.
    list GET Returns {"cdp_endpoints": {name: {has_headers, metadata}}}. It shows neither the URLs nor the header values: an agentenv address carries its credential in the URL.
    remove DELETE {name} Removes the endpoint. A name that isn't there answers absent, not an error.
    • Names follow the skill-name pattern, so chromium and chromium-buyer both work.
    • http_url must be an absolute http(s) URL and ws_url an absolute ws(s) URL, each with a host and, if it gives one, a valid port.
  • Every turn gets the endpoints as TaskRequest.cdp_endpoints, headers included, as a detached copy.

  • request.cdp_endpoint(name=None) returns the named endpoint, or the only one. Anything else fails the turn as an infra_error:

    • cdp.no_endpoint when there is none, or none by that name;
    • cdp.ambiguous when there are several and no name.
  • It fails the turn by raising a private exception, which the executor turns into TaskResult.failure, streaming or not. The protocol gains no public way to fail a turn. The docs say to call cdp_endpoint() before any handler of the agent's own that catches every exception. Any other exception still becomes the opaque framework.unhandled_exception.

  • agent-env knows the extension as A2AAgent.EXT_CDP_CONFIG, and the card check logs whether an agent advertises it.

  • Docs: a cdp-config/v1 section in agents/AGENTS.md, which says nothing in agent-env sends endpoints yet, and the wire contract in the protocol README.

Nothing adds endpoints yet. browser_connect_cdp (M8.17) does that, and the Browser Use change is M8.18. No shipped agent lists the extension in this PR.

Choices to confirm in review:

  1. A missing browser is an infra_error, not an agent_error. The run couldn't happen, and it isn't the agent's fault. Benchmark results shouldn't count it as the agent scoring 0.
  2. remove is a DELETE with a JSON body, as the design's sketch has it. The route reads a body for any method but GET.

How it was tested

  • make lint: clean.
  • Unit tier plus protocol tests (pytest tst/unit packages/agentenv-protocol/tests -n auto): 12,471 passed, 66 skipped.
    • New protocol tests, in test_a2a_cdp_config.py:
      • the card entry;
      • add, replace, list and remove;
      • 400s for bad names, URLs (no host, bad port) and extra fields;
      • URLs and headers hidden from list but passed to the turn;
      • the endpoints on every turn;
      • the fail-closed codes, streaming and not;
      • the detached copy.
    • New agent-env test: the card resolves add, list and remove to their methods and path.
  • End to end on a VM in the agent-environments-admin account (Ubuntu 24.04, Docker 29): 20 of 20 checks passed on 7b0749b, the final head, on a fresh VM.
    • Setup:
      • A fixture agent built from this branch's protocol package lists CDP_CONFIG_V1 and drives the turn's endpoint with Playwright 1.52 connect_over_cdp.
      • It was registered and deployed through agent-env on local Docker: DockerImageArtifact.put, A2AAgent.put, deploy.
      • It was prompted through send_a2a_message, poll_a2a_task and TerminalResponse, the path prompt_agent uses.
      • A real Chromium 136 served CDP from a second container, and a static page from a third.
    • Checks:
      • The card lists add (POST), list (GET) and remove (DELETE) at /ext/cdp-config.
      • With no endpoint, the turn fails: infra_error, cdp.no_endpoint.
      • After add, the turn opens the page in Chromium and reads its title: once by http_url, once by ws_url.
      • Adding chromium again with another address replaces it, and the next turn uses the new one.
      • With two endpoints, a turn with no name fails cdp.ambiguous, and an unknown name fails cdp.no_endpoint. A named turn reaches its endpoint with its headers, while list shows neither URLs nor header values.
      • An http ws_url, a bad name, http://:8080/ and a non-numeric port get 400s.
      • remove, removing again (absent), an empty list, and the turn fails closed again.
      • The agent's sandbox was gone after the run.
    • The harness stays out of this PR.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no new actionable issues were found.

Summary

Adds cdp-config/v1 so agents can use registered browser endpoints.

  • Callers can add, list, and remove named browser endpoints.
  • Each task can select its browser endpoint or fail with a clear task error.

Since the previous review, listing hides URLs and the failure exception is private. The earlier URL-validation finding is fixed: addresses need a host and a valid port.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["POST /ext/cdp-config"] --> B["Registered endpoints"]
  B --> C["GET: names, metadata, has_headers"]
  B --> D["TaskRequest: full detached copy"]
  D --> E{"cdp_endpoint(name)"}
  E --> F["Selected browser endpoint"]
  E --> G["Missing or ambiguous: infra_error"]
Loading

Reviews (3) · Last reviewed commit: "refactor(agents): cdp-config/v1 list sho..." · Reviewed by Greptile

An agent that drives a browser itself lists CDP_CONFIG_V1. The SDK serves
add (adding a name again replaces it), list (header values hidden) and
remove at /ext/cdp-config, and passes the endpoints to every turn as
TaskRequest.cdp_endpoints. request.cdp_endpoint(name=None) returns the
named or only endpoint and otherwise fails the turn as an infra_error
(cdp.no_endpoint, cdp.ambiguous), so an agent never falls back to a
browser of its own. TaskFailed is the exception behind it: raised in
run(), it ends the turn as TaskResult.failure would.

agent-env knows the extension as A2AAgent.EXT_CDP_CONFIG. Nothing adds
endpoints yet; browser_connect_cdp does that next.

FD-4948

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner November 2, 2026 16:34
earakely-scale and others added 2 commits November 2, 2026 08:38
…with a bad port

urlsplit accepts http://:8080/ and ws://browser.test:abc/ with a netloc,
so add stored addresses no client could connect to.

FD-4948

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ure stays private

- list returns each endpoint's has_headers and metadata only. An
  agentenv address carries its credential in the URL, so hiding headers
  alone hid nothing that matters.
- TaskFailed becomes the private _TaskFailed: only cdp_endpoint raises
  it, so the protocol gains no public way to fail a turn yet. The docs
  say to call cdp_endpoint before an exception handler that catches
  everything.
- AGENTS.md no longer says agent-env sends endpoints: nothing does until
  the browser_connect_cdp task step.

FD-4948

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit 965ab76 into main Nov 2, 2026
25 of 26 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-4948-m81-cdp-configv1-in-the-agent-sdk branch November 2, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant