Files
rea/docs/specs/agent-environments/environment-serve/spec.md
T
Edgar Arakelyan 10f1994fe1 feat(environment): serve an environment manifest as a replay environment (#4975)
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>
2026-10-20 03:06:29 +08:00

134 lines
8.2 KiB
Markdown

# Serve an environment manifest as a replay environment
Issue: https://github.com/morluto/rea/issues/4933
## Outcome
`rea environment-serve <manifest.json> --evidence <capture.json> [--host H]
[--port N]` serves an environment manifest, projected by `inspect-analysis-view`
from retained HAR capture Evidence, as an agent environment: an MCP server at
`/mcp` with one tool per manifest tool that replays the retained records the
tool cites, the environment card at `/.well-known/agent-env.json` in the shape
the agentenv framework's echo example publishes, and a `/agentenv` data plane
that answers every JSON-RPC method as not found. Nothing is fetched from the
recorded application; every answer is retained Evidence marked as target
content. This increment adds the serve command only; exporting a bundle is a
later increment.
## Requirements
- Read both files as bounded JSON input, authenticate each as REA Evidence and
require `inspect_analysis_view` Evidence of kind `environment-manifest` and
complete `inspect_web_network_capture` Evidence; refuse any other pair with an
`invalid_request` whose issue path names the offending file.
- Forward caller cancellation to file reads and check startup boundaries before
authentication/listener work; an already observed abort must not bind or
announce a listener.
- Refuse a manifest whose `parent_evidence_id` differs from the evidence file's
`evidence_id` before binding any port; Evidence authentication covers the
capture's records, so no further record-coverage refusal is defined.
- Build an `McpServer` from the manifest alone: each tool's input schema lists
every path parameter as a required string described by its segment index,
rule and observed values, every observed query name as an optional string,
and an optional `body` object for `POST`, `PUT` and `PATCH` (a query name
that collides with a path parameter becomes `query_<name>`, a query name
longer than 64 characters becomes `query_<n>`, and the body
becomes `request_body` when a query parameter is named `body`), closed with
`additionalProperties: false` so unknown arguments are refused; the
description reads `METHOD template; replays retained records [ordinals];
evidence ev_…`. The served catalog stays separate from REA's own tool
contracts and digests.
- Prefix tool descriptions with the shared captured-catalog warning so
standalone card readers receive the trust notice before MCP initialization.
- Bound each path-observation, body-key and cited-ordinal description sample to 1 KiB
of UTF-8 whole values before joining them, with a separate omitted-count and
manifest-Evidence notice. Keep complete arrays and replay semantics unchanged.
- Bound captured method, path and query-name prose to UTF-8-safe display prefixes
under the same budget, with an explicit full-value Evidence notice. Preserve
complete lookup values; long path and query names use collision-safe aliases.
- Parse a recorded query only when supplied query arguments can affect selection.
- Replay by selecting, among the tool's cited records, the one whose path
segments at the parameter indexes match the most supplied values, then the
most of those segments equal to the value as written rather than only after
percent-decoding, then the most supplied query values, keeping manifest order
on ties; return `status`,
`content_type`, `body` (parsed JSON for JSON-like content types with safe numeric conversion,
else text; bodies over 256 KiB cut at a UTF-8 boundary with
`truncated: true`) and `replay` with `record_ordinal`, `evidence_id`,
`exact_match`, `recorded_path` and `recorded_query` (each cut to 1 KiB, with
`recorded_line_truncated`; an explicit empty query is `?`), `body_recorded` (false
when the capture kept no response body, so a `null` body is not mistaken for
a recorded one), `body_encoding` (`base64` when the retained bytes are not
UTF-8, so they are returned exactly rather than decoded lossily) and
`truncated` (a large retained base64 body is decoded only up to that limit),
as both
structured content and text, with REA's existing target-content provenance
marker in `_meta`.
- Serve the card with exactly the echo example's keys and nesting: `name`
(`ENVIRONMENT_NAME` from the environment snapshot, else the manifest name; a
set but blank value is refused as `configuration_invalid`),
`protocolVersion` `1.0`, `url` `/agentenv`, `preferredTransport` `JSONRPC`,
`additionalInterfaces` `[{url: /mcp, transport: mcp}]`, `capabilities` with
`extensions: null`, the tools and `operations: []`, and
`children_environments: null`. Read the `/agentenv` body within the SDK's
4 MiB bound and answer `413` with JSON-RPC `-32000` beyond it.
- Resolve host and port as `rea mcp --http` does (flags, `MCP_HOST`/`MCP_PORT`,
then `127.0.0.1` and port `0`), announce the bound URL as one pino JSON line
on stderr, serve through the shared streamable HTTP listener, and run until
SIGINT or SIGTERM only, then close, print one `stopped` document and exit
`130` or `143` through the shared command cancellation handler; a signal
during loading returns `cancelled` before binding; a listener
failure after binding is logged and ends the command with a
`resource_constraint` error and exit `1`.
- Register the flat `environment-serve` command in the CLI inventory with
described arguments and no MCP tool contract; keep the CLI wiring thin, the
mapping and replay in the application layer and the server assembly in the
server layer; load the listener lazily so the command catalog registers
without it.
## Acceptance and limits
Services tests cover source loading and each refusal, tool mapping and schema
derivation, the card shape, exact and best-effort selection, query matching,
tie order, text and non-parsing bodies, truncation at a UTF-8 boundary and
base64 content. Adapters tests byte-compare the served card with the echo card
shape carrying the fixture tools, drive `/agentenv` including a body over the
bound, and call tools over the SDK streamable HTTP client with provenance and
the unknown-argument refusal asserted. The mcp-boundary test
spawns the built CLI on a manifest and capture produced from the slice-1
Grafana-shaped fixture and asserts tool count, an exact `get_users_by_id`
replay with `body.login === "probe"`, an inexact replay, the card under
`ENVIRONMENT_NAME` and the data-plane refusal; the cli-boundary test covers
the mismatched-parent refusal, the missing `--evidence` usage error and an
unreadable path; the process-boundary test asserts a `143` exit with a
released port. Replay validates no supplied values, performs no network
activity and establishes no authorization, response semantics or state
transitions. One MCP client is served at a time, as the shared listener
defines. Non-loopback binds relax the `Host` check and are not exercised; a
listener `error` after binding is wired but not provoked by a test.
## Resource and cancellation boundaries
- Whole discovery admission reserves at most 1 MiB using six bytes per UTF-16
unit plus schema overhead before retaining each schema property. Aggregate
tools, arguments and selected environment identity participate; oversized
catalogs fail with a non-retryable capacity error before bind. The calibration
used 1500 query fields with eight concurrent discovery requests; compact wire
size can be smaller than the conservative reservation.
- Query matching scans selected names one field at a time; path scoring scans
once and retains only supplied parameter coordinates. Native form-decoding
semantics, malformed escape handling and complete captured facts are preserved.
- Content-type display is bounded to 1 KiB and reports `content_type_truncated`;
complete media-type semantics remain available to body interpretation.
- Cancellation also owns DNS/listen startup; an observed abort closes the
listener before its URL is announced.
## Deferred issue criteria
Exporting a manifest and its evidence as an agent-environment bundle with a
verifier, and the hosted verification lane, are later independent increments
of #4933. This PR must reference rather than close the whole issue.
Numeric data-plane IDs that lose value through JSON number conversion are invalid
requests with null response IDs, rather than silently changed correlation values.