Docs/SDK/Tracing & Debugging

Agent Tracing & Debugging

v0.99.7 - Auto-emit for complete trace structure (run_start, step_start, snapshot) for seamless Studio visualization.

What is a Trace?

  • A trace is an append-only, step-structured log of an agent’s interaction with a web page.
  • Stored as trace.jsonl
  • Each line is a time-ordered event.
  • Traces are:
    • deterministic
    • replayable
    • auditable

“A trace is to a web agent what a commit history is to a Git repo.”

Why Use Traces?

  • Debug Failures: Understand exactly why an agent failed or got stuck
  • Analyze Costs: Track LLM token usage and API calls
  • Replay Sessions: Record and replay agent executions
  • Train Models: Collect successful runs as training data
  • Monitor Production: Track agent performance in real-time

Basic Usage

from predicate import PredicateBrowser, PredicateAgent
from predicate.llm_provider import OpenAIProvider
from predicate.tracing import Tracer, JsonlTraceSink
from predicate.agent_config import AgentConfig

# 1. Create a tracer with JSONL file sink
tracer = Tracer(
    run_id

Trace Events

Each action generates multiple trace events saved to the JSONL file:

Event Types:

Event TypeDescription
run_startAgent run begins (includes agent type, LLM model, config)
step_startAgent begins executing a goal (step_id, goal, attempt)
snapshot / snapshot_takenPage state captured with elements, digests, and diagnostics
llm_called / llm_responseLLM decision made (includes prompt/response hashes, token usage)
action / action_executedAction executed (click, type, press, finish, navigate)
verificationAssertion or verification result (assert, task_done, captcha, scroll)
recoveryRecovery strategy attempted after failure
step_endStep completed (full StepResult with pre/llm/exec/post/verify)
run_endAgent run completed (status: success/failure/partial/unknown)
errorError occurred during step execution

Example trace.jsonl:

{"v":1,"type":"run_start","ts":"2025-12-26T10:00:00.000Z","run_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","seq":1,"data":{"agent":"PredicateAgent","llm_model":"gpt-4o","config":{}}}
{"v":1,"type":"step_start","ts":"2025-12-26T10:00:00.100Z","run_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","seq":2,"step_id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","data":{"step_id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","step_index":1,"goal":"Click the search box","attempt":0,"pre_url":"https://amazon.com"}}
{"v":1,"type":"snapshot_taken","ts":"2025-12-26T10:00:01.000Z","run_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","seq":3,"step_id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","data":{"snapshot_digest":"sha256:abc123...","snapshot_digest_loose":"sha256:def456...","url":"https://amazon.com","element_count":127}}
{"v":1,"type":"llm_called","ts":"2025-12-26T10:00:02.000Z","run_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","seq":4,"step_id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","data":{"step_id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","model":"gpt-4o","response_text":"CLICK(42)","response_hash":"sha256:...","usage":{"prompt_tokens":1523,"completion_tokens":12,"total_tokens":1535}}}
{"v":1,"type":"action_executed","ts":"2025-12-26T10:00:03.000Z","run_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","seq":5,"step_id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","data":{"kind":"click","element_id":42,"success":true,"outcome":"dom_updated","duration_ms":234}}
{"v":1,"type":"step_end","ts":"2025-12-26T10:00:03.500Z","run_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","seq":6,"step_id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","data":{"step_id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","step_index":1,"goal":"Click the search box","attempt":0,"pre":{"url":"https://amazon.com","snapshot_digest":"sha256:abc123..."},"llm":{"response_text":"CLICK(42)","response_hash":"sha256:...","usage":{"prompt_tokens":1523,"completion_tokens":12}},"exec":{"success":true,"outcome":"dom_updated","duration_ms":234},"post":{"url":"https://amazon.com","snapshot_digest":"sha256:xyz789..."},"verify":{"passed":true,"policy":"default"}}}

Snapshot Events for Studio Screenshots

NEW in v0.99.7 - Screenshots are automatically emitted to traces for visualization in Predicate Studio.

Auto-Emit (Default Behavior)

When you call runtime.snapshot(), a snapshot trace event with screenshot_base64 is automatically emitted. This ensures screenshots appear in the Predicate Studio timeline without any extra code.

from predicate.agent_runtime import AgentRuntime

# Default: auto-emit is enabled (emit_trace=True)
snapshot = await runtime.snapshot()  # Automatically emits snapshot trace event

# The trace now includes screenshot_base64 for Studio visualization
# No additional code needed!

Disabling Auto-Emit

If you need manual control over when snapshot events are emitted, disable auto-emit:

# Disable auto-emit for manual control
snapshot = await runtime.snapshot(emit_trace=False)

# Later, manually emit the snapshot when ready
tracer.emit_snapshot(
    snapshot=snapshot,
    step_id=runtime.step_id,
    step_index=

Using emit_snapshot() / emitSnapshot() Directly

The tracer provides a dedicated helper method for emitting snapshot events:

from predicate.tracing import Tracer, JsonlTraceSink

tracer = Tracer(run_id="my-run", sink=JsonlTraceSink("trace.jsonl"))

# Emit snapshot event with screenshot for Studio visualization
tracer.emit_snapshot(
    snapshot=my_snapshot,

Snapshot Event Format

The snapshot event includes these key fields for Studio visualization:

{
  "v": 1,
  "type": "snapshot",
  "ts": "2025-12-26T10:00:01.000Z",
  "run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "seq": 3,
  "step_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "data": {
    "url": "https://example.com",
    "element_count": 127,
    "step_index": 1,
    "elements": [...],
    "screenshot_base64": "iVBORw0KGgo...",
    "screenshot_format": "jpeg"
  }
}

Key fields:

  • screenshot_base64 - Base64-encoded screenshot image (JPEG or PNG)
  • screenshot_format - Image format ("jpeg" or "png")
  • step_index - Step number for timeline correlation
  • step_id - UUID linking snapshot to a specific step

When to Use Manual Emit

Manual emission via emit_trace=False is useful when:

  1. Batching snapshots - Taking multiple snapshots but only emitting the final one
  2. Custom processing - Applying PII redaction or other transforms before emission
  3. Conditional emission - Only emitting snapshots that meet certain criteria
  4. Performance optimization - Reducing trace size by selectively emitting snapshots

Screenshot Processing

Apply custom processing (e.g., PII redaction) before screenshots are emitted. The screenshot_processor callback receives a base64-encoded image string and must return a processed base64 string.

Optional Dependencies for Image Processing

For custom screenshot processing (PII redaction, blurring, masking), you may need image processing libraries:

# Python: Pillow is optional but recommended for image manipulation
pip install Pillow

# Or install with the vision-local extras
pip install sentienceapi[vision-local]

Note: The SDK automatically converts non-JPEG screenshots to JPEG format during cloud upload. This conversion requires Pillow in Python. If Pillow is not installed, non-JPEG screenshots will be uploaded in their original format with a warning.

Example: PII Redaction with Image Processing

from predicate.tracer_factory import create_tracer
import base64
from io import BytesIO

def redact_pii(screenshot_base64: str) -> str:
    """Redact PII by blurring sensitive regions."""
    try:

Simple Example (No Dependencies)

If you don't need image manipulation, you can still use the processor for logging or validation:

from predicate.tracer_factory import create_tracer

def log_screenshot(screenshot_base64: str) -> str:
    """Log screenshot size without modifying it."""
    size_kb = len(screenshot_base64) * 3

Complete Trace Structure (Auto-Emit)

NEW in v0.99.7 - The SDK now automatically emits a complete trace structure for proper visualization in Predicate Studio. This includes run_start, step_start, snapshots, and run_end events.

Tracer Factory Auto-Emits run_start

When you create a tracer using create_tracer() / createTracer(), the run_start event is automatically emitted with the metadata you provide. This ensures every trace has a proper starting point for Studio visualization.

from predicate.tracer_factory import create_tracer

# run_start is automatically emitted with metadata
tracer = create_tracer(
    api_key="sk_pro_xxxxx",
    run_id="my-run-123",
    upload_trace=True,
    goal="Add headphones to cart",        # Displayed as trace name in Studio

AgentRuntime Auto-Emits step_start

When you call runtime.begin_step() / runtime.beginStep(), the step_start event is automatically emitted. This ensures each step has a proper starting point in the trace timeline.

from predicate.agent_runtime import AgentRuntime

# step_start is automatically emitted when you begin a step
step_id = runtime.begin_step(
    goal="Click the search button",
    step_index=1,
    pre_url="https://example.com",  # Optional: URL before step
)

Emitting run_end

The run_end event must be emitted manually before closing the tracer to signal the run's completion status:

# After all steps are complete, emit run_end
tracer.emit_run_end(
    steps=total_steps,           # Total number of steps executed
    status="success"             # "success", "failure", "partial", or "unknown"
)

# Then close the tracer to upload
tracer.close()

Complete Trace Lifecycle Example

Here's a complete example showing all auto-emit events in action:

from predicate.tracer_factory import create_tracer
from predicate.agent_runtime import AgentRuntime

# 1. Create tracer - run_start is auto-emitted
tracer = create_tracer(
    api_key="sk_pro_xxxxx",
    run_id="complete-example",
    upload_trace=True,
    goal

Summary of Auto-Emit Behavior

EventAuto-Emit MethodDefaultOpt-Out Parameter
run_startcreate_tracer() / createTracer()Enabledauto_emit_run_start=False / autoEmitRunStart: false
step_startruntime.begin_step() / runtime.beginStep()Enabledemit_trace=False / emitTrace: false
snapshotruntime.snapshot()Enabledemit_trace=False / emitTrace: false
run_endManualN/AN/A (always manual)

AgentConfig Options

Configure agent behavior with AgentConfig:

from predicate.agent_config import AgentConfig

config = AgentConfig(
    # Snapshot settings
    snapshot_limit=50,              # Max elements to include (default: 50)

    # LLM settings
    temperature=0.0,                # LLM temperature 0.0-1.0 (default: 0.0)
    max_retries=1,

Snapshot Utilities

Snapshot Digests (Loop Detection)

Compute fingerprints to detect when page state hasn't changed:

from predicate import snapshot
from predicate.utils import compute_snapshot_digests

snap = snapshot(browser)

# Compute both strict and loose digests
digests = compute_snapshot_digests(snap.elements)

print(digests["strict"]

Digest Types:

  • Strict Digest: Includes element text - detects any content change
  • Loose Digest: Ignores text - only detects layout/structure changes

LLM Prompt Formatting

Format snapshots for LLM consumption:

from predicate.formatting import format_snapshot_for_llm

snap = snapshot(browser)

# Format top 50 elements for LLM context
llm_context = format_snapshot_for_llm(snap, limit=50)

print(llm_context)
# Output:
# [1] <button> "Sign In" {PRIMARY,CLICKABLE} @ (100,50) (Imp:10)

Format Explanation:

  • [ID] - Element ID for actions
  • <role> - Semantic role (button, input, link, etc.)
  • "text" - Element text (truncated to 50 chars)
  • {CUES} - Visual cues (PRIMARY, CLICKABLE)
  • @ (x,y) - Screen position
  • (Imp:score) - Importance score (0-10)

Cloud Tracing & Screenshots

NEW in v0.12.0+ - Upload traces and screenshots to cloud storage for remote viewing, analysis, and collaboration.

Function Signature

The create_tracer() function signature:

def create_tracer(
    api_key: str | None = None,
    run_id: str | None = None,
    api_url: str | None = None,
    logger: PredicateLogger | None = None,
    upload_trace: bool = False,
    goal: str | None = None,
    agent_type: str | None = None,
    llm_model: str | None = None,
    start_url: str | None = None,
    screenshot_processor: Callable[[str], str] | None = None,
)

Overview

Cloud tracing enables Pro, Builder, Teams, and Enterprise tier users to:

  • Upload traces to cloud - Access traces from any device via web interface
  • Automatic screenshot capture - Visual debugging with time-travel replay
  • Crash recovery - Traces survive process crashes and are automatically recovered
  • Non-blocking uploads - Agent continues immediately while uploads happen in background
  • Zero performance impact - Writes to local cache (10μs) instead of network (50ms+)

Quick Start

from predicate import PredicateBrowser, PredicateAgent
from predicate.llm_provider import OpenAIProvider
from predicate.tracer_factory import create_tracer

# 1. Create tracer with automatic tier detection

tracer = create_tracer(
    api_key="sk_pro_xxxxx",  # Pro/Builder/Teams/Enterprise tier key
    run_id

Automatic Tier Detection

The create_tracer() function automatically detects your tier and configures the appropriate sink:

Pro/Builder/Teams/Enterprise Tier (with API key and upload enabled):

  • Uses CloudTraceSink (uploads to cloud)
  • Prints: "[Predicate] Cloud tracing enabled (Pro/Builder/Teams/Enterprise tiers)"

Local-only tracing (opt-out of cloud upload):

  • Uses JsonlTraceSink (local-only even with API key)
  • Prints: "💾 [Predicate] Local tracing: traces/demo.jsonl"

Free Tier (no API key):

  • Uses JsonlTraceSink (local-only)
  • Prints: "💾 [Predicate] Local tracing: traces/demo.jsonl"

Graceful Fallback:

  • If cloud init fails (network error, timeout, etc.), automatically falls back to local tracing
  • Agent continues working normally - no crashes or errors
  • Traces are preserved locally for later upload

Viewing Traces in Cloud

After uploading, access your traces via:

  • Predicate Studio: https://predicatesystems.ai/studio
  • API: GET /api/traces/list to list all runs
  • Web Interface: Time-travel debugging with screenshot replay

Best Practices

  1. Always close the tracer:

    try:
        # Your agent code
        pass
    finally:
        tracer.close()  # Ensures upload even on errors
  2. Use non-blocking uploads for long-running agents:

    tracer.close(blocking=False)  # Don't wait for upload
  3. Set meaningful run IDs and metadata:

    tracer = create_tracer(
        api_key="sk_pro_xxxxx",
        run_id=f"amazon-shopping-{datetime.now().strftime('%Y%m%d-%H%M%S')}",
        upload_trace=True,
        goal="Buy a laptop from Amazon",
        agent_type="Amazon Shopping Agent",
        llm_model="gpt-4o",
        start_url="https://www.amazon.com",
        screenshot_processor=None, # function for PII redaction
    )
  4. Enable screenshots for debugging:

    config = AgentConfig(capture_screenshots=True)
    agent = PredicateAgent(browser, llm, tracer=tracer, config=config)

Custom Trace Sinks

Implement custom trace storage by extending the TraceSink interface. This allows you to store traces in databases, cloud storage, or any custom backend.

TraceSink Interface

from predicate.tracing import TraceSink

class CustomTraceSink(TraceSink):
    """Base interface for trace storage"""

    def emit(self, event_dict: dict) -> None:

Example: Database Trace Sink

Store traces directly in a database:

from predicate.tracing import TraceSink, Tracer
import psycopg2
import json

class DatabaseTraceSink(TraceSink):
    """Store traces in PostgreSQL database"""

    def __init__(self, connection_string: str

Example: Cloud Storage Sink

Upload traces to S3, Google Cloud Storage, or other cloud providers:

from predicate.tracing import TraceSink
import boto3
import json
from typing import List

class S3TraceSink(TraceSink):
    """Store traces in AWS S3"""

    def __init__(self, bucket:

Example: Multi-Sink (Write to Multiple Destinations)

Write traces to multiple sinks simultaneously:

from predicate.tracing import TraceSink, JsonlTraceSink
from typing import List

class MultiSink(TraceSink):
    """Write to multiple trace sinks simultaneously"""

    def __init__(self, sinks: List[TraceSink

PredicateLogger Interface

Integrate Predicate tracing with your existing logging infrastructure using the PredicateLogger interface.

Logger Interface

from typing import Protocol

class PredicateLogger(Protocol):
    """Protocol for optional logger interface."""

    def info(self, message: str) -> None:
        """Log info message."""

Using Python's Built-in Logger

import logging
from predicate import create_tracer

# Use Python's built-in logging module
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# Add handler to output to console
handler = logging.StreamHandler()

Custom Logger Implementation

from predicate import create_tracer

class CustomLogger:
    """Custom logger implementation"""

    def info(self, message: str) -> None:
        print(f"[INFO] {

What Gets Logged

The logger receives the following types of messages:

  • File sizes - Compressed trace file size and screenshot totals (in MB)
  • Upload completion - Confirmation that trace was reported to gateway
  • Errors - Upload failures, measurement errors, API errors
  • Warnings - Non-critical issues like failed completion reports

Benefits:

  • Integration - Connect Predicate to your existing logging system (Winston, Bunyan, etc.)
  • Monitoring - Track trace upload metrics in production
  • Debugging - See detailed file sizes and upload status
  • Silent mode - Omit logger parameter for no logging (default behavior)

Crash Recovery

Traces automatically survive process crashes and are recovered on next SDK initialization.

How It Works

  1. Persistent Cache - Traces are stored in ~/.sentience/traces/pending/ during execution
  2. Process Crash - If your script crashes, traces remain on disk
  3. Automatic Recovery - Next time you create a tracer, orphaned traces are automatically detected
  4. Auto-Upload - Recovered traces are uploaded to cloud (if using Pro/Builder/Teams/Enterprise tier)

Example: Crash and Recovery

from predicate import create_tracer, PredicateBrowser, PredicateAgent
from predicate.llm import OpenAIProvider

# Run 1: Agent crashes mid-execution
tracer = create_tracer(
    api_key="sk_pro_xxxxx",
    run_id="run-1",
    upload_trace=True,

Recovery Details

  • Cache Location - ~/.sentience/traces/pending/{run_id}.jsonl
  • Auto-Detection - Happens on create_tracer() initialization
  • Upload Priority - Orphaned traces uploaded before new trace begins
  • Error Handling - If upload fails, traces remain in cache for next retry
  • Manual Cleanup - You can safely delete traces from ~/.sentience/traces/pending/

Best Practices for Crash Recovery

  1. Always use meaningful run IDs:

    run_id = f"shopping-{datetime.now().isoformat()}"
    tracer = create_tracer(
        api_key="sk_pro_xxxxx",
        run_id=run_id,
        upload_trace=True,
        goal="Buy a laptop from Amazon",
        agent_type="Amazon Shopping Agent",
        llm_model="gpt-4o",
        start_url="https://www.amazon.com",
        screenshot_processor=None, # function for PII redaction
    )
  2. Monitor cache directory:

    ls -lh ~/.sentience/traces/pending/
  3. Use try/finally for cleanup:

    tracer = create_tracer(
        api_key="sk_pro_xxxxx",
        run_id="run-123",
        upload_trace=True,
        goal="Complete task",
        agent_type="My Agent",
        llm_model="gpt-4o",
        start_url="https://example.com",
        screenshot_processor=None, # function for PII redaction
    )
    try:
        # Agent code
        agent.act("Do something")
    finally:
        tracer.close()  # Ensures upload even on errors
  4. Check logs for recovery messages:

    • Look for "Found N un-uploaded trace(s)" messages
    • Verify "Uploaded orphaned trace: [run-id]" confirmations

Non-Blocking Trace Uploads

Upload traces in the background to avoid blocking your script execution.

Blocking vs Non-Blocking

from predicate import create_tracer

tracer = create_tracer(
    api_key="sk_pro_xxxxx",
    run_id="run-123",
    upload_trace=True,
    goal="Example task",
    agent_type="Example Agent",
    llm_model="gpt-4o"

Progress Callbacks

Monitor upload progress with callbacks:

def progress_callback(uploaded_bytes: int, total_bytes: int):
    percent = (uploaded_bytes / total_bytes) * 100
    print(f"Upload progress: {percent:

Use Cases

Use non-blocking uploads when:

  • Running long-running agents that produce multiple traces
  • Deploying in serverless environments with time limits
  • Performance is critical (don't wait for network I/O)

Use blocking uploads when:

  • You need confirmation that upload succeeded
  • Running in CI/CD and need to verify all traces uploaded
  • Script exits immediately after tracing

Troubleshooting

"Cloud tracing requires Pro tier"

Cause: Your API key is valid but account is on Free tier

Solution:

  • Upgrade to Pro, Builder, Teams, or Enterprise tier at https://predicatesystems.ai/pricing
  • Traces will automatically use local storage until upgrade
  • Set upload_trace=False to suppress this message

"Cloud init timeout"

Cause: Network connectivity issue or API service temporarily unavailable

Solution:

  • Check your internet connection
  • Verify API endpoint is reachable
  • SDK automatically falls back to local tracing
  • Traces saved locally can be uploaded later via crash recovery

"Upload failed: HTTP 500"

Cause: Server error (temporary)

Solution:

  • Trace is preserved locally at ~/.sentience/traces/pending/{run_id}.jsonl
  • Retry upload by creating tracer again (automatic recovery kicks in)
  • Check service status at https://status.predicatesystems.ai

"Local trace preserved"

Cause: Upload failed but trace is safely stored on disk

Solution:

  • Check network connection and retry
  • Trace file location is printed in console
  • Next tracer initialization will automatically retry upload
  • You can manually upload by copying to cloud storage

Trace Schema Reference

All trace events follow this structure:

{
  "v": 1,                                              // Schema version (always 1)
  "type": "event_type",                                // Event type (see table below)
  "ts": "2025-12-26T10:00:00.000Z",                    // ISO 8601 timestamp
  "run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",   // UUID for the agent run
  "seq": 1,                                            // Monotonically increasing sequence number
  "step_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",  // UUID for the step (optional)
  "data": {...},                                       // Event-specific payload
  "ts_ms": 1735210800000                               // Unix timestamp in milliseconds (optional)
}

⚠️ Warning: Trace formats and internal fields may evolve and are not guaranteed to be stable across versions.

Event-Specific Data Schemas:

run_start - Agent run begins
{
  "agent": "PredicateAgent",
  "llm_model": "gpt-4o",
  "config": {}
}
step_start - Step execution begins
{
  "step_id": "uuid",
  "step_index": 1,
  "goal": "Click the search box",
  "attempt": 0,
  "pre_url": "https://example.com"
}
snapshot / snapshot_taken - Page state captured
{
  "step_id": "uuid",
  "snapshot_id": "uuid",
  "snapshot_digest": "sha256:abc123...",
  "snapshot_digest_loose": "sha256:def456...",
  "url": "https://example.com",
  "element_count": 127,
  "timestamp": "2025-12-26T10:00:01.000Z",
  "diagnostics": {
    "confidence": 0.95,
    "reasons": ["dom_stable", "no_pending_requests"],
    "metrics": {
      "ready_state": "complete",
      "quiet_ms": 500,
      "node_count": 1234,
      "interactive_count": 45
    },
    "captcha": {
      "detected": false,
      "provider_hint": null,
      "confidence": 0.0,
      "evidence": {}
    }
  },
  "elements": [
    {
      "id": 42,
      "role": "button",
      "text": "Search",
      "importance": 950,
      "bbox": {"x": 100, "y": 50, "width": 80, "height": 32},
      "visual_cues": {"is_primary": true, "is_clickable": true},
      "in_viewport": true,
      "is_occluded": false,
      "z_index": 10
    }
  ],
  "screenshot_base64": "iVBORw0KGgo...",
  "screenshot_format": "png"
}
llm_called - LLM query made
{
  "step_id": "uuid",
  "model": "gpt-4o",
  "temperature": 0.0,
  "system_prompt_hash": "sha256:...",
  "user_prompt_hash": "sha256:...",
  "response_text": "CLICK(42)",
  "response_hash": "sha256:...",
  "usage": {
    "prompt_tokens": 1523,
    "completion_tokens": 12,
    "total_tokens": 1535
  }
}
action / action_executed - Action executed
{
  "kind": "click",           // click, type, press, finish, navigate
  "element_id": 42,
  "text": "search query",    // for type action
  "key": "Enter",            // for press action
  "url": "https://...",      // for navigate action
  "raw": "CLICK(42)"
}
verification - Assertion/verification result
{
  "step_id": "uuid",
  "passed": true,
  "kind": "assert",          // assert, task_done, captcha
  "label": "Search box is visible",
  "required": true,
  "reason": "Element found with matching text",
  "details": {},
  "signals": {}
}
recovery - Recovery strategy attempted
{
  "step_id": "uuid",
  "strategy": "retry",
  "attempt": 1
}
step_end - Step completed (full StepResult)
{
  "v": 1,
  "step_id": "uuid",
  "step_index": 1,
  "goal": "Click the search box",
  "attempt": 0,
  "pre": {
    "url": "https://example.com",
    "snapshot_digest": "sha256:abc123...",
    "snapshot_digest_loose": "sha256:def456..."
  },
  "llm": {
    "response_text": "CLICK(42)",
    "response_hash": "sha256:...",
    "usage": {"prompt_tokens": 1523, "completion_tokens": 12, "total_tokens": 1535}
  },
  "action": {
    "kind": "click",
    "element_id": 42,
    "raw": "CLICK(42)"
  },
  "exec": {
    "success": true,
    "outcome": "dom_updated",
    "action": "click",
    "element_id": 42,
    "duration_ms": 234,
    "url_changed": false,
    "bounding_box": {"x": 100, "y": 50, "width": 80, "height": 32}
  },
  "post": {
    "url": "https://example.com",
    "snapshot_digest": "sha256:xyz789...",
    "snapshot_digest_loose": "sha256:uvw123..."
  },
  "verify": {
    "passed": true,
    "policy": "default",
    "signals": {
      "url_changed": false,
      "assertions": [
        {"label": "Element clicked", "passed": true, "required": true}
      ],
      "task_done": false
    }
  },
  "recovery": null
}
run_end - Agent run completed
{
  "steps": 5,
  "status": "success"        // success, failure, partial, unknown
}
error - Error occurred
{
  "step_id": "uuid",
  "attempt": 0,
  "error": "Element not found: id=42"
}

Next Steps