A BrowserSkill Task Is a Bridge, Not a Browser

BrowserSkill's architecture answers a specific question: how can an agent request a browser operation without becoming a direct, unscoped client of the user's browser? The useful way to understand the answer is to follow one request across the CLI, daemon, extension, and browser, and to ask at each boundary what state is translated, routed, or finally changed. That model also explains why BrowserSkill can support several agent harnesses without making the ownership and cleanup problem disappear.

Four layers, four kinds of responsibility

The first layer is the agent harness. It has a goal expressed in human or task language: read a page, navigate to a destination, fill a field, or inspect a result. BrowserSkill's agent-facing skill teaches the harness how to turn that goal into bsk operations. The harness supplies intent and inputs; it does not receive a direct browser socket or a general-purpose Chrome object. That distinction is more than an abstraction boundary. It prevents the user-facing agent interface from being mistaken for a browser authorization mechanism.

The second layer is the bsk CLI. It is the process the agent can invoke, and it gives browser operations a stable command surface across harnesses. The CLI parses subcommands, formats human-readable or JSON results, and communicates with the daemon over local IPC. On Unix-like systems the documented local path uses JSON Lines over a Unix domain socket; on Windows it uses a named pipe. The CLI is therefore a translator and a client of the daemon, not the component that owns the browser tab or executes page JavaScript.

The third layer is the bsk daemon. It is the local control-plane authority. It knows which browser extensions are connected, which sessions belong to which browser connection, and which per-session queue should receive a tool request. It forwards a typed request to the extension and returns the structured response to the CLI. In the local configuration, the extension link is a WebSocket on loopback. The daemon's session registry and routing state are what keep a request associated with a particular browser connection and session rather than with an accidental globally active tab.

The fourth layer is the browser extension, which is the first component in this path that can use Chrome APIs and the browser's debugging protocol to operate a page. The extension maintains the session's Agent Window, reference store, tab claims, and browser-side cancellation state. Its dispatcher maps an incoming method to a tool implementation. Only then does a request become a navigation, observation, screenshot, input event, or other browser operation.

The browser itself remains a separate owner of page state. A tool can ask Chrome to dispatch input, navigate, or read a document, but the site decides how that input changes its page. A successful RPC response therefore has a deliberately limited meaning. It may establish that a request reached the relevant browser layer and that the browser operation returned a result. It does not automatically establish that an external service accepted a form, that a workflow reached its business goal, or that no effect occurred after a timeout.

What crosses the interfaces is consequently more structured than a vague instruction such as “open the browser.” The request carries an operation and session context. The daemon uses that context to resolve a connected browser and its per-session route. The extension uses it to resolve an Agent Window, a target tab, and any reference or selector supplied for the page element. A read operation may return semantic text, a screenshot payload, or a structured diagnostic result. An input operation may return the target and dispatch coordinates, but it must also preserve the possibility that the page changed between target resolution and input. A session operation changes the set of resources that later calls may address. Each layer is handling a different interpretation of the same intent, and each interpretation can narrow the result without pretending to know more than the browser reported.

This is why the daemon is not merely a relay and the extension is not merely a driver. The daemon's routing decision answers “which browser connection and session should receive this request?” The extension's policy decision answers “which window and tab may this session control?” The browser operation answers “what did Chrome and the page do with the request?” Mixing those questions would create dangerous shortcuts. A global active tab could satisfy the first question while violating the second. A successful Chrome call could satisfy the third at the browser level while failing to satisfy the user's larger goal. The layered bridge preserves these distinctions long enough for the caller to make a better judgment.

The control path can be summarized as a sequence rather than a component inventory:

sequenceDiagram participant Agent as Agent harness participant CLI as bsk CLI participant Daemon as bsk daemon participant Ext as Browser extension participant Page as Browser/CDP Agent->>CLI: Request a scoped browser operation CLI->>Daemon: Send JSON request over local IPC Daemon->>Daemon: Resolve browser and session queue Daemon->>Ext: Forward typed RPC over WebSocket Ext->>Page: Apply session and target rules Ext->>Page: Read or mutate the page through Chrome/CDP Page-->>Ext: Browser result or error Ext-->>Daemon: Structured response Daemon-->>CLI: Route response to caller CLI-->>Agent: Render JSON or human-readable result

The sequence has two important asymmetries. Requests travel inward toward the browser, but authority is checked along the way rather than granted by the mere existence of a route. Responses travel outward, but their meaning remains bounded by the operation that produced them. The daemon can route a request to the extension; it cannot turn a page's own response into proof of a business outcome. The extension can dispatch input; it cannot make an irreversible page action reversible.

Interfaces create boundaries, not automatic security

It is tempting to call this layered path a security architecture and stop there. That would overstate what the evidence supports. Local IPC and a loopback WebSocket reduce accidental exposure and make the normal control path explicit. The daemon's handshake checks an extension-shaped origin, requires a first handshake frame, and validates protocol compatibility. The extension transport tracks socket generations and reconnects after unexpected closure. These are useful defenses and operational guarantees, but transport topology is not the same as authorization.

