* feat(browser): record when scenario requests were observed and how long they took Request events carry observed_at, the wall-clock time the provider observed the request start, and finished or failed events carry the browser's elapsed_ms when it reports one. Both are optional, so earlier captures still parse, and scenario comparisons leave them out as a committed built-in rule. The real-Chrome network lane checks every request and finished response is timed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(environment): time browser scenario records so their state learns a write clock Scenario records take startedDateTime from when each request was observed and time from the browser's duration, as HAR entries are timed. A timed scenario of the book session learns the same clock and serves the same writes as its HAR; an untimed one keeps record order and learns no clock, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): document scenario request times and timed scenario records Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): start scenario records at the browser's own request start The provider observes a request up to tens of milliseconds after the browser starts it while it reads other responses, so a record starting then and ending after the browser's duration mixed two clocks. Finished and failed events now carry the browser's started_at, records prefer it and fall back to observed_at, and comparisons drop it too. Tests cover the fallback for a request that never ended or ended twice, a partly untimed capture keeping record order, comparing an untimed capture with a timed one, and the real-Chrome lane checks every finished request carries the browser's start and duration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): document the browser start time, its fallback lag and older readers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the scenario timing verification Gates, the real-Chrome lane, prompt-recorded and re-recorded published scenario captures built with and without their times, the stale published environments left unrepublished, and the review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(browser): compare timed completion events in the request-time comparison test The comparison test's captures had no finished or failed events, so dropping started_at and elapsed_ms from them went untested. The captures now end one request and fail the other, the test asserts both carry times, compares them shifted by seven seconds and against an untimed capture, and a control shows a changed failure on a timed completion is still reported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
10 KiB
Agent environments from a live application
Use this when the user asks for an agent environment (an AgentEnv bundle) of a
web application: a folder agent-env deploys, whose MCP tools answer from what
one recording of the application observed and serve the writes that recording
confirmed. The workflow records the application once with
capture_browser_scenario, checks what the environment would serve, records
again if a hint names a gap, then writes the bundle with
export_environment_bundle. For a HAR the user already has, skip to
export through inspect_web_network_capture, or use
rea environment-build <capture.har> <dir>.
Before recording
- Record only against an instance the user says is disposable or seeded for this purpose: every write step creates, edits or deletes its data. If the user has not said so, ask before any scenario that writes. REA does not start, seed or reset applications.
- Credentials come from environment variables the user names. Declare each as a
scenario secret,
{secret_id, environment_variable}; the variable must be set in the environment REA's MCP server started with. Never ask for a password in chat. Declared secrets are redacted as the capture records. - Map the application first with
inspect_web_page(andcapture_web_screenshotwhen layout matters): the login form's locators, and the API calls behind the pages that list and edit the entities the user asked about.
Record a session an environment can learn writes from
One capture_browser_scenario records the whole session. Start from this
shape, with the application's own locators, paths and headers:
{
"browser": { "mode": "launch", "executable_path": "/usr/bin/chromium" },
"start_url": { "url": "http://127.0.0.1:3000/login" },
"secrets": [
{ "secret_id": "admin_password", "environment_variable": "APP_PASSWORD" }
],
"capture": {
"network": {
"request_body": true,
"response_body": true,
"header_values": true
}
},
"actions": [
{
"step_id": "user",
"action": "fill",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "input[name=username]" },
"value": { "source": "literal", "value": "admin" }
},
{
"step_id": "password",
"action": "fill",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "input[name=password]" },
"value": { "source": "secret", "secret_id": "admin_password" }
},
{
"step_id": "sign-in",
"action": "press",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "input[name=password]" },
"key": "Enter"
},
{
"step_id": "signed-in",
"action": "wait_for",
"timeout_ms": 15000,
"locator": { "kind": "css", "selector": "nav a[href='/settings']" },
"state": "visible"
},
{
"step_id": "folders-1",
"action": "request",
"timeout_ms": 15000,
"method": "GET",
"path": "/api/folders",
"expected_status": [200]
},
{
"step_id": "create-folder-1",
"action": "request",
"timeout_ms": 15000,
"method": "POST",
"path": "/api/folders",
"json": { "title": "Scratch folder 1" },
"headers": [
{
"name": "X-CSRFToken",
"value": { "source": "cookie", "name": "csrftoken" }
}
],
"expected_status": [200, 201]
}
]
}
Give every step a timeout_ms. A step without one waits until the scenario
is cancelled, so a locator that never matches hangs the capture, and the
client with it. Match wait_for locators to exactly one element.
- Sign in through the UI:
fillthe user and password fields (the password as a secret reference), submit, thenwait_foran element only a signed-in user sees. - Read with
requeststeps: each collection the environment needs, twice before any write, so fields that change on their own show up; one item of each. - Use
requeststeps only when the API lets the caller choose each scratch object's ID or a prior read already established it. Request paths are fixed strings; no action substitutes a value from an earlier response. For server-assigned IDs, create through the UI and open the new object's detail view to record its read, edit and delete through UI actions. If that journey cannot be scripted, leave the write unserved and report the limitation; never guess an ID or create objects with no supported cleanup path. Record two numbered scratch objects per write type, with varied free text. After each write, and before the next one, read the object back by id and then its list: create 1, read 1, list, create 2, read 2, list; never several writes before their reads. - Leave more than a second between a create and its first edit
(
wait_for_timeoutwithduration_ms: 1100). - Update both objects and read each back; delete both and read each back (expect 404) and the list.
About request steps:
pathis on the start URL's origin; the page must be on that origin.- Header values are literals, declared secrets, a cookie the browser holds
(
{source: "cookie", name}), or a local storage entry ({source: "local_storage", key, json_pointer}), with an optionalprefixsuch as"Bearer ". Copy the headers the application's own requests send (a CSRF header, a bearer token). Values read from the page are redacted from records made after they are read, not from the login response that returned them, and values shorter than 8 characters are not redacted at all. - Reading a page value makes later bodies costlier to keep: after a CSRF
token, bodies over about 47 KiB are kept as
unavailable. When the API returns larger responses, raisecapture.limits(max_network_body_allocation_bytes,max_total_event_allocation_bytes). - Request steps follow no redirects: a redirect fails the step, and
expected_statuscannot name a 3xx status. Use the path the redirect names, such as the one with the trailing slash. - Set
expected_statuson every step, so a refused call fails its step and the later steps report not having run, instead of recording read-backs of nothing.
Keep it to about 60 to 160 requests. A scenario with no request step is
recorded exactly as before.
The capture returns its whole Evidence, every recorded body included, which is
often more than a client shows inline. Before checking coverage, confirm that
every entry of normalized_result.steps has status: "completed"; if the
client saved the result to a file, search it for "status":"failed" and read
that step's error. A failed sign-in or write cancels every step after it.
Use the result's evidence_id for coverage and export; do not paste the
Evidence back into another tool.
Check coverage
inspect_analysis_view with source: {kind: "retained-evidence", evidence_id} (the capture's id) and view: {kind: "environment-coverage", template_rules: ["after-uid-literal", "opaque-token", "sibling-variance", "prefix-variance"]}. It reports read tools
answered from the state or replayed (reads.recorded_reasons), the served write
tools, and each withheld write with its reason and hint. It does not apply
the serve-time catalog and state size checks; the export reports those.
Re-record a withheld write at most twice, and only for a recording gap. Stop at once when the reason is application behaviour, and report the write as withheld with its hint:
| Reason | What to do |
|---|---|
no-state-target |
Re-record: read the collection or item the write changes. |
no-observation |
Re-record: read the entity back by id after the write; read it first for an update or delete. |
unconfirmed |
Re-record: read the entity and its list after the write. |
incomplete-entities |
Re-record: read the entity through every view that lists it. |
unexplained-change |
Re-record once with a second, different call of the same write. |
no-call |
Re-record once with a write that succeeds. |
side-effect |
Stop: the application changes other entities. |
contradicted |
Stop: a later read disagrees with the modelled effect. |
body-unusable |
Stop: the body is not a JSON object. |
Use new scratch names on each attempt; the first attempt's objects may still exist. New browser scenario captures include request times, so a fully timed capture can learn a write clock and serve updates that stamp modification times. Older captures or ones missing a request start keep record order and learn no clock. When coverage reports that limitation, record again with the current version before concluding that the timestamp change cannot be served.
Export and hand off
export_environment_bundle with source: {kind: "retained-evidence", evidence_id} (or {kind: "evidence-file", path}), an absolute
output_directory that does not exist yet and whose parent does, and a name
(required when the application runs on localhost or an IP address). It returns
the coverage again, tool_count, the written files and warnings; dry_run: true reports without writing. The bundle installs rea-agents of this REA's
version from the registry; when the user runs an unreleased REA build, pass the
package_tarball they give you (a packed rea-agents tarball) instead.
Tell the user how many tools answer from the state, which writes are served and
which are withheld and why, and the run command (agent-env run <folder>). Say
that the bundle ships the recorded responses and the capture's local paths
(for a browser scenario, the browser executable's path), and
list any credential-named-members warning pointers for review. Never publish
or share the bundle yourself.
See agent environments for what the state, replay and the bundle establish, and evidence workflows for the manifest and state views.