Skip to content

Commit a8b5b11

Browse files
committed
feat: add Browser Use run hooks
1 parent fc9fd16 commit a8b5b11

39 files changed

Lines changed: 518 additions & 281 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ to keep the first contribution small and reviewable.
99

1010
## Unreleased
1111

12+
## 0.1.15 - 2026-05-10
13+
14+
- Added `create_run_hooks` for Browser Use apps that pass `on_step_start` and
15+
`on_step_end` directly to `agent.run(...)`.
1216
- Improved Skyvern metadata extraction so nested task/workflow run IDs and
1317
statuses are promoted into step metadata.
1418

LAUNCH.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# BrowserTrace Launch Control Room
22

33
Canonical repo: https://github.com/aaronlab/browsertrace
4-
Current release: `v0.1.14`
4+
Current release: `v0.1.15`
55
Owner account: `aaronlab`
66

77
## Current State
@@ -14,10 +14,10 @@ Owner account: `aaronlab`
1414
- If posting before PyPI is live, use the tested no-install `uvx` fallback:
1515

1616
```bash
17-
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.14" browsertrace doctor
18-
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.14" browsertrace demo
19-
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.14" browsertrace list
20-
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.14" browsertrace
17+
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.15" browsertrace doctor
18+
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.15" browsertrace demo
19+
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.15" browsertrace list
20+
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.15" browsertrace
2121
```
2222

2323
- Current audited star count should be checked before every public push:
@@ -30,8 +30,8 @@ gh repo view aaronlab/browsertrace --json stargazerCount,url,homepageUrl,owner
3030
- README animation: `docs/demo.gif`
3131
- Static poster: `docs/demo-poster.png`
3232
- Live zero-install demo: https://aaronlab.github.io/browsertrace/
33-
- Downloadable demo trace: `browsertrace-demo.html` attached to release `v0.1.14`
34-
- Public-safe demo trace: `browsertrace-demo-public.html` attached to release `v0.1.14`
33+
- Downloadable demo trace: `browsertrace-demo.html` attached to release `v0.1.15`
34+
- Public-safe demo trace: `browsertrace-demo-public.html` attached to release `v0.1.15`
3535
- Launch discussion: https://github.com/aaronlab/browsertrace/discussions/6
3636

3737
## Day 0 Asset Checklist

README.md

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ Use this before PyPI publishing is enabled. The quickest path is `uvx` from the
4242
GitHub release tag:
4343

4444
```bash
45-
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.14" browsertrace doctor
46-
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.14" browsertrace demo
47-
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.14" browsertrace list
48-
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.14" browsertrace
45+
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.15" browsertrace doctor
46+
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.15" browsertrace demo
47+
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.15" browsertrace list
48+
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.15" browsertrace
4949
```
5050

5151
If you see `uvx: command not found`, install `uv` from the
@@ -73,10 +73,10 @@ Requires Python 3.11+.
7373

