This guide shows how to use Sentience as a browser tool layer inside:
Both Python and TypeScript examples are provided throughout this guide.
Sentience owns browser execution + structured state. LangChain/LangGraph own orchestration. Together, they enable reliable observe → act → verify → branch/retry flows.
Agent frameworks get unreliable when browser actions aren't verified. Sentience's value is that it gives you structured observation (element IDs/roles/bboxes) and lets you combine act → verify deterministically.
Implement your agent as:
observe → act → verify → branch/retry
Sentience gives you stable primitives for each step:
| Step | Primitives |
|---|---|
| Observe | snapshot(limit=50), read(text/markdown/raw), findTextRect |
| Act | click, type_text, press, scroll_to, navigate, click_rect |
| Verify | URL/text guards, assertions (Python SDK), retry loops |
Install Sentience with LangChain support:
pip install sentienceapi[langchain]Integration entrypoints:
sentience.integrations.langchain.SentienceLangChainCore — framework-agnostic, async wrapperssentience.integrations.langchain.build_sentience_langchain_tools(...) — LangChain StructuredTool packExamples:
sdk-python/examples/lang-chain/langchain_tools_demo.pysdk-python/examples/lang-chain/langgraph_self_correcting_graph.pyInstall Sentience with LangChain dependencies:
npm install sentienceapi @langchain/core zodExample:
sdk-ts/examples/lang-chain/sentience-tools-demo.tsNote: The TypeScript integration is intentionally lightweight—you can wrap Sentience primitives into LangChain JS tools without extra SDK layers.
snapshot is for interaction planning (element IDs/roles/bboxes). It's how you choose what to click/type/scroll.read is for extraction and verification ("did the success message appear?", "what's the price?").from sentience import AsyncSentienceBrowser
from sentience.integrations.langchain import SentienceLangChainContext, SentienceLangChainCore
ctx = SentienceLangChainContext(browser=browser)
core = SentienceLangChainCore(ctx)
state = await core.snapshot_state(limit=50)
page = await core.read_page(format="markdown")click(element_id)) — more stable across minor layout shiftsfindTextRect + click_rect) when:
# Element-ID based actions
await core.click(element_id=123)
await core.type_text(element_id=123, text="hello")
await core.press_key("Enter")
await core.scroll_to(element_id=123)
await core.navigate("https://example.com")
# Coordinate-based actions
matches = await core.find_text_rect("Sign In")
m0 = next((m for m in (matches.results or []) if m.in_viewport), None)
if m0:
await core.click_rect(
x=m0.rect.x,
y=m0.rect.y,
width=m0.rect.width,
height=m0.rect.height
)Browser actions can be "successful" at the API level but still fail to reach the intended state (SPA transitions, slow redirects, flakey DOM updates).
The fix: Always pair an action with a guard:
# Simple verification
out = await core.verify_url_matches(r"/checkout")
if not out.passed:
# retry or branch
...
# Built-in retry helper (polls until timeout)
out = await core.assert_eventually_url_matches(
r"/checkout",
timeout_s=10.0,
poll_s=0.25
)
# How retry works:
# - Every poll_s seconds, re-check the condition
# - Stop early if it passes
# - Fail once timeout_s is exceededLangChain wants tools with stable schemas and descriptions. The best integration is a small tool pack that exposes Sentience primitives and keeps outputs bounded (snapshot default limit=50).
from sentience.integrations.langchain import (
SentienceLangChainContext,
build_sentience_langchain_tools
)
ctx = SentienceLangChainContext(browser=browser)
tools = build_sentience_langchain_tools(ctx)
# Use with your LangChain agent
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools)
# See: sdk-python/examples/lang-chain/langchain_tools_demo.pyGraphs make retries/branches explicit and testable. "Act → verify → branch" is the simplest way to eliminate flaky web agents.
See: sdk-python/examples/lang-chain/langgraph_self_correcting_graph.py
Graph structure:
observe, act, verifyobserve if verify fails (with bounded retries)from langgraph.graph import StateGraph
# Define state
class AgentState(TypedDict):
url: str
attempts: int
done: bool
error: str | None
# Create graph
workflow = StateGraph(AgentState)
# Add nodes
workflow.add_node("observe", observe_node)
workflow.add_node("act", act_node)
workflow.add_node("verify", verify_node)
# Add edges with conditional routing
workflow.add_edge("observe", "act")
workflow.add_edge("act", "verify")
workflow.add_conditional_edges(
"verify",
should_retry, # returns "observe" or "end"
{
"observe": "observe",
"end": END
}
)If you adopt LangGraph JS, use the same pattern:
url, attempts, done(We currently ship a TS LangChain tool demo, not a full TS LangGraph template.)
When a run fails, you need to know whether the failure was:
Sentience tracing captures the browser-side truth.
Pass a Tracer into the integration context:
from sentience import create_tracer
from sentience.integrations.langchain import SentienceLangChainContext
# Local trace (JSONL to disk)
tracer = create_tracer(run_id="langchain-demo")
# Cloud trace (Pro/Enterprise - uploads on close)
tracer = create_tracer(
api_key="sk_pro_...",
upload_trace=True,
goal="LangChain + Sentience run",
agent_type="LangChain",
)
# Pass to context
ctx = SentienceLangChainContext(browser=browser, tracer=tracer)
core = SentienceLangChainCore(ctx)
# ... run your agent ...
# Always close to flush/upload
tracer.close()Key insight: Your framework (LangChain/LangGraph) owns LLM orchestration, while Sentience owns browser execution + structured state.
You can (and often should) instrument both:
This dual-layer observability gives you complete visibility into both what the agent decided and what it actually did in the browser.
See also: Observability documentation
Last updated: January 2026