Atomic Agents + Anchor: Modular Browser Agents in Python

Hands On
Jun 29
by Idan Raman

Most agent frameworks hand you a browser and tell you to figure out the rest. Atomic Agents takes the opposite approach: every tool has a strict Pydantic schema for both input and output, making your agent logic composable, testable, and debuggable by design.

Pair that with Anchor's managed cloud browser infrastructure—isolated sessions, built-in anti-detect, persistent authentication—and you get a stack where the infrastructure stays out of the way while your agent logic stays clean.

Why Atomic Agents?

  • Typed I/O: Every tool input and output is a Pydantic model. The LLM can't hallucinate a field that doesn't exist in your schema.
  • No framework magic: Tools are plain Python classes—easy to unit-test, easy to swap out for mocks.
  • Context injection: Tools receive context explicitly rather than reading global state, which makes concurrency straightforward.
  • Minimal footprint: The entire library is small enough to audit in an afternoon.

Setup

pip install atomic-agents anchor-browser playwright
playwright install chromium

export ANCHOR_API_KEY=your_anchor_key
export OPENAI_API_KEY=your_openai_key

Creating an Anchor Session

Anchor manages the browser lifecycle. A session gives you a CDP endpoint that Playwright connects to—no local Chromium to maintain, no IP reputation to manage.

import os
from anchor_browser import AnchorClient

anchor  = AnchorClient(api_key=os.environ["ANCHOR_API_KEY"])
session = anchor.sessions.create(
    proxy_country="us",
    options={"adblock": True},
)

Wrapping Anchor as an Atomic Tool

An Atomic Agents tool is a class with three things: an input_schema, an output_schema, and a run() method. Here's a NavigateTool that visits a URL and returns extracted text:

from pydantic import Field
from atomic_agents.agents.base_agent import BaseIOSchema
from atomic_agents.lib.base.base_tool import BaseTool, BaseToolConfig

class NavigateInput(BaseIOSchema):
    # Navigate to a URL and return the page body text
    url:      str = Field(..., description="The URL to visit")
    selector: str = Field("body", description="CSS selector for content extraction")

class NavigateOutput(BaseIOSchema):
    # Text content extracted from the page
    title: str = Field(..., description="Page title")
    text:  str = Field(..., description="Extracted text (first 4000 chars)")

class AnchorNavigateTool(BaseTool):
    input_schema  = NavigateInput
    output_schema = NavigateOutput

    def run(self, params: NavigateInput) -> NavigateOutput:
        pw      = session.get_playwright()
        browser = pw.chromium.connect_over_cdp(session.ws_endpoint)
        page    = browser.contexts[0].pages[0]

        page.goto(params.url, wait_until="domcontentloaded")
        title = page.title()
        text  = page.locator(params.selector).inner_text()
        return NavigateOutput(title=title, text=text[:4000])

Because NavigateInput is a Pydantic model, a missing URL or malformed selector raises a validation error before your browser ever opens. In tests, replace the tool with a mock that returns a fixture NavigateOutput.

Building the Agent

from atomic_agents.agents.base_agent import BaseAgent, BaseAgentConfig
from atomic_agents.lib.components.system_prompt_generator import SystemPromptGenerator
from openai import OpenAI

tool  = AnchorNavigateTool()
agent = BaseAgent(
    config=BaseAgentConfig(
        client=OpenAI(),
        model="gpt-4o",
        system_prompt_generator=SystemPromptGenerator(
            background=[
                "You are a research assistant that retrieves facts from live websites.",
            ],
            steps=[
                "Identify the most relevant URLs for the user's question.",
                "Call the navigate tool for each URL.",
                "Synthesize findings into a concise, sourced answer.",
            ],
            output_instructions=[
                "Include the source URL for every fact you report.",
            ],
        ),
        tools=[tool],
    )
)

response = agent.run("What are the current Anchor Browser pricing plans?")
print(response.message)

Running Pages in Parallel

Anchor sessions are independent—you can provision as many as your plan allows and run them concurrently. Atomic Agents doesn't manage concurrency itself, but standard Python does:

import concurrent.futures

urls = [
    "https://anchorbrowser.io/pricing",
    "https://browserbase.com/pricing",
    "https://steel.dev/pricing",
]

def fetch(url: str) -> NavigateOutput:
    t = AnchorNavigateTool()
    return t.run(NavigateInput(url=url))

with concurrent.futures.ThreadPoolExecutor(max_workers=3) as ex:
    results = list(ex.map(fetch, urls))

for r in results:
    print(r.title, "—", r.text[:200])

Each worker provisions its own Anchor session, navigates independently, and returns typed output—no shared state, no locking.

Testing Without a Browser

Explicit I/O contracts make mocking trivial:

from unittest.mock import patch

def test_agent_uses_page_text():
    mock = NavigateOutput(
        title="Anchor Pricing",
        text="Starter: $49/mo · Growth: $199/mo · Enterprise: contact us",
    )
    with patch.object(AnchorNavigateTool, "run", return_value=mock):
        response = agent.run("What does Anchor cost?")
    assert "49" in response.message

No browser spun up, no network calls, fully deterministic. This is the payoff of strict I/O: your agent's reasoning is testable in complete isolation from the browser layer.

Production Tips

  • Reuse sessions: Create the Anchor session once per run and share it across tools—don't open a new browser per tool call.
  • Always clean up: Call session.close() in a finally block so you don't accumulate idle sessions.
  • Extend naturally: FillFormTool, ClickTool, ScreenshotTool—each follows the same pattern: typed input, typed output, one run() method.
  • Scale without rewriting: For high-throughput workloads, use Anchor's session pooling to pre-warm browsers and eliminate cold-start latency without touching your tool code.

Get Started

Atomic Agents on GitHub · Anchor documentation · Start your free Anchor trial →

Stay ahead in browser automation

We respect your inbox. Privacy policy

Welcome aboard! Thanks for signing up
Oops! Something went wrong while submitting the form.