rtrvr.ai logo
Retriever AI
Blog
Book Demo
Pricing
API Docs
For AI labs & RL environment teams

Web agent trajectories, captured as semantic trees

We have accumulated over 4 million web agent trajectories from real knowledge work in real browsers. Our agent does not drive from screenshots — every observation is a compact accessibility tree in which each node carries a stable id, and every action in the trajectory names one of those ids. That makes each (state, action) pair exactly reconstructable.

Below are three complete runs, published in full. Walk them step by step, read the planner's reasoning, and inspect the exact page state behind every decision.

Explore the runsGet the bundles

18

observations published

39,786

tree nodes in them

5,777

link targets

13

per-decision cycles

The observation space

The state is not a screenshot

A screenshot shows you the viewport. On one of the pages in the samples below — an Amazon search results page — the viewport is a few percent of the action space. The accessibility tree for that single page holds 8,699 nodes and 1,137 link targets, all of them addressable, all of them in the state.

ApproachObservationAction spaceOff-screen contentReconstructable?
Screenshot onlyRendered viewport pixelsPixel coordinates, inferred from the imageLost — everything below the fold is absent from the stateOnly if you keep the image; coordinates do not survive a layout change
Raw DOM / HTMLFull serialised markupCSS or XPath selectorsPresent, but buried in framework noise and hidden subtreesIn principle, but selectors break on class-name churn and the payload is enormous
Semantic accessibility treewhat we shipOne line per node: role, accessible name, stable integer id, plus a separate href recordThe integer ids themselves — every action in the trajectory names oneFully present. The whole page is in the state regardless of scroll positionExactly. Every (observation, action) pair joins by accTreeId and element id

Ids are the action space

Each node is one line: [role] accessible name [id=N]. Every element_id in the trajectory indexes one of those labels, so joining an action back to the node it touched is a lookup, not an inference.

Observations chain into a state graph

Each tree records the previous observation in the same run under provenance.parentTreeId. Nodes are observations, edges are the actions between them, and across many runs on one site those graphs merge.

Hrefs kept out of the tree text

Links live in a separate elementLinkRecord. The agent is shown an enriched view; we store the lean text so the observation stays byte-stable and the enrichment stays derivable.

The execution model

Two granularities in every run

The agent does not emit one model call per click. A planner reads the page once and writes a JavaScript plan script that runs in a sandbox, calling browser helpers directly. When a step is too dynamic to script blind, the script delegates to a sub-agent that does run a classical per-step loop. Both levels are recorded, and they want modelling separately.

Macro

Look once, then execute a program

One observation in, a whole script of actions out. In the Ashby run a single planner pass produces a nine-thousand-character program that then drives twelve browser calls with no further model round-trips.

  • plannerThought carries the full chain of reasoning for the pass
  • generatedNetworkCode is the program it wrote, verbatim
  • Every helper step it spawns is stored with its resolved runtime arguments

This is not a per-step MDP, and we do not pretend it is. Because the script runs without model round-trips, intra-script page transitions are genuinely unobserved. That is a property of the policy, stated plainly in every bundle's README.

Micro

One observation, one decision, one action

Inside an rtrvr.act delegation, agentSteps[] is a classical observe→decide→act loop. Each entry names exactly the observation that decision saw.

  • accTreeId pins the state; functions[].args.element_id pins the action
  • thought is the reasoning that produced it
  • A final entry with an observation and no action marks the terminal state

Failures are kept verbatim — for example Element with ID '128' not found in DOM — usually followed by the model's recovery on the next observation. Those error→recovery pairs are typically the highest-signal part of a run.

Reward signal · Per action

functions[].response

Every tool call carries the runtime's own response string — "Success", or the verbatim failure such as "Element with ID '128' not found in DOM".

Reward signal · Per step

status / error

Each stored step is independently labelled, so a step that errored is separable from the run that still succeeded around it.

Reward signal · Per task

status + code_plan_review.verdict + output

A terminal verification pass re-observes the page and certifies completion, giving a task-level label grounded in a fresh observation rather than a self-report.

The trajectory space

What people actually ask a browser agent to do

This is the measured mix of knowledge work running on the platform, not a taxonomy we invented on a whiteboard. Shares are taken over a recent one-week production window, deduplicated to distinct tasks and normalised across these eight categories.

Pull structured records off listings, dashboards, search results and directories, usually into a sheet. The broadest category by number of distinct users.

Typical horizon

10–100+ actions, often fanned across many tabs

What makes it hard

Pagination and lazy loading mean the observation is never complete. The agent has to decide when it has seen enough, not just what is on screen.

Shares describe the mix within this slice of knowledge work. They are not a claim about total platform volume, and the categories are not mutually exclusive at the margin — an extraction task that ends in a spreadsheet touches two of them.

Three complete runs

Walk a real trajectory

Nothing here is a mock-up. These are production runs, served as the same static JSON you get in the download. Pick a step to read the planner's reasoning and the program it wrote; pick a decision to load the exact page state that decision saw, with the element it acted on highlighted.

Raw workflow.json
Job applicationsjobs.ashbyhq.comdeepseek-v4-flash

The entire instruction

“apply to this job”

Three words of instruction become a filled, uploaded and submitted ATS application.

