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
ResearchStateflows through every node—no shared globals, no database reads mid-run. - Branching on page content. Add a conditional edge after
navigate_and_extractto 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
SqliteSaverorPostgresSavercheckpointers 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
SendAPI to fan outnavigate_and_extractacross multiple sessions concurrently. - Always add a
cleanup_sessionnode 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.



