Attach Predicate’s verification & trace layer to your existing agent.
No rewrite required. Keep your planner, executor, and models.
Predicate can run as a sidecar that observes browser state, evaluates assertions, and produces traces and artifacts when things go wrong.
Not sure whether you should attach as a sidecar or drive the loop with the SDK? Choose your path →
What it does
What it does not do
Predicate Debugger only verifies outcomes. Your agent still decides and executes actions.
Use the same pattern no matter what framework you run:
from predicate import PredicateDebugger
from predicate.tracing import Tracer, JsonlTraceSink
# page is a Playwright Page from your framework
page = agent.get_page()
tracer = Tracer(run_id="run-123", sink=JsonlTraceSink("trace.jsonl"))
dbg = PredicateDebugger.attach(page, tracer=tracer)
# your agent loop
await agent.step()
# verification sidecar
await dbg.snapshot()
await dbg.check(...).eventually()Predicate step IDs are now 0-based. The first auto-generated step will be step-0.
SnapshotOptions without an API key
If you set use_api: false, snapshots are processed locally and do not require a Predicate API key:
await dbg.snapshot(use_api=False, limit=100)Your LLM can decide what to verify, when to verify.
# inside your planner/executor loop
await dbg.snapshot(goal="verify:post-action")
# LLM decides which predicate to add
predicate = url_contains("checkout")
dbg.check(predicate, label="on_checkout", required=True).once()As long as your loop can provide a compatible Page or backend, verification can be added dynamically without changing the loop itself.
Sidecar mode means Predicate doesn’t execute actions — your framework does. To keep traces readable, report what your framework just did:
# after your framework/tool executes an action
await dbg.record_action("page.click('Add to cart')", url=page.url)
await dbg.snapshot(goal="verify:post-action", use_api=False)
await dbg.check(...).eventually()If you call dbg.check(...) without an explicit step, Debugger will open a verify:* step automatically and now auto-closes it after .once()/.eventually() completes. For cleaner traces, prefer explicit step boundaries.
Each adapter answers:
Where is the page?
From BrowserSession.get_current_page().
Where do I verify?
Right after actions in your existing loop.
What happens on failure?
Predicate emits a trace + artifacts (snapshot + screenshot) for that step.
Example integration
See the full working demo (screenshots + stitched video + Studio trace): browser-use-debugging →
Benefits / what you’ll get
How to set up a browser-use agent with PredicateDebugger + AgentRuntime
import os
from predicate import PredicateDebugger, get_extension_dir
from predicate.agent_runtime import AgentRuntime
from predicate.backends import BrowserUseAdapter
from predicate.models import SnapshotOptions
from predicate.tracing import Tracer, JsonlTraceSink
from predicate.verification import any_of, exists, url_contains
# browser-use
from browser_use import BrowserProfile, BrowserSession
# 1) Start browser-use with the Predicate extension loaded
profile = BrowserProfile(args=[f"--load-extension={get_extension_dir()}"], headless=False)
session = BrowserSession(browser_profile=profile)
await session.start()
# 2) Create a Predicate backend from the browser-use session (CDP)
adapter = BrowserUseAdapter(session)
backend = await adapter.create_backend()
# 3) Wrap in AgentRuntime + PredicateDebugger (verification sidecar)
tracer = Tracer(run_id="run-1", sink=JsonlTraceSink("trace.jsonl"))
runtime = AgentRuntime(
backend=backend,
tracer=tracer,
predicate_api_key=os.getenv("PREDICATE_API_KEY"),
snapshot_options=SnapshotOptions(use_api=True, limit=100, screenshot=True, show_overlay=True),
)
dbg = PredicateDebugger(runtime=runtime)
# 4) Verify after actions in your existing loop
await dbg.snapshot(goal="verify:post-action", use_api=True, limit=80, show_overlay=True)
await dbg.check(
any_of(url_contains("dw.com"), exists("text~'DW'")),
label="on_domain",
required=True,
).eventually(timeout_s=10)Where is the page?
Use your LangChain Playwright tool to access the Page.
Where do I verify?
Immediately after the tool executes an action.
Example integration
See the full working demo: langchain-debugging →
What happens on failure?
Your run stops early with a trace you can inspect.
page = langchain_agent.get_page()
dbg = PredicateDebugger.attach(page, tracer=tracer)Where is the page?
Use the same Page used by your graph’s executor node.
Where do I verify?
In the verify node, before branching or retrying.
What happens on failure?
A failed assertion becomes a clear branch condition.
async def verify(state):
await dbg.snapshot()
ok = dbg.check(url_contains("example.com"), label="on_domain", required=True).once()
return { **state, "ok": ok }Where is the page?
Use the same browser/page stored in your agent deps.
Where do I verify?
After tool calls or at the end of each action.
What happens on failure?
You get deterministic verification failure signals to guide retries.
deps = PredicatePydanticDeps(browser=browser)
page = deps.browser.page
dbg = PredicateDebugger.attach(page, tracer=tracer)If your loop is CDP-based, attach the debugger using a BrowserBackend:
from predicate.agent_runtime import AgentRuntime
from predicate.backends import CDPBackendV0
from predicate.debugger import PredicateDebugger
backend = CDPBackendV0(...)
runtime = AgentRuntime(backend=backend, tracer=tracer)
dbg = PredicateDebugger(runtime=runtime)Requirement: you still need Predicate snapshots. That means your CDP setup must expose the extension or a compatible snapshot provider.
When a required assertion fails, Predicate emits:
These traces are designed to explain why a step failed, not just that it failed.