7474
```bash
7575
# SDK only
76-
pip install "browsertrace @ git+https://github.com/aaronlab/browsertrace@v0.1.14"
76+
pip install "browsertrace @ git+https://github.com/aaronlab/browsertrace@v0.1.15"
7777

7878
# SDK + local web UI
79-
pip install "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.14"
79+
pip install "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.15"
8080
browsertrace doctor
8181
browsertrace demo
8282
browsertrace
@@ -109,9 +109,9 @@ For compact AI/coding-agent troubleshooting context, use
109109
project links and prompts.
110110

111111
- The first-run troubleshooting checklist walks through `browsertrace doctor`, `browsertrace demo`, `browsertrace list`, `browsertrace show`, and public-safe export; see the [checklist](examples/#first-run-troubleshooting-checklist).
112-
- The live static demo and public-safe demo export let you inspect a trace before installing anything; open the [live static demo](https://aaronlab.github.io/browsertrace/) or download [`browsertrace-demo-public.html`](https://github.com/aaronlab/browsertrace/releases/download/v0.1.14/browsertrace-demo-public.html).
112+
- The live static demo and public-safe demo export let you inspect a trace before installing anything; open the [live static demo](https://aaronlab.github.io/browsertrace/) or download [`browsertrace-demo-public.html`](https://github.com/aaronlab/browsertrace/releases/download/v0.1.15/browsertrace-demo-public.html).
113113
- The command cheat sheet summarizes `browsertrace doctor`, `browsertrace demo`, `browsertrace list`, `browsertrace show`, and public-safe export commands; see the [cheat sheet](examples/#browsertrace-command-cheat-sheet).
114-
- The v0.1.14 release notes summarize what changed in the pinned GitHub tag; read the [v0.1.14 release notes](https://github.com/aaronlab/browsertrace/releases/tag/v0.1.14).
114+
- The v0.1.15 release notes summarize what changed in the pinned GitHub tag; read the [v0.1.15 release notes](https://github.com/aaronlab/browsertrace/releases/tag/v0.1.15).
115115
- The PyPI tracking issue is the source for publishing status while install commands stay pinned to the GitHub tag; follow the [PyPI tracking issue](https://github.com/aaronlab/browsertrace/issues/5).
116116
- `uvx` is the no-install trial path, and pinned GitHub-tag `pip install` is the persistent install path.
117117
- `[ui]` is needed for the local web UI, while SDK-only install is enough for trace capture integrations.
@@ -147,13 +147,13 @@ If install or demo startup fails, use the
147147
[first-run troubleshooting checklist](examples/#first-run-troubleshooting-checklist).
148148

149149
For changes in this pinned tag, read the
150-
[v0.1.14 release notes](https://github.com/aaronlab/browsertrace/releases/tag/v0.1.14).
150+
[v0.1.15 release notes](https://github.com/aaronlab/browsertrace/releases/tag/v0.1.15).
151151

152152
Want to inspect an exported trace before installing anything? Open the
153153
[live static demo](https://aaronlab.github.io/browsertrace/) or download
154-
[`browsertrace-demo.html`](https://github.com/aaronlab/browsertrace/releases/download/v0.1.14/browsertrace-demo.html)
154+
[`browsertrace-demo.html`](https://github.com/aaronlab/browsertrace/releases/download/v0.1.15/browsertrace-demo.html)
155155
or the public-safe
156-
[`browsertrace-demo-public.html`](https://github.com/aaronlab/browsertrace/releases/download/v0.1.14/browsertrace-demo-public.html)
156+
[`browsertrace-demo-public.html`](https://github.com/aaronlab/browsertrace/releases/download/v0.1.15/browsertrace-demo-public.html)
157157
from the latest release.
158158

159159
For a walkthrough, read
@@ -302,7 +302,7 @@ is recorded against the last step.
302302
```python
303303
from browser_use import Agent
304304
from browsertrace import Tracer
305-
from browsertrace.integrations.browser_use import attach_tracer
305+
from browsertrace.integrations.browser_use import attach_tracer, create_run_hooks
306306

307307
tracer = Tracer()
308308
agent = Agent(task="...", llm=ChatOpenAI(model="gpt-4o"))
@@ -314,9 +314,18 @@ with attach_tracer(agent, tracer, name="my run"):
314314
The adapter records the step URL, screenshot when exposed by Browser Use,
315315
action summary, model thought/actions, and compact browser-state context such
316316
as step count, title, tabs, and whether a screenshot was captured.
317+
For Browser Use apps that pass lifecycle hooks directly to `agent.run(...)`,
318+
use the run-hook helper instead:
319+
320+
```python
321+
hooks = create_run_hooks(tracer, name="my run")
322+
with hooks:
323+
await agent.run(on_step_start=hooks.on_step_start, on_step_end=hooks.on_step_end)
324+
```
325+
317326
For Browser Use callback compatibility, see
318327
[Debug Browser Use failures with BrowserTrace](https://aaronlab.github.io/browsertrace/browser-use-debugging.html),
319-
including `register_new_step_callback` notes.
328+
including `register_new_step_callback` and `create_run_hooks` notes.
320329

321330
### Stagehand integration
322331

@@ -429,7 +438,7 @@ For AI summaries before PyPI publishing is enabled, install the `ai` extra from
429438
the release tag you are using:
430439

431440
```bash
432-
pip install "browsertrace[ui,ai] @ git+https://github.com/aaronlab/browsertrace@v0.1.14"
441+
pip install "browsertrace[ui,ai] @ git+https://github.com/aaronlab/browsertrace@v0.1.15"
433442
```
434443

435444
```bash

ROADMAP.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ LLM, and computer-use runs easier to inspect, export, and discuss.
66

77
## Current Release
88

9-
`v0.1.14` is the current launch release.
9+
`v0.1.15` is the current launch release.
1010

1111
Shipped:
1212

@@ -43,9 +43,9 @@ Completed launch prep:
4343

4444
- [#13 GitHub profile README](https://github.com/aaronlab/browsertrace/issues/13)
4545
now points the `aaronlab` profile at BrowserTrace.
46-
- GitHub Release `v0.1.14` includes the wheel, sdist, full demo export,
46+
- GitHub Release `v0.1.15` includes the wheel, sdist, full demo export,
4747
public-safe demo export, demo video, poster, and GIF.
48-
- `v0.1.14` also includes the `browsertrace doctor` onboarding fix for
48+
- `v0.1.15` also includes the `browsertrace doctor` onboarding fix for
4949
pre-PyPI UI dependency guidance.
5050
- IndexNow submission is prepared and submitted for the main GitHub Pages
5151
launch URLs.

browsertrace/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
from .tracer import Run, Tracer, trace
44

5-
__version__ = "0.1.14"
5+
__version__ = "0.1.15"
66
__all__ = ["Tracer", "Run", "trace"]

browsertrace/integrations/browser_use.py

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,34 @@
33
Usage:
44
from browser_use import Agent
55
from browsertrace import Tracer
6-
from browsertrace.integrations.browser_use import attach_tracer
6+
from browsertrace.integrations.browser_use import attach_tracer, create_run_hooks
77
88
tracer = Tracer()
99
agent = Agent(task="...", llm=ChatOpenAI(model="gpt-4o"))
1010
run = attach_tracer(agent, tracer, name="my browser-use run")
1111
history = await agent.run()
1212
run.close() # or use the returned object as a context manager
1313
14+
# For Browser Use apps that pass run hooks directly:
15+
hooks = create_run_hooks(tracer, name="my browser-use run")
16+
with hooks:
17+
history = await agent.run(
18+
on_step_start=hooks.on_step_start,
19+
on_step_end=hooks.on_step_end,
20+
)
21+
1422
The adapter hooks `Agent.register_new_step_callback` and saves a step in the
1523
trace for each agent step, including the page URL, action(s) the model decided
16-
on, the model's stated thought, and a screenshot of the page state.
24+
on, the model's stated thought, and a screenshot of the page state. The
25+
run-hook helper covers Browser Use versions that expose `on_step_start` and
26+
`on_step_end` through `agent.run(...)`.
1727
"""
1828

1929
from __future__ import annotations
2030

2131
import base64
2232
import contextlib
33+
import inspect
2334
from typing import Any, Optional
2435

2536
from ..tracer import Run, Tracer
@@ -52,6 +63,70 @@ def __exit__(self, exc_type, exc, tb) -> None:
5263
self.close(error=f"{exc_type.__name__}: {exc}" if exc_type else None)
5364

5465

66+
class BrowserUseRunHooks(BrowserUseRun):
67+
"""Run-hook callbacks for Browser Use versions that pass hooks to run()."""
68+
69+
def __init__(self, tracer: Tracer, run: Run):
70+
super().__init__(tracer, run)
71+
self._started = False
72+
self._latest_start_context: Optional[dict] = None
73+
74+
async def on_step_start(self, agent: Any) -> None:
75+
self._ensure_started()
76+
state = await _browser_state_from_agent(agent)
77+
self._latest_start_context = _serialize_agent_context(
78+
agent,
79+
browser_state=state,
80+
has_screenshot=_extract_screenshot(state) is not None,
81+
)
82+
83+
async def on_step_end(self, agent: Any) -> None:
84+
self._ensure_started()
85+
state = await _browser_state_from_agent(agent)
86+
screenshot = _extract_screenshot(state)
87+
model_input = self._latest_start_context or _serialize_agent_context(
88+
agent,
89+
browser_state=state,
90+
has_screenshot=screenshot is not None,
91+
)
92+
history = _serialize_history(agent)
93+
self.run.step(
94+
action=_format_action_items(_normalize_actions(history.get("actions")))
95+
or "(no action)",
96+
url=_safe_attr(state, "url", default=""),
97+
screenshot=screenshot,
98+
model_input=model_input,
99+
model_output=history,
100+
hook="browser_use_run_hooks",
101+
)
102+
self._latest_start_context = None
103+
104+
def close(self, error: Optional[str] = None) -> None:
105+
self._ensure_started()
106+
super().close(error=error)
107+
108+
def _ensure_started(self) -> None:
109+
if self._started:
110+
return
111+
self.run._start()
112+
self._started = True
113+
114+
115+
def create_run_hooks(
116+
tracer: Tracer,
117+
name: str = "browser-use run",
118+
) -> BrowserUseRunHooks:
119+
"""Create callbacks for `agent.run(on_step_start=..., on_step_end=...)`.
120+
121+
This covers Browser Use apps that expose run-time lifecycle hooks instead
122+
of an attachable agent callback. Keep the returned object open for the
123+
duration of `agent.run(...)`, then call `.close()` or use it as a context
124+
manager.
125+
"""
126+
run = Run(tracer, run_id=__import__("uuid").uuid4().hex, name=name)
127+
return BrowserUseRunHooks(tracer, run)
128+
129+
55130
def attach_tracer(
56131
agent: Any,
57132
tracer: Tracer,
@@ -135,6 +210,59 @@ def _serialize_browser_state(
135210
return {"step_count": step_count, "browser_state": browser_state}
136211

137212

213+
async def _browser_state_from_agent(agent: Any) -> Any:
214+
browser_session = _safe_attr(agent, "browser_session", default=None)
215+
getter = _safe_attr(browser_session, "get_browser_state_summary", default=None)
216+
if not callable(getter):
217+
return None
218+
with contextlib.suppress(Exception):
219+
state = getter()
220+
if inspect.isawaitable(state):
221+
state = await state
222+
return state
223+
return None
224+
225+
226+
def _serialize_agent_context(
227+
agent: Any,
228+
*,
229+
browser_state: Any,
230+
has_screenshot: bool,
231+
) -> dict:
232+
context = {
233+
"task": _safe_attr(agent, "task", default=None),
234+
"browser_state": {"has_screenshot": has_screenshot},
235+
}
236+
for name in ("url", "title", "tabs"):
237+
value = _safe_attr(browser_state, name, default=None)
238+
if value is not None:
239+
context["browser_state"][name] = _json_safe(value)
240+
return context
241+
242+
243+
def _serialize_history(agent: Any) -> dict:
244+
history = _safe_attr(agent, "history", default=None)
245+
return {
246+
"thought": _latest_history_value(history, "model_thoughts"),
247+
"output": _latest_history_value(history, "model_outputs"),
248+
"actions": _latest_history_value(history, "model_actions") or [],
249+
"extracted_content": _latest_history_value(history, "extracted_content"),
250+
"url": _latest_history_value(history, "urls"),
251+
}
252+
253+
254+
def _latest_history_value(history: Any, method_name: str) -> Any:
255+
method = _safe_attr(history, method_name, default=None)
256+
if not callable(method):
257+
return None
258+
with contextlib.suppress(Exception):
259+
values = method()
260+
if isinstance(values, (list, tuple)) and values:
261+
return _json_safe(values[-1])
262+
return _json_safe(values)
263+
return None
264+
265+
138266
def _json_safe(value: Any) -> Any:
139267
if value is None or isinstance(value, (str, int, float, bool)):
140268
return value
@@ -172,6 +300,10 @@ def _extract_screenshot(state: Any) -> Optional[bytes]:
172300

173301
def _format_actions(output: Any) -> str:
174302
actions = _serialize_actions(output)
303+
return _format_action_items(actions)
304+
305+
306+
def _format_action_items(actions: list) -> str:
175307
if not actions:
176308
return ""
177309
parts = []
@@ -188,6 +320,14 @@ def _format_actions(output: Any) -> str:
188320
return " | ".join(parts)
189321

190322

323+
def _normalize_actions(actions: Any) -> list:
324+
if actions is None:
325+
return []
326+
if isinstance(actions, list):
327+
return actions
328+
return [actions]
329+
330+
191331
def _serialize_actions(output: Any) -> list:
192332
actions = _safe_attr(output, "action", default=None)
193333
if actions is None:

docs/browser-use-debugging.html

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,9 @@ <h2>Why this exists</h2>
261261
<h2>Try it before wiring Browser Use</h2>
262262
<div class="command-block">
263263
<button class="copy-button" type="button" data-copy-target="quickstart-command">Copy</button>
264-
<pre><code id="quickstart-command">uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.14" browsertrace doctor
265-
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.14" browsertrace demo
266-
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.14" browsertrace</code></pre>
264+
<pre><code id="quickstart-command">uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.15" browsertrace doctor
265+
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.15" browsertrace demo
266+
uvx --from "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.15" browsertrace</code></pre>
267267
</div>
268268
<p>Open <code>http://127.0.0.1:3000</code>, then inspect <code>demo: checkout agent fails on disabled button</code>. From a source checkout, <code>python examples/browser_use_callback_demo.py</code> records Browser Use-shaped callback steps without installing Browser Use.</p>
269269
</section>
@@ -286,7 +286,16 @@ <h2>Attach it to a Browser Use agent</h2>
286286
<section>
287287
<h2>Callback compatibility</h2>
288288
<p><code>attach_tracer</code> supports Browser Use agents that expose <code>register_new_step_callback</code>, plus older or forked agents with <code>on_step_start</code>, <code>on_step</code>, or <code>_new_step_callback</code> attributes.</p>
289-
<p>Current Browser Use examples may also pass <code>on_step_start</code> or <code>on_step_end</code> directly to <code>agent.run(...)</code>. If your app is run-hook-only and does not expose an attachable agent callback, keep using <code>run.snapshot(...)</code> manually for now and comment on issue #11 with the Browser Use version and hook shape you need.</p>
289+
<p>Current Browser Use examples may also pass <code>on_step_start</code> or <code>on_step_end</code> directly to <code>agent.run(...)</code>. For that run-hook-only path, use <code>create_run_hooks</code>:</p>
290+
<pre><code>from browsertrace import Tracer
291+
from browsertrace.integrations.browser_use import create_run_hooks
292+
293+
tracer = Tracer()
294+
hooks = create_run_hooks(tracer, name="browser-use checkout run")
295+
296+
with hooks:
297+
await agent.run(on_step_start=hooks.on_step_start, on_step_end=hooks.on_step_end)</code></pre>
298+
<p>The run-hook helper reads Browser Use history and browser-session summaries when they are available, then records the latest thought, action, extracted content, URL, title, tabs, and screenshot flag into the same local timeline. If your Browser Use version exposes a different hook shape, comment on issue #11 with the version and callback surface.</p>
290299
</section>
291300

292301
<section>

0 commit comments

Comments
 (0)