# Browser Automation Coding Agent Loop uses the managed `agent_browser` tool for all browser automation. The browser can run headlessly in the workspace or attach to a user-visible Chrome through CDP. ## Modes | Mode | Behavior | Typical use | |---|---|---| | `none` | Browser tools are disabled. | Workflows that do not browse. | | `auto` | Use a reachable configured CDP browser; otherwise use headless. | Default. | | `headless` | Use the signed-in user’s managed Chromium. | Background and scheduled runs. | | `cdp` | Attach to the configured Chrome debugging port. | Existing logins, visual QA, and sites that reject headless browsers. | The workflow manifest stores the mode under `capabilities.browser_mode`. Browser steps attach the `agent-browser` skill. ## Starting a CDP browser On macOS, install the default launcher on port `9222` with: ```bash curl -fsSL 'https://raw.githubusercontent.com/manishiitg/coding-agent-loop/main/scripts/install-chrome-cdp-macOS.sh' | bash ``` Install another independent launcher/profile by passing a port: ```bash curl -fsSL 'https://raw.githubusercontent.com/manishiitg/coding-agent-loop/main/scripts/install-chrome-cdp-macOS.sh' | bash -s -- --port 9333 ``` Each CDP profile must use its own port and `--user-data-dir`. The usual port is `9222`; the port-specific installer creates a separate application and profile. For a specialized workflow that needs multiple login identities, launch more profiles on different ports, for example `9222` and `9333`, then configure: ```json { "browser_mode": "cdp", "cdp_ports": [9222, 9333] } ``` The runtime accepts at most four configured ports. Ordinary workflow concurrency does not require multiple profiles: workflows share one CDP browser and use labeled tabs plus a per-port select-and-act lock. ## Managed tool Do not run the `agent-browser` CLI through the shell for browser actions. Call the managed `agent_browser` tool. The runtime injects and validates the CDP endpoint, applies session limits, serializes shared-tab actions, and keeps file access inside the workspace. To check whether CDP is reachable, use the backend status operation: ```text agent_browser(command="status", args=[], session="default") ``` `status` needs no tab and no `--cdp` argument. `snapshot` is not a connectivity probe: it reads one specific page, so in shared CDP mode it must name the tab. Before the first browser action, load the installed CLI's matching command guide: ```text agent_browser(command="skills", args=["get", "core"]) ``` The common flow is: ```text agent_browser(command="open", args=["https://example.com"]) agent_browser(command="snapshot", args=["-i"]) agent_browser(command="click", args=["@e1"]) agent_browser(command="snapshot", args=["-i"]) ``` In CDP mode, list and reuse a suitable tab before asking to create one. Include the returned real tab ID (`t1`, `t2`, and so on) inline for every page action. `open` itself remains URL-only. The inline system prompt gives the exact endpoint and argument form for the active session. ## Shared CDP tab lifecycle One visible Chrome is shared safely by verifying and acting under a per-port lock. A workflow must not assume that the tab selected during its previous tool call is still active: the user, the website, or another workflow may have changed Chrome in the meantime. The backend therefore reads the real tab state immediately before every page action while it holds the shared lock. It keeps using the requested real `tN` when that tab is already active, and switches only when another tab is active. This avoids repeatedly bringing Chrome to the foreground on macOS without allowing one workflow to act in another tab. The normal flow is: 1. Call `agent_browser(command="tab", args=["--cdp", ""])` once to inspect real tab IDs and query-free display URLs. 2. Reuse the workflow's already-owned labeled tab when one exists. It may be navigated to the requested URL. 3. Otherwise, reuse a pre-existing tab only when its normalized URL exactly matches the requested URL. 4. If neither matches, request a stable labeled tab with `agent_browser(command="tab", args=["--cdp", "", "new", "--label", "", "https://target.example"])`. 5. Keep the returned real `tN` and provide it inline on subsequent actions. The backend repeats the list-and-reuse check atomically before executing `tab new`. It refuses creation if the real tab list is unavailable or invalid, rather than risking a duplicate. A label collision with a pre-existing tab at a different URL is also an error. An arbitrary same-origin tab is deliberately not reused because navigating it could destroy unrelated user state. URL query parameters are hidden from model-facing tab lists, but the backend retains the full normalized URL for exact-match decisions. `tab new` arguments are parsed and rewritten into the canonical `new --label