Docs/SDK/Predicate Debugger

Predicate Debugger integration

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 →


1) Concept

What it does

What it does not do

You keep control

Predicate Debugger only verifies outcomes. Your agent still decides and executes actions.


2) Universal Pattern

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()

Step IDs start at step-0

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)

3) Dynamic Checks

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.


3.1) Improve trace readability with record_action / recordAction

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()

Auto-step is self-contained

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.


4) Framework-Specific Adapters

Each adapter answers:

  1. Where do I get the page?
  2. Where do I insert verification?
  3. What do I get when it fails?

browser-use

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)

LangChain

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)

LangGraph

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 }

Pydantic AI

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)

5) CDP / Non‑Playwright Loops

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.


6) When it Fails

When a required assertion fails, Predicate emits:

These traces are designed to explain why a step failed, not just that it failed.