A loopback listener says where a connection is expected to come from; it does not by itself prove that every request represents the user's intended task. An extension-shaped origin says something about the shape of the peer; it is not a complete account of which browser or session the peer may control. The ownership rules in the extension and daemon do the more specific work: they associate calls with session state, hide other sessions' Agent Windows, constrain remote targets, and determine whether a target can be acted on.

The same distinction explains why a convenient CLI command does not imply unrestricted browser authority. bsk may expose commands for navigation, observation, tabs, interaction, screenshots, or help, but the command still enters a session-scoped path. A command that names a tab does not automatically authorize that tab. A command that returns successfully does not erase the need to stop the session. A command that is interrupted does not prove that Chrome received nothing. The interface makes capabilities available; the runtime's state and policies decide whether a particular invocation is allowed and what its result means.

This separation also contains hidden coupling that matters later. The daemon's queue protects the extension's session-local references and debugger state from racing tool calls. The extension's reference store depends on the page and document remaining the same long enough for a target to be used. Session teardown depends on the extension being able to return borrowed tabs before closing its own window. Transport reconnection depends on cleanup and session reconciliation, not on replaying every command that was in flight. In other words, the layers are separate in responsibility but connected by lifecycle assumptions.

A failure at one layer may therefore appear as a failure somewhere else. If a short-lived command sandbox kills a daemon after the CLI exits, the browser extension may still be present but unreachable. If the WebSocket reconnects, old session state may be purged rather than replayed. If the page changes after observation, a valid-looking reference may no longer identify the intended element. If the extension cannot return a borrowed tab, the correct result is a cleanup failure, not a successful session stop. The architecture is useful because these cases can be named and handled; it is not useful if the layers are treated as independent black boxes.

Consider a simple observation followed by a click. The agent asks the CLI to observe the active page. The daemon routes that read to one session, and the extension captures the page in that session's Agent Window, producing text and references. The agent then sends a click request using one of those references. The daemon does not need to know what the reference means; it needs to send the request to the same session. The extension does need to know: it checks the reference against the session's current reference store and tab, resolves the live element through the browser's debugging interface, brings it into view, and only then dispatches input. If navigation occurred between the two calls, the reference may be invalidated. That is not a routing failure, and it is not evidence that the click should be retried against a guessed target. It is a page-state change that the browser layer must report so the agent can observe again.

Now change the failure point. Suppose the click reaches the extension, but the CLI or WebSocket connection disappears while the page is processing it. The caller may not receive a final response. The architecture cannot infer “nothing happened” from the missing message because the browser may already have received the input. That uncertainty belongs in the result and in the next decision: inspect the current page before replaying the action. The same principle applies at shutdown. If a borrowed tab is still inside the Agent Window, closing the window first would turn a transport or cleanup problem into potential loss of user state. The session lifecycle must therefore coordinate with the browser layer instead of letting each process clean up only its own memory.

Harness neutrality without ownership neutrality

DeepSeek Harness makes the architectural point concrete. Its BrowserSkill plugin exposes native browser tools for sessions, pages, inspection, interaction, tabs, and assistance, but the plugin runs bsk on the agent's behalf. It is an adapter to the same bridge, not a second browser backend with a different ownership model. A DSH conversation can use a current-session pointer and domain-specific tool names, while the underlying daemon and extension still enforce their session and tab rules.

The plugin adds an ownership boundary of its own. It tracks only sessions created by that plugin instance. A session created by another terminal, agent, or DSH instance sharing the daemon is not adopted merely because its identifier is known. Listing exposes plugin-owned sessions, explicit foreign identifiers are rejected, and unload or archive cleanup can stop only sessions the plugin created. This is a useful example of layered responsibility: the shared daemon may be broader than one plugin, but the plugin refuses to turn shared infrastructure into shared ownership.

The plugin also shows that interface design affects operational reliability. Its model-facing operations are serialized per session because the daemon can reject a second unfinished command for the same session. Its process runner invokes the CLI with structured JSON, tags child processes by session, honors cancellation, and gives the daemon a chance to reconcile before force-killing a child. These details do not create a new browser guarantee; they preserve the guarantees of the underlying bridge when a different harness presents the tools.

Progressive disclosure serves a related purpose. The DSH plugin can advertise the browser skill before exposing every browser-tool schema, then register the tool suite after the skill is invoked. This keeps a conversation from receiving a large capability surface before the agent has the instructions needed to use it. It is not a permission boundary—the browser settings, sessions, and tab claims still provide that—but it reduces the chance that the interface is used without its operating context.

The result is a useful distinction between harness neutrality and ownership neutrality. BrowserSkill aims to let many harnesses reach the same control plane. It does not aim to make all harnesses, sessions, or programs interchangeable owners of every browser resource. The bridge is shared by design; control remains scoped by session, browser connection, and explicit tab state.

That is why the next architectural question is not “What other command does the CLI support?” It is “What state does a task create so the bridge can keep one operation separate from another?” A session is the answer to that question. It gives the routed request a concrete Agent Window, a reference context, and a lifecycle that can be started, operated, interrupted, and stopped. Without that state, the bridge would be a path to the browser. With it, the path can begin to express whose work is being done and what must be cleaned up when it ends.

References