A fast browser agent built on
langchain-typesafe
and the LangChain SDK. No third-party browser-agent package. The loop is LangChain's
create_agent; TypeSafe is the model, and the browser actions are the tools.
Here, the agent plays the Wikipedia Game, going from LangChain to Microphone:
-
Install:
uv sync uv run playwright install chromium
-
Add a
.envbased on.env.exampleand fill in your values. -
Start the dev server:
uv run langgraph dev
Studio loads two graphs.
browseris a deep agent that plans and delegates each page to thebrowse_fasttool.browser_loopis the browser agent itself, for watching the classifier's decisions one tool call at a time; it holds one browser, so run one goal at a time on it. -
Type a goal. The deep agent plans, hands each page to the
browse_fasttool, and reports back. Each call opens a visible Chromium.Go to https://en.wikipedia.org/wiki/Main_Page and open the article about the Rosetta Stone. Tell me its first sentence.
Find the cheapest direct flights between Montreal and Cancun for a Saturday-to-Saturday trip between December 2026 and February 2027.
TypeSafeClassifier answers categorical questions about a JSON payload in one request.
It returns probabilities over answers you offered, not generated text. A browser step
fits: which operation, and which element. Trimmed from decision.py:
from langchain_typesafe import Choice, TypeSafeClassifier
classifier = TypeSafeClassifier(
questions={
"operation": Choice(
instructions="Advance the goal from the current page using one operation.",
criteria={"CLICK": "Click an element.", "TYPE_TEXT": "Type into a field.", "DONE": "..."},
),
"click_target": Choice(
instructions="Choose the best target if the next operation is CLICK.",
criteria={"14": {"label": "Where to?"}, "21": {"label": "Cancún, Mexico"}},
),
}
)
response = classifier.invoke({"goal": goal, "page": page, "elements": elements})
response.choices["operation"].choice # "CLICK"
response.choices["click_target"].probabilities # {"21": 0.91, "14": 0.09}TypeSafeBrowserModel wraps that call as a chat model: each turn it reads the page from
the last tool result and answers with one tool call, so create_agent runs it as it
would any model.
Docs: provider guide · API reference · TypeSafe docs · speculative fan-out pattern · PyPI
Both Studio graphs are importable pieces.
make_browse_fast_tool() returns a LangChain tool for create_agent or
create_deep_agent.
from deepagents import create_deep_agent
from ts_browser_agent import make_browse_fast_tool
browse_fast = make_browse_fast_tool() # headless=True, allow_private=False by default
agent = create_deep_agent(model="openai:gpt-5.5", tools=[browse_fast])
await agent.ainvoke({"messages": [("user", "Find the pricing page and summarize the tiers.")]})A call returns a status line plus the final page's visible text, so the caller can read
a price or a title from it. headless, allow_private, and text_model are fixed
when the tool is built; they are not tool arguments. Each call builds its own browser
agent, so parallel calls do not share a browser.
url comes from a model, so the open tool checks it with ensure_navigable before
any browser launches.
See examples/deep_agent.py.
build_browser_agent() returns the compiled create_agent graph. It is async-only:
drive it with ainvoke or astream.
import asyncio
from ts_browser_agent import build_browser_agent
agent = build_browser_agent()
goal = "Open the article about the Rosetta Stone.\n\nStart at https://en.wikipedia.org/wiki/Main_Page"
result = asyncio.run(agent.ainvoke({"messages": [("user", goal)]}))
print(result["messages"][-1].content) # "DONE", "BLOCKED", or "STALLED: ..."Name the start URL at the end of the goal. The classifier weighs the goal's opening
heavily, and a goal that opens with the start page reads as anchored to it once the
agent has moved on. DONE is the classifier's judgment, not a guarantee; verify before
acting on it. Options: max_steps, headless, allow_private (permits a local dev
server), and snapshot_filter, a hook applied to every observation before the model
sees it — examples/wiki_game.py uses it to enforce the game's rules.
Every example opens a visible browser so you can watch.
| Example | Path | What it exercises |
|---|---|---|
flight_search.py |
agent | A long click/select chain on real dynamic UI (Google Flights). Search only; it never books |
github_issue.py |
agent | Structurally different UI at each step (tab, filter, list, issue) |
wiki_hop.py |
agent | The same decision (first link in body text) made correctly many times in a row |
wiki_game.py |
agent | Reach one article from another (--start, --end). The game's rules live in a snapshot_filter: no revisits, article links only, no search |
deep_agent.py |
deep agent | The minimal shape: one browse_fast call, one page-scoped goal |
uv run --env-file .env python examples/wiki_game.py --start "Jimmy Page" --end MicrophoneThe design follows jev-ultrafast by
Browser Use: one TypeSafe request per step, a small model for typed text only, no LLM
reasoning per click. The instruction text in decision.py is adapted from it under the
MIT license. The implementation is independent — Playwright rather than Browser
Harness, langchain-typesafe rather than a custom client, and LangChain's
create_agent as the loop.
