The live view and debugging
Watching a run over VNC, reading the step feed's entry types, following sandbox code in the code tab, and exporting a run as JSON for diagnosis.
Every session is watchable while it runs and inspectable after it ends. The dashboard at http://<your-host>:8420/ (HTTP Basic auth, user admin unless you changed DASHBOARD_USER) shows a live session list; each session page combines the browser view, the step feed and the export controls.
The browser view
The stream is a noVNC client attached to the actual X display the agent's Chromium is running on, so what you see is the browser itself, not a reconstruction. Parallel tab waves from read_pages are visible as tabs opening, being focused one by one, and closing. The session's liveUrl field carries the same view as a path on your instance, served through the dashboard's authentication, so you can link an operator straight to a running session.
The view starts when somebody looks. A session allocates only the virtual framebuffer it needs to run; the VNC server and the websocket bridge spawn on the first request to view it, from either the sessions grid or the session page. Runs therefore start faster, and an unwatched run does not stream pixels to nobody. The first frame after you open a view can take a moment longer than the ones after it.
When the agent runs a sandbox script, it opens a dedicated code tab on the same display: an IDE-style page into which the script's source streams token by token as the model writes it, with its status and the file name. The tab closes when the script finishes. This makes the run's code layer as observable as its clicking layer.
The step feed
Each feed row is one message from the run. The row's badge tells you which layer produced it:
| Type | Meaning |
|---|---|
planning | Run start: which model the session launched with |
browser_action | A completed step: the primary action, its key parameter, and the step duration |
result | A step whose action returned content, such as a page read or a store write summary |
browser_action_error | A step that failed; the row shows the first sentence and the expandable card carries the full error text |
event | Annotations that are not steps: goal reminders, coverage checks, read progress, reviewer verdicts |
completion | The final outcome line |
Those six are what a run itself produces. A seventh, user_message, records a follow-up task sent to a keep-alive session. GET /v3/sessions/{id}/messages applies no type filter at all, so match on all seven if you are filtering programmatically.
Two names people reach for do not exist. There is no error type; failures are browser_action_error. And done is not a type either: it is an action name carried in a row's data.action when the agent submits its result for review, and that row is typed result rather than browser_action, because any step whose action returns content is recorded as a result.
Steps expand to show the agent's three cards, see (what is on the page), plan (how it gets from here to the goal) and next (the next single move), plus the model's own chain-of-thought summary when the provider exposes one. That reasoning arrives as its own event row per step, with category reasoning.
Events carry a category that tells you which subsystem is speaking:
| Category | What it reports |
|---|---|
goal | The derived goal sentence at the start, then a reminder every ten steps |
reasoning | The provider's own chain-of-thought summary for the step, when the model exposes one |
memory | Clipboard writes, including the auto-captured startUrl |
read | read_pages progress, wave by wave: read_pages wave 2/3: 12 of 12 pages ok, 12 read inside their embedded panel (38s) |
code | Sandbox script starts, such as Running map_rows.py |
schema | Completeness gate activity: the bounce with its deficiency list, then Completeness check passed with the final coverage |
judge | Reviewer verdicts, change requests, and any dissent between the judge and the recorded outcome |
system | Host pressure warnings when the processor is stalling at launch, which degrade timing-sensitive embed reads |
Reading what is happening right now
Above the feed, one activity surface answers "what is the run doing at this instant?". A phase declares which kind it is, and the two kinds look different on purpose:
- A phase that thinks shimmers, and the model's reasoning streams underneath it as it is generated, rather than as a truncated tail of it. A finished thought collapses to how long it took, with a chevron to read it again.
- A phase that acts spins. Running actions, preparing the next step, reading pages, running code and solving a CAPTCHA all turn a spinner. Nothing wears both, so a shimmer means the model is reasoning rather than merely that something is happening.
Adaptive thinking can open a block and then say nothing for tens of seconds, so the label goes up on the block itself and the streaming card takes over the moment real text arrives. The elapsed time a finished thought reports is the same figure the durable feed row beneath it carries, so reopening a session later says what the live view said at the time.
On a keep-alive session, each finished turn's completion folds away under its own title as the next begins, newest on top and open, and a reload rebuilds that stack. Every turn's answer stays reachable rather than being overwritten by the next one.
Diagnosing a run from the feed
The feed usually shows the problem directly. Things worth scanning for:
find_linksmatched 0 links, or a frame filter matched 0 frames. The listing lives in an embedded panel that had not attached yet, or the selector was wrong. The tool's own error text says which, and lists the attached frame hosts to target instead.read_pagesreporting shell reads. Rows likeread the embedding shell, not this page's real contentmean the pages' content lives in a cross-origin embed; the retry inside the panel happens automatically and is logged, so check whether the recovery line follows.- A completeness bounce. The
schemaevent lists exactly which fields were empty and on how many items. If the run then finished cleanly, nothing was wrong; the gate did its job. If it looped intomark_absenton data you know is there, the pages that show it probably never rendered; look for read failures above. Stopped: Cost $X exceeded budget $Y. The cost cap fired, and the session carriesfailureKind: "budget_exceeded". Whatever the run had produced is kept; see cost control.- A failed session's
failureKind. Before reading the feed at all, check it. Aprovider_rate_limitorprovider_server_errorsays the run failed for a reason that has nothing to do with your task or the site, and is worth simply retrying. How OpenBrowse works lists the full set. - A
systempressure warning at launch. Failures in that run may be environmental (an overloaded host missing embed attach windows) rather than site changes. Re-run when the host is quiet before concluding anything. Step timed out and was cancelled before completing. One step exceeded 520 seconds; the run continues, but repeated occurrences usually mean the host is underpowered for the page.
Exporting a run
The session page exports JSON at three scopes, which are also plain authenticated GETs:
curl -u admin:$DASHBOARD_PASSWORD \
"http://<your-host>:8420/session/<session-id>/log?scope=full&download=true"| Scope | Contents |
|---|---|
output | Only the schema answer, as JSON |
steps | The session record and every step, with raw model thinking stripped |
full | Everything the feed shows, including reasoning text |
The full export is the right attachment for a bug report: it contains the task, every action with its parameters and duration, every error in full, and the reviewer's reasoning.
The v3 API exposes the same underlying feed programmatically at GET /v3/sessions/{session_id}/messages, paginated with after, before and limit, which is what you want for driving your own progress UI.
How OpenBrowse works
What actually happens between POST /v3/sessions and a finished result: the display slots, the step loop, the goal pre-flight, the answer store, and the reviewer.
Cost control
What a run costs, how the real-cost engine prices it from actual token usage, and how maxCostUsd and CLOUD_MAX_COST_FACTOR enforce a hard stop-loss.