Export an authenticated capture and environment manifest as a pinned agentenv bundle with provenance, a replay verifier and smoke task.
Followups preserve immutable input ownership, bounded discovery, caller paths and numeric values; bind bundle writes and package copying to retained filesystem owners; verify POSIX modes; retain cleanup authority; and verify framework/npm executable selection. Default MCP remains stdio.
Validation: pinned Linux toolchain, typecheck/build/lint/format/docs/knip, 452 focused tests, ten product-catalog cases, real GitHub response replay in the generated non-root Docker image, and the real pinned agentenv framework smoke workflow. Platform and resource-measurement limits are recorded in the feature verification document.
Co-authored-by: gensokyo-dev <gensokyo-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Export's headroom check reserved one native parse per input, while the
raw parse, its authenticated Evidence copy and the schema copy of its
result are live together while the pair loads; it now reserves all
three, so a file near the heap limit is refused as a resource
constraint rather than exhausting the heap.
A bundle name that Windows reserves as a device name (con, prn, aux,
nul, com1-com9, lpt1-lpt9) is refused while planning, before any file
is written, since the env folder could not be created there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The serve change now loads environmentSource lazily so CLI startup never
imports it; the export command does the same, and the unused static
import is dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Serve retained capture Evidence as a bounded MCP replay environment with discovery.
Preserve default stdio, caller-selected facts and complete source Evidence.
Fix numeric fidelity, argument identity, startup cancellation and bounded discovery,
query/path matching and output metadata. Verify real recorded API data and public
CLI/MCP workflows; preserve explicit gaps for Windows and third-party hosts.
Co-authored-by: Edgar Arakelyan <edgar.arakelyan@scale.com>
Co-authored-by: gensokyo-dev <gensokyo-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A captured query name longer than 64 characters is advertised under a
short, collision-safe `query_<n>` alias, with the captured name sampled
in its description; replay maps the alias back, and the manifest keeps
the full name.
Replies cut `recorded_path` and `recorded_query` to 1 KiB and set
`recorded_line_truncated`, so a megabyte-scale recorded URL is not
echoed into every reply; selection still uses the full recorded line.
An explicit empty query (`/api?`) keeps its `?` in `recorded_query`. The
shared WHATWG URL parser reports it as "", like no query, so the replay
exchange reads the delimiter from the recorded URL instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replay parses a JSON body only when every integer survives exactly; a
body with an integer outside JavaScript's safe range, such as an id of
9007199254740993, is returned as its recorded text instead of with
other digits.
A SIGINT or SIGTERM that arrives while environment-serve reads and
authenticates its files now ends startup with a cancelled result before
the listener binds, rather than binding and stopping at once.
A tool description cuts a captured path longer than 1 KiB at a
character boundary and points to the manifest Evidence, matching the
list sample budget; the manifest keeps the full path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sample captured observations, request-body keys and record ordinals before joining them. Preserve complete manifest facts and replay selection, and show omission counts with their Evidence source. Share the capture trust notice across standalone cards, tool discovery and MCP initialization.
Add an explicit Streamable HTTP entry for environment/gateway hosts while keeping stdio registrations and default behavior. Preserve the shared provider/session owner across DELETE and replacement; fence and drain tool/completion work before handover or teardown.
Bound HTTP input and plain-response admission, cancel refused bodies, join whole exchanges and asynchronous response cleanup before provider hooks, gate startup readiness, and reject queued HTTP/1.1 pipelining while retaining sequential keep-alive.
Validation: 8 static/build tasks, 83 adapter tests and registry metadata pass. A real installed gh binary produces seven equivalent CLI/stdio/HTTP reports with DELETE/replacement and clean signal shutdown. Broader local native-provider/ownership failures reproduce on clean main and are documented with limitations in the PR.
Co-authored-by: Edgar Arakelyan <edgar.arakelyan@scale.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
The bundle's manifest.json and evidence.json are copies of captured
content, which can include values an application returned such as
session tokens. They are now written with mode 0600 regardless of the
caller's umask. The Dockerfile already copies them with --chown for the
runtime user, so the served environment can still read them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replay ranks a record whose path segment equals the argument as written
above one that matches only after percent-decoding, so two recorded
spellings that decode alike no longer resolve by manifest order.
The /agentenv data plane answers -32600 for an id that is not a string,
number or null and for params that are not an object or array, and
refuses a batch of more than 100 messages before building any reply.
The environment MCP server now extends RequestTrackingMcpServer, which
the HTTP owner requires since the streamable-HTTP change it builds on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Classify loopback from the actual bound IP only, then add the configured name to the Host/Origin allowlist. Remove DNS-name classification and the default/fallback that substituted configuration for binding evidence.
Validate the TCP address once before URL/allowlist consumers, preserve IPv4 and IPv6 alias behavior, and add negative controls for localhost names bound to non-loopback IPv4 and IPv6 addresses.
Record the gates and the real-browser runs: a loopback fixture and five
applications captured with capture-browser-scenario, projected, exported
and smoke-tested, with the limits observed along the way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The environment-manifest view, environment-serve and environment-export
accepted only inspect_web_network_capture HAR Evidence, so an environment
needed an external capture script and HAR scrubbing step. They now also
accept capture_browser_scenario Evidence: a session REA records in a
browser it launches, with credential headers and declared secrets
redacted.
Each network transaction becomes one HAR-shaped record in request order,
so the manifest accumulator, the replay and the bundle work unchanged. A
redacted URL is never templated, an unattributable transaction is left
unrepresented, and a scenario that selected neither response bodies nor
header values is refused because tools are chosen by response media
type. The bundle README names the capture kind and, for a scenario, the
scenario definition's SHA-256.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A transport wrapper marks every tools/call result as target content,
including the argument errors the SDK returns before a replay runs.
- The server's instructions add that tool names, descriptions, parameter
names and observed values come from the capture too, so the catalog is
treated as data like the replies.
- Strict UTF-8 decoding keeps a leading byte-order mark instead of
dropping it from the recorded text.
- A large retained base64 body is decoded only as far as the 256 KiB a
reply can carry, and the replay reports it truncated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The replay MCP server serves names, descriptions and replies taken from a
captured application but advertised no instructions, so clients that
rely on server instructions had no warning not to follow target text. It
now carries the same untrusted-content instructions as REA's own server,
moved next to the target-content result marker so both import it.
Retained HAR base64 bodies were decoded as UTF-8 even when the bytes were
not UTF-8, inventing replacement characters. They are now decoded
strictly; other bytes are returned exactly as base64 with
body_encoding: "base64".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Path parameters, query parameters and the write body share one argument
object, so a query named like a path parameter or 'body' could not be
supplied, and query names such as 'constructor' were dropped as already
taken. One argument map, used by both the schema and the replay scorer,
now renames a colliding query to query_<name> and a colliding body to
request_body, and is built without inherited keys. Replay results gain
body_recorded, so a body the capture did not keep is not passed off as a
recorded null, and truncation encodes only the 256 KiB prefix instead of
the whole body.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hosted runners share that registry's quota, and the framework builds its
infra images from it on every fresh runner; a 429 from it earns one retry
after 90 seconds, recorded in the receipt, while any other failure ends the
lane at once.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The pattern was a `const` below the top-level try block, so the leak filter
reached it in the temporal dead zone the first time a run actually created
the helper; locally no container was new, so the filter never ran. Reproduced
the fresh-host path by removing the local `agentenv-registry` before a run.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add rea environment-serve <manifest.json> --evidence <capture.json>
[--host] [--port]: it loads an environment-manifest view and the capture
Evidence it was projected from, refuses a manifest whose parent evidence id
does not match the capture, builds one MCP tool per manifest tool and serves
them over streamable HTTP beside an environment card at
/.well-known/agent-env.json and a data plane at /agentenv that refuses every
method. Each tool call replays the retained record whose path and query
values best match the arguments, cites the record ordinal and evidence id,
marks replayed content as untrusted target content, and truncates bodies
over 256 KiB with a flag. Nothing executes the captured application.
Refs morluto/rea#4933
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The first hosted run of the real-agentenv lane failed only on the cleanup
check: agent-env's local image store starts a registry:2 helper
(`agentenv-registry[-<port>]`) on first use and keeps it running across runs,
so a fresh runner ends the run with one more container than it started with.
The verifier now lists containers with their names, reports that helper under
`image_store_helpers` and asserts that nothing else was added.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add rea environment-export <manifest.json> --evidence <capture.json> <dir>
[--package-tarball <path>] [--name <override>]: it reads and authenticates the
manifest and capture pair once and writes an agent environment bundle folder
(README with provenance and a rights note, envs/<name> with a Dockerfile that
pins this exact rea version or installs a given tarball, the manifest and the
Evidence as byte-identical copies, a Python verifier that checks replay
through the MCP endpoint, and a smoke task) that the agentenv framework runs
with agent-env run. Refuses a non-empty directory and a mismatched pair
before writing. Adds the real-agentenv hosted lane, which packs the checkout,
exports a bundle from the source-owned capture fixture and runs it on the
runner's Docker with a pinned agentenv-framework, wired into the CI planner
and its guard tests.
Refs morluto/rea#4933
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(analysis-view): project an environment manifest from web network captures
Add the environment-manifest view kind to inspect_analysis_view for retained
HAR Evidence: the primary origin's recorded request lines become a
deterministic tool surface (names, path templates, parameters with their
rule and basis, observed query names, value-free body shapes, record
pointers), with excluded groups, three fixed unknowns and the shared
projection budget. The four format rules always apply; after-uid-literal,
opaque-token and sibling-variance are opt-in and labelled, and literal
fold-in never touches a format-rule parameter. The api-surface accumulator
moves onto shared route, media-type and budget helpers without behaviour
change.
Refs morluto/rea#4933
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(web-network): check the manifest refusal text like the api-surface refusal
The real-capture verifier stringified the whole MCP result for the
environment-manifest refusal, so a declared "[" literal matched the result
envelope itself; compare the parsed refusal as the api-surface branch does.
Refs morluto/rea#4933
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(specs): record the hosted web-network lane run for the manifest view
Refs morluto/rea#4933
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(specs): reconcile the manifest view verification scope note with the hosted run
Refs morluto/rea#4933
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(analysis-view): bound body, route and name work in the environment manifest
Addresses the automated review findings on the PR: read request bodies at the
top level only instead of walking nested values; admit every literal route
part before materializing it; compare at most 32 segments in sibling-variance
and say so; keep stop words literal after a uid literal; keep the next
collision suffix per tool-name base; preserve a templated exclusion when two
groups print the same row.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(analysis-view): keep manifest folding, naming and media-type reads linear
Addresses the second automated review round: fold literal siblings through
set membership, key collision suffixes by the truncated name they share, and
charge a request media type before it is scanned.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(analysis-view): charge the manifest group key before building it
Addresses the third automated review round: the composite key copies the
method, origin, template and rules, so that copy is reserved before the key
is stringified.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(analysis-view): accept a +json suffix only on application media types
Addresses the fourth automated review round: `image/example+json` or
`text/example+json` no longer counts as JSON for origin selection, exclusion
or body shapes, matching the documented `application/json` and
`application/*+json` contract.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(analysis-view): require the capture parent's exact subject for a manifest
Addresses the fifth automated review round: the manifest cites the report
path and digest, so a parent whose subject is absent or names another path or
SHA-256 is refused at admission, as the capture view already does; the
api-surface view keeps its existing admission.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(analysis-view): share truncated suffix allocation work
Track suffix progress by the actual truncated prefix and suffix width. Distinct full names can collide only after suffix truncation; sharing that progress prevents quadratic rescanning while preserving shorter free names and existing allocation order.
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
* fix(analysis-view): merge only identifier-like siblings under sibling-variance
Addresses the sixth automated review finding: a bucket merged whenever one
literal carried a digit, folding plain words such as `mine` into the
parameter. The rule now partitions the bucket, merges the identifier-like
siblings and leaves the other literals as their own tools.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(analysis-view): combine qualifying sibling cohorts
Keep coded identifiers and a qualifying plural hyphenated cohort in one eligibility Set before checking the minimum merge size. Read each literal once and reuse the same membership at the consumer, preserving non-identifier routes and the three-hyphen minimum.
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
* docs(analysis-view): clarify opt-in sibling folding
State the additional sibling-variance selection in the guide, acceptance specification and MCP parameter description. Preserve the existing single-rule behavior and caller-selected inference rules.
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Addresses a thirteenth automated review finding: `application/json;q=0`
explicitly refuses JSON, so it no longer counts toward the exact Accept ranges
a session takeover requires.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses the twelfth automated review finding: bodyUsed only means a body
was disturbed, so a route that read one chunk of an upload left the rest
paused on a keep-alive socket. The close decision now follows the incoming
message's end of stream.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the eleventh automated review finding: dot segments such as
`/public/../mcp` were canonicalized to `/mcp` before routing. The bridge now
refuses any target whose path differs after parsing, so routing sees exactly
the path the client sent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the tenth automated review round: the bridge joins an origin-form
request target onto the listener's origin so a network-path target stays a
path and absolute-form targets are refused; only a lone initialize can replace
an open session; and the shutdown hook records completion only after it
returns, so a hook that threw stays eligible for the next close.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the ninth automated review round: a method Fetch refuses, such as
TRACE, is answered with 405 instead of escaping the request guard; the
unread-body decision now comes from the incoming message's own body
declaration, so a GET or HEAD carrying a body closes too; and the regression
uses a keep-alive client and checks that the socket actually closes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the eighth automated review finding: a refused Host or Origin, an
unknown route or the startup 503 answered a POST while its body stayed unread
on the paused bridged stream; such replies now carry Connection: close so an
unread upload cannot keep a socket open.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the follow-on finding on the signal latch: the HTTP entry now owns
its exit status, so a 0 returned by startup after a recorded signal keeps the
signal's 130 or 143 while a real failure still wins; a process test sends the
signal as the listening line appears.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the sixth automated review finding: SIGINT and SIGTERM are recorded
from the moment the HTTP entry starts, the lifecycle owner acts on a recorded
signal as soon as it registers, and a second signal during startup exits at
once.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the fifth automated review round: the instance's shutdown hook runs
exactly once even when the listener closes without a live session; the
initialize probe no longer awaits cancelling its cloned body, which would wait
for the original request to drain; and the node:http bridge passes repeated
Set-Cookie values through as an array.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the fourth automated review round: a sessionless POST replaces a
session only when its Accept header lists application/json and
text/event-stream as exact media ranges, and a session whose transport
shutdown rejected stays owned so the next close retries it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the third automated review round: loopback is classified from the
address the listener bound with the configured name added to the Host and
Origin allowlist; scoped IPv6 literals are refused before binding and a URL
that cannot be formed closes the listener; listener errors reach onerror only
after a successful bind; the MCP runtime keeps its factory owner as the single
owner of instance cleanup through `closeInstance: false`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the second automated review round: a sessionless POST replaces a
session only when its media type proper is application/json, and the server
instance is created after the listener binds so a bind failure leaves nothing
to clean up and a factory failure releases the listener.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>