LangGraph + Anchor: Building Stateful Browser Agents in Python

Hands On
Jun 22
by Idan Raman
LangGraph + Anchor: Building Stateful Browser Agents in Python

Most browser automation scripts are stateless—each run is a cold start with no memory of what came before. LangGraph changes that by giving your agents a persistent graph-based state machine. Pair it with Anchor Browser and you get stateful, stealth-capable browser automation that can pause, resume, and branch based on what it sees on the web.

In this guide we’ll build a research agent that navigates multiple sites, extracts structured data, and remembers what it found across steps—all with Anchor handling the browser infrastructure.

What We’re Building

A LangGraph agent that:

  • Opens an Anchor browser session with proxy and CAPTCHA-solving enabled
  • Navigates to target URLs and extracts content
  • Passes findings through graph state across multiple nodes
  • Produces a structured summary at the end

Setup

pip install langgraph langchain-anthropic anchorbrowser playwright
playwright install chromium
import os
from anchorbrowser import Anchorbrowser
from langgraph.graph import StateGraph, END
from langchain_anthropic import ChatAnthropic
from typing import TypedDict, List

anchor = Anchorbrowser(api_key=os.environ["ANCHOR_API_KEY"])
llm    = ChatAnthropic(model="claude-sonnet-4-6")

Define Agent State

LangGraph nodes share typed state. We include the Anchor session ID so any node can reconnect to the same live browser—no global variables, no state leakage between runs:

class ResearchState(TypedDict):
    query:         str
    urls_visited:  List[str]
    findings:      List[dict]
    summary:       str
    session_id:    str

Node 1: Start a Browser Session

async def start_session(state: ResearchState) -> ResearchState:
    session = anchor.sessions.create(
        proxy={"active": True},
        solver={"active": True},        # automatic CAPTCHA solving
        timeout={"max_duration": 300},
    )
    return {**state, "session_id": session.id}

Node 2: Navigate and Extract

from playwright.async_api import async_playwright

async def navigate_and_extract(state: ResearchState) -> ResearchState:
    url = f"https://example.com/search?q={state['query']}"

    async with async_playwright() as pw:
        browser = await pw.chromium.connect_over_cdp(
            f"wss://connect.anchorbrowser.io?apiKey={os.environ['ANCHOR_API_KEY']}"
            f"&sessionId={state['session_id']}"
        )
        page = browser.contexts[0].pages[0]
        await page.goto(url, wait_until="domcontentloaded")
        content = await page.locator("body").inner_text()
        await browser.close()

    result = await llm.ainvoke([{
        "role": "user",
        "content": f"Extract key data points from this page:\n{content[:3000]}"
    }])

    return {
        **state,
        "urls_visited": state["urls_visited"] + [url],
        "findings":     state["findings"]     + [{"source": url, "data": result.content}],
    }

Node 3: Summarize Findings

async def summarize(state: ResearchState) -> ResearchState:
    all_findings = "\n\n".join(
        f"Source: {f['source']}\n{f['data']}" for f in state["findings"]
    )
    result = await llm.ainvoke([{
        "role": "user",
        "content": f"Summarize these research findings in 3–5 bullet points:\n{all_findings}"
    }])
    return {**state, "summary": result.content}


async def cleanup_session(state: ResearchState) -> ResearchState:
    anchor.sessions.stop(state["session_id"])
    return state

Wire the Graph

workflow = StateGraph(ResearchState)

workflow.add_node("start_session",        start_session)
workflow.add_node("navigate_and_extract", navigate_and_extract)
workflow.add_node("summarize",            summarize)
workflow.add_node("cleanup_session",      cleanup_session)

workflow.set_entry_point("start_session")
workflow.add_edge("start_session",        "navigate_and_extract")
workflow.add_edge("navigate_and_extract", "summarize")
workflow.add_edge("summarize",            "cleanup_session")
workflow.add_edge("cleanup_session",      END)

app = workflow.compile()

Run It

import asyncio

async def main():
    result = await app.ainvoke({
        "query":        "headless browser pricing 2026",
        "urls_visited": [],
        "findings":     [],
        "summary":      "",
        "session_id":   "",
    })
    print(result["summary"])

asyncio.run(main())

Why LangGraph + Anchor Works Well Together

  • Persistent state without globals. Typed ResearchState flows through every node—no shared globals, no database reads mid-run.
  • Branching on page content. Add a conditional edge after navigate_and_extract to retry a page, pivot to a different URL, or short-circuit when the target data is found early.
  • Anchor handles the hard parts. CAPTCHA solving, fingerprint stealth, and residential proxy routing all run automatically—your graph nodes stay focused on logic, not infrastructure.
  • Single session, multiple nodes. The same browser session (and its cookies, auth state, and browsing history) persists across every node. No re-login between steps.

Production Tips

  • Use LangGraph’s SqliteSaver or PostgresSaver checkpointers to persist graph state across process restarts—useful for long-running overnight research jobs.
  • Anchor sessions run for up to 24 hours. Match your LangGraph node timeout to your session TTL to avoid orphaned browsers.
  • For parallel research across many URLs, use LangGraph’s Send API to fan out navigate_and_extract across multiple sessions concurrently.
  • Always add a cleanup_session node at the end of your graph so sessions are explicitly stopped and don’t idle after the job completes.

What’s Next

Once you have the core graph running, the next natural steps are:

  • Add login flows via OmniConnect to reach auth-gated pages
  • Route sessions through specific countries with Anchor VPN for geo-targeted research
  • Hook your graph into a scheduled job via Trigger.dev + Anchor

Get Started

Sign up at anchorbrowser.io, grab your API key, and run your first stateful browser agent. The full session configuration reference is at docs.anchorbrowser.io.

Building something interesting with LangGraph and Anchor? We’d love to hear about it.

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.