Why this run is worth training on

  • A single planner pass writes a nine-thousand-character program against one observation, then twelve browser calls execute with no model round-trips. The macro policy is a program, not a step.
  • Two file uploads, four free-text essay answers and a radio choice — all resolved to concrete element ids you can join back to the exact tree the planner read.
  • The run ends with a verification pass against a fresh observation, so the terminal reward is grounded in a post-submit page, not in the model asserting success.

Caveat: Zero failed actions. Clean runs are useful for behaviour cloning but carry no recovery signal — for that, see the Amazon run.

This bundle

Observations
7
Tree nodes
722
Link targets
61
Sub-agent decisions
5
Grounded actions
5
Get this bundle · 46 KB
Anatomy of a bundle

The schema, in full

Every bundle ships this same README. Four files, one join key, no proprietary format.

workflow.json        the trajectory
trees/<id>.json      one accessibility-tree observation per file, keyed by accTreeId
trees/INDEX.json     accTreeId -> url, tree size, link count
SUMMARY.json         machine-readable counts, including tree coverage and missingIds

workflow.json

userInputstring
The literal prompt that started the run.
statusstring
Terminal outcome of the whole task.
modelConfigstring
Planner model that produced the plan scripts.
multiSteps[]Step[]
The ordered execution trace.
outputstring[]
What the agent returned to the user, verbatim.
totalCreditsUsednumber
Billed cost of the run in our internal unit — not tokens.

multiSteps[] — one step

toolNamestring
code_plan, code_plan_review, rtrvr.act, rtrvr.askUser, or a browser helper.
toolArgsobject
Resolved runtime arguments — real element ids and literal typed text, not the selector the script was written with.
accTreeIdsstring[]
Observation join: which trees/<id>.json this step acted against.
plannerThoughtstring
Full planner reasoning on a code_plan pass.
generatedNetworkCodestring
The plan script the planner wrote for this pass.
agentSteps[]Decision[]
Per-decision observe→decide→act cycles inside an rtrvr.act delegation.
status / errorstring
Per-step outcome, with the failure text kept verbatim.
durationMsnumber
Wall-clock time for the step.

agentSteps[] — one decision

accTreeIdstring
Exactly the observation this decision saw.
thoughtstring
The model's reasoning before acting.
functions[]Call[]
Tool calls with args and the runtime's response string.
failstring?
Present only when one or more calls failed.

trees/<accTreeId>.json

accTreeIdstring
Join key referenced by steps and decisions.
urlstring
Page the observation was captured on.
treestring
The observation. One line per node: [role] accessible name [id=N].
elementNameRecordRecord<id,string>
Accessible name by element id.
elementLinkRecordRecord<id, string | InlineRef>
Link targets by element id, kept separate so the tree text stays byte-stable. A normal href is a bare URL string; a data:/blob:/inline-SVG target is an object { kind: "inline", mimeType, method, bytes?, omitted: true, srcUrl? } recording the descriptor without the payload — it has no url key.
provenanceobject
trajectoryId, surface, tabId, parentTreeId, turnOrdinal — chain parentTreeId to get a per-site state graph.

Reconstructing a step-level dataset

trees = {t["accTreeId"]: t for t in load_all("trees/*.json")}

for step in workflow["multiSteps"]:
    for decision in step.get("agentSteps", []):
        obs   = trees[decision["accTreeId"]]      # state
        acts  = decision.get("functions", [])     # action(s)
        why   = decision.get("thought")           # reasoning trace
        # the next decision's accTreeId is the resulting state

Stated plainly, because you will find it yourself: these are live-web runs, so the sites have changed since capture. The trees are the durable record; the URLs may no longer render the same page. Tree coverage is reported per bundle in SUMMARY.json, and where a planner-pass observation is missing it is listed by id rather than quietly dropped.

Take it offline

Get the bundles

Want a corpus cut to your spec?

We can export by task shape, site, horizon length, failure density, or surface — extension runs in a real logged-in browser, or cloud-browser runs we drive ourselves. Tell us what an environment or an eval needs to look like and we will cut it.

Book time with usarjun@rtrvr.ai
rtrvr.ai logo
Retriever AI

Retrieve, Research, Robotize the Web

By subscribing, you agree to receive marketing emails from Retriever AI. You can unsubscribe at any time.

Product

  • Browser Extension
  • Cloud
  • RoverNEW
  • API & MCP
  • CLI & SDK
  • Templates
  • WhatsApp

Use Cases

  • Vibe Scraping
  • Lead Enrichment
  • Agentic Form Filling
  • Web Monitoring
  • Social Media
  • Job Applications
  • Data Migration
  • AI Web Context
  • Agentic Checkout

Compare

  • vs Apify
  • vs Bardeen
  • vs Browserbase
  • vs Browser Use
  • vs Clay
  • vs Claude
  • vs Comet
  • vs Firecrawl

Resources

  • Documentation
  • Blog
  • Case Studies
  • Newsletters
  • Changelog
  • Integrations
  • Pricing
  • AppSumo Deal
  • Book Demo
  • Affiliate Program

Company

  • Team
  • Contact
  • GCP Partner
  • Privacy Policy
  • Terms of Service
  • Security Brief
support@rtrvr.ai

© 2026 Retriever AI. All rights reserved.

Made withfor the automation community