Everything we've described so far — MCP, A2A, plain REST APIs — works the same way: register a downstream server, and AGF fronts it as a gateway, intercepting traffic before it reaches your agent's target. That model breaks down for a real and increasingly common category of agent: one that drives a browser directly, via Playwright or a similar automation library, clicking buttons and filling forms the way a human would.
There's no downstream server to front. The browser is local — spun up by the agent's own process, or a remote session it controls directly. A network gateway has nothing to sit in front of.
Where the gap shows up
A browser-automation agent's "tool calls" are just method calls on a Page object: goto(), click(), fill(), set_input_files(). Nothing about that API asks whether the agent should be allowed to navigate to a given URL, click a given element, or upload a given file — it just does it. The same problem we described for LangChain and CrewAI tool calls applies here, just without a "tool" abstraction to hang a decision on.
How it works
Since there's no network hop to intercept, this ships as an SDK-side guard, not a gateway: agf.browser.GuardedPage wraps a real Playwright Page — sync or async — and gates the four actions with the most governance relevance before delegating to the real page:
from playwright.sync_api import sync_playwright
from agf import AGFClient
from agf.browser import GuardedPage
client = AGFClient(api_key="ak_live_...")
with sync_playwright() as p:
browser = p.chromium.launch()
page = GuardedPage(
browser.new_page(),
client,
agent_id="did:agf:agt_01abc",
)
page.goto("https://vendor-portal.acme.com/invoices") # checked: navigate
page.click("#approve-payment") # checked: click
page.fill("#amount", "4200.00") # checked: fill
page.set_input_files("#receipt", "receipt.pdf") # checked: upload
Everything else on page — title(), content(), screenshot(), the rest of Playwright's API — passes straight through untouched, including with page:/async with page:. Only the four actions above go through a real policy check first; a DENY raises before the underlying Playwright call ever runs, so the click, fill, or navigation genuinely never happens.
Why only four actions
We didn't try to wrap Playwright's entire API. goto, click, fill, and set_input_files are the actions most likely to have real consequences — leaving a trusted context, submitting something, or exfiltrating a file. Read-only inspection (title(), content(), inner_text()) passes through by design, the same way GET requests pass through the HTTP Gateway. More actions can be added as real usage shows a need for them; we'd rather ship a small, honest set than a large one that's only partially thought through.
One deliberate privacy choice: fill()'s policy check sees the selector you're filling, never the value you're filling it with — the same reasoning as the MCP Gateway's opt-in (default off) tool-argument logging. A form field's contents can carry secrets or PII that don't need to transit a policy decision to be governed correctly.
Where we are today
agf.browser ships in agf-sdk — pip install agf-sdk[browser] for the Playwright dependency. No separate gateway, no runtime component, no numbered spec (there's no wire protocol here to document, the same way our AutoGen support has never needed one).

