22Agent runtime for verification loop support.
33
44This module provides a thin runtime wrapper that combines:
5- 1. Browser session management
5+ 1. Browser session management (via BrowserBackendV0 protocol)
662. Snapshot/query helpers
773. Tracer for event emission
884. Assertion/verification methods
99
1010The AgentRuntime is designed to be used in agent verification loops where
1111you need to repeatedly take snapshots, execute actions, and verify results.
1212
13- Example usage:
14- from sentience import AsyncSentienceBrowser
13+ Example usage with browser-use:
14+ from browser_use import BrowserSession, BrowserProfile
15+ from sentience import get_extension_dir
16+ from sentience.backends import BrowserUseAdapter
1517 from sentience.agent_runtime import AgentRuntime
1618 from sentience.verification import url_matches, exists
1719 from sentience.tracing import Tracer, JsonlTraceSink
1820
21+ # Setup browser-use with Sentience extension
22+ profile = BrowserProfile(args=[f"--load-extension={get_extension_dir()}"])
23+ session = BrowserSession(browser_profile=profile)
24+ await session.start()
25+
26+ # Create adapter and backend
27+ adapter = BrowserUseAdapter(session)
28+ backend = await adapter.create_backend()
29+
30+ # Navigate using browser-use
31+ page = await session.get_current_page()
32+ await page.goto("https://example.com")
33+
34+ # Create runtime with backend
35+ sink = JsonlTraceSink("trace.jsonl")
36+ tracer = Tracer(run_id="test-run", sink=sink)
37+ runtime = AgentRuntime(backend=backend, tracer=tracer)
38+
39+ # Take snapshot and run assertions
40+ await runtime.snapshot()
41+ runtime.assert_(url_matches(r"example\\ .com"), label="on_homepage")
42+ runtime.assert_(exists("role=button"), label="has_buttons")
43+
44+ # Check if task is done
45+ if runtime.assert_done(exists("text~'Success'"), label="task_complete"):
46+ print("Task completed!")
47+
48+ Example usage with AsyncSentienceBrowser (backward compatible):
49+ from sentience import AsyncSentienceBrowser
50+ from sentience.agent_runtime import AgentRuntime
51+
1952 async with AsyncSentienceBrowser() as browser:
2053 page = await browser.new_page()
2154 await page.goto("https://example.com")
2255
23- sink = JsonlTraceSink("trace.jsonl")
24- tracer = Tracer(run_id="test-run", sink=sink)
25-
26- runtime = AgentRuntime(browser=browser, page=page, tracer=tracer)
27-
28- # Take snapshot and run assertions
56+ runtime = await AgentRuntime.from_sentience_browser(
57+ browser=browser,
58+ page=page,
59+ tracer=tracer,
60+ )
2961 await runtime.snapshot()
30- runtime.assert_(url_matches(r"example\\ .com"), label="on_homepage")
31- runtime.assert_(exists("role=button"), label="has_buttons")
32-
33- # Check if task is done
34- if runtime.assert_done(exists("text~'Success'"), label="task_complete"):
35- print("Task completed!")
3662"""
3763
3864from __future__ import annotations
3965
4066import uuid
4167from typing import TYPE_CHECKING , Any
4268
43- from .verification import AssertContext , AssertOutcome , Predicate
69+ from .models import Snapshot , SnapshotOptions
70+ from .verification import AssertContext , Predicate
4471
4572if TYPE_CHECKING :
4673 from playwright .async_api import Page
4774
75+ from .backends .protocol_v0 import BrowserBackendV0
4876 from .browser import AsyncSentienceBrowser
49- from .models import Snapshot
5077 from .tracing import Tracer
5178
5279
@@ -63,8 +90,7 @@ class AgentRuntime:
6390 to the tracer for Studio timeline display.
6491
6592 Attributes:
66- browser: AsyncSentienceBrowser instance
67- page: Playwright Page instance
93+ backend: BrowserBackendV0 instance for browser operations
6894 tracer: Tracer for event emission
6995 step_id: Current step identifier
7096 step_index: Current step index (0-based)
@@ -73,36 +99,90 @@ class AgentRuntime:
7399
74100 def __init__ (
75101 self ,
76- browser : AsyncSentienceBrowser ,
77- page : Page ,
102+ backend : BrowserBackendV0 ,
78103 tracer : Tracer ,
104+ snapshot_options : SnapshotOptions | None = None ,
105+ sentience_api_key : str | None = None ,
79106 ):
80107 """
81- Initialize agent runtime.
108+ Initialize agent runtime with any BrowserBackendV0-compatible browser .
82109
83110 Args:
84- browser: AsyncSentienceBrowser instance for taking snapshots
85- page: Playwright Page for browser interaction
111+ backend: Any browser implementing BrowserBackendV0 protocol.
112+ Examples:
113+ - CDPBackendV0 (for browser-use via BrowserUseAdapter)
114+ - PlaywrightBackend (future, for direct Playwright)
86115 tracer: Tracer for emitting verification events
116+ snapshot_options: Default options for snapshots
117+ sentience_api_key: API key for Pro/Enterprise tier (enables Gateway refinement)
87118 """
88- self .browser = browser
89- self .page = page
119+ self .backend = backend
90120 self .tracer = tracer
91121
122+ # Build default snapshot options with API key if provided
123+ default_opts = snapshot_options or SnapshotOptions ()
124+ if sentience_api_key :
125+ default_opts .sentience_api_key = sentience_api_key
126+ if default_opts .use_api is None :
127+ default_opts .use_api = True
128+ self ._snapshot_options = default_opts
129+
92130 # Step tracking
93131 self .step_id : str | None = None
94132 self .step_index : int = 0
95133
96134 # Snapshot state
97135 self .last_snapshot : Snapshot | None = None
98136
137+ # Cached URL (updated on snapshot or explicit get_url call)
138+ self ._cached_url : str | None = None
139+
99140 # Assertions accumulated during current step
100141 self ._assertions_this_step : list [dict [str , Any ]] = []
101142
102143 # Task completion tracking
103144 self ._task_done : bool = False
104145 self ._task_done_label : str | None = None
105146
147+ @classmethod
148+ async def from_sentience_browser (
149+ cls ,
150+ browser : AsyncSentienceBrowser ,
151+ page : Page ,
152+ tracer : Tracer ,
153+ snapshot_options : SnapshotOptions | None = None ,
154+ sentience_api_key : str | None = None ,
155+ ) -> AgentRuntime :
156+ """
157+ Create AgentRuntime from AsyncSentienceBrowser (backward compatibility).
158+
159+ This factory method wraps an AsyncSentienceBrowser + Page combination
160+ into the new BrowserBackendV0-based AgentRuntime.
161+
162+ Args:
163+ browser: AsyncSentienceBrowser instance
164+ page: Playwright Page for browser interaction
165+ tracer: Tracer for emitting verification events
166+ snapshot_options: Default options for snapshots
167+ sentience_api_key: API key for Pro/Enterprise tier
168+
169+ Returns:
170+ AgentRuntime instance
171+ """
172+ from .backends .playwright_backend import PlaywrightBackend
173+
174+ backend = PlaywrightBackend (page )
175+ runtime = cls (
176+ backend = backend ,
177+ tracer = tracer ,
178+ snapshot_options = snapshot_options ,
179+ sentience_api_key = sentience_api_key ,
180+ )
181+ # Store browser reference for snapshot() to use
182+ runtime ._legacy_browser = browser
183+ runtime ._legacy_page = page
184+ return runtime
185+
106186 def _ctx (self ) -> AssertContext :
107187 """
108188 Build assertion context from current state.
@@ -113,28 +193,57 @@ def _ctx(self) -> AssertContext:
113193 url = None
114194 if self .last_snapshot is not None :
115195 url = self .last_snapshot .url
116- elif self .page :
117- url = self .page . url
196+ elif self ._cached_url :
197+ url = self ._cached_url
118198
119199 return AssertContext (
120200 snapshot = self .last_snapshot ,
121201 url = url ,
122202 step_id = self .step_id ,
123203 )
124204
125- async def snapshot (self , ** kwargs ) -> Snapshot :
205+ async def get_url (self ) -> str :
206+ """
207+ Get current page URL.
208+
209+ Returns:
210+ Current page URL
211+ """
212+ url = await self .backend .get_url ()
213+ self ._cached_url = url
214+ return url
215+
216+ async def snapshot (self , ** kwargs : Any ) -> Snapshot :
126217 """
127218 Take a snapshot of the current page state.
128219
129220 This updates last_snapshot which is used as context for assertions.
130221
131222 Args:
132- **kwargs: Passed through to browser.snapshot()
223+ **kwargs: Override default snapshot options for this call.
224+ Common options:
225+ - limit: Maximum elements to return
226+ - goal: Task goal for ordinal support
227+ - screenshot: Include screenshot
228+ - show_overlay: Show visual overlay
133229
134230 Returns:
135231 Snapshot of current page state
136232 """
137- self .last_snapshot = await self .browser .snapshot (self .page , ** kwargs )
233+ # Check if using legacy browser (backward compat)
234+ if hasattr (self , "_legacy_browser" ) and hasattr (self , "_legacy_page" ):
235+ self .last_snapshot = await self ._legacy_browser .snapshot (self ._legacy_page , ** kwargs )
236+ return self .last_snapshot
237+
238+ # Use backend-agnostic snapshot
239+ from .backends .snapshot import snapshot as backend_snapshot
240+
241+ # Merge default options with call-specific kwargs
242+ options_dict = self ._snapshot_options .model_dump (exclude_none = True )
243+ options_dict .update (kwargs )
244+ options = SnapshotOptions (** options_dict )
245+
246+ self .last_snapshot = await backend_snapshot (self .backend , options = options )
138247 return self .last_snapshot
139248
140249 def begin_step (self , goal : str , step_index : int | None = None ) -> str :
0 commit comments