Require operator-declared hostnames before serving MCP HTTP outside loopback, preserving Host/Origin gates. Parse hostname identities once, share typed startup recovery across CLI/MCP, and announce an admitted client URL. Generated AgentEnv bundles declare their client/service names and validate DNS/HTTP name identity before export. Validated through real packaged HTTP requests, direct SDK connection, compiled export refusals, 184 focused tests, static and documentation checks on the refreshed main integration. Final CI and review evidence is recorded in the PR body. BREAKING CHANGE: non-loopback deployments require MCP_ALLOWED_HOSTS or rea mcp --http --allowed-hosts; bundle names must be unchanged HTTP hostnames within the 63-octet DNS label bound. Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
7.2 KiB
Export an environment manifest as an agent environment bundle
Issue: https://github.com/morluto/rea/issues/4933
Outcome
rea environment-export <manifest.json> --evidence <capture.json> <dir> [--package-tarball <path>] [--name <override>] writes the manifest and
capture pair that environment-serve serves as a bundle folder the agentenv
framework runs with agent-env run <dir>: a README with provenance, an env
folder whose Dockerfile installs this exact rea-agents version and serves the
copied pair, a Python replay verifier and a smoke task. The hosted
real-agentenv lane exports such a bundle from the source-owned Grafana-shaped
capture through the built CLI, packs the checkout into the image and runs it
with the pinned framework release on the runner's Docker. This increment
completes the agent-environment work of the issue; serving came in the
previous increment.
Requirements
- Read each of the two files once through the regular-file reader, parse it
from those bytes and authenticate and cross-check the pair with
loadEnvironmentSourceasenvironment-servedoes, so the bytes copied into the bundle are the bytes that were authenticated; every refusal reports operationenvironment-export, and a manifest projected from a different capture is refused before anything is written. - Refuse a bundle directory that exists and is not empty, or that names a file,
with an
invalid_requestwhose issue path isdirectory; an absent or empty directory is accepted. Write every file with the exclusive-create flag so nothing is ever overwritten. - Write
README.md,envs/<name>/Dockerfile,envs/<name>/env.toml,envs/<name>/manifest.jsonandenvs/<name>/evidence.jsonas byte-exact copies of the inputs (mode0600on POSIX; Windows retains inherited ACLs and does not provide an owner-only guarantee),artifacts/check_<name>/verify.pyandtasks/smoke.json, where<name>defaults to the manifest's kebab-case name and--namemust be a kebab-case label of at most 63 characters for its DNS label that is not a device name Windows reserves (con,prn,aux,nul,com1–com9,lpt1–lpt9). Its HTTP hostname identity must remain unchanged, so numeric labels interpreted as IP addresses are refused before planning. - The Dockerfile uses the Node version pinned in
.nvmrcwith thebookworm-slimvariant, setsMCP_HOST=0.0.0.0,MCP_PORT=18765, andMCP_ALLOWED_HOSTS=127.0.0.1,localhost,<name>, installs Debian'spython3for the framework's readiness probe, installsrea-agents@<PRODUCT_IDENTITY.packageVersion>from the registry or, with--package-tarball, copies the tarball asrea-agents.tgz, installs it from/tmp/and removes it in the same layer, copies the pair to/env/, switches to the image'snodeuser and runsrea environment-serve /env/manifest.json --evidence /env/evidence.json. verify.pymirrors the framework's echo example:async def verify(mcp_url)overmcp.client.streamable_http, lists the tools and checks the count equals the manifest's, calls the firstGETtool without path parameters with no arguments and checks the replayedstatusequals the recorded status of that tool's first cited record, prints the description and returns[{"id": "replay", "result": <bool>, "description": <both checks>}]; with no such tool it checks the count only and says so.tasks/smoke.jsonisdeploy_envof<name>on the local sandbox followed byenv_outcome_verifierwithfile_artifact_idcheck_<name>andverifier_idreplay. The README records the capture artifact SHA-256, both Evidence ids, the manifest view digest, the tool count and the package source, states what replay does and does not establish, names the run command asagent-env run .from the bundle root (the folder it sits in, whatever the caller named it), and carries the rights note.- Print one JSON document with
status: "exported", the directory, name, tool count, both Evidence ids, the pinnedpackage_sourceand the written files with sizes. Register the flatenvironment-exportcommand in the CLI inventory; no MCP tool is added. - Add the
agentenvCI scope, the reusablereal-agentenv.ymlworkflow (workflow_dispatchandworkflow_callwithbuild_cache_artifact,ubuntu-24.04, SHA-pinned actions,setup-python3.12, a virtual environment withagentenv-framework==0.9.1334, the runner's Docker, 30 minutes), theverify:agentenv:environmentscript andscripts/verify-agentenv-environment.mjs, which requires absoluteREA_AGENT_ENV_COMMANDandREA_DOCKER_COMMANDlike every other lane verifier, runsagent-env run <dir> --sandbox localbounded to 15 minutes and resolves nothing fromPATH; wireci.ymlat the four spots and extend the two CI guard tests.
Acceptance and limits
Services tests cover the planned file set, the Dockerfile for both package
sources, the verifier text, the smoke task, the README, the name override and
its refusals, writing into an absent and an empty directory with byte-exact
copies and correct sizes, the tarball copy, and the refusals of a non-empty
directory, a file path, a missing tarball and an inline manifest. The
conformance test pins the registry install to the package.json version. The
cli-boundary test writes the folder from the fixture pair through the built
CLI, refuses a non-empty directory leaving it unchanged, and refuses a
mismatched pair without creating the directory. The CI guard tests enumerate
the new consumer, route and classification cases. The local end-to-end run
recorded in verification.md is the proof that one exported bundle runs under
agent-env run with the smoke task passed and no container left behind.
The export copies bytes and never inspects the tarball; it does not build or
run the image itself. Its writes are not transactional: a host failure after
the first file leaves the partial folder for inspection, as docs/cli.md
states. The verifier checks one replayed status and the tool count, not every
tool. The lane passes --sandbox local as designed although
tasks/smoke.json already pins sandbox_type: local on its deploy step, so
the flag restates what the task fixes. The lane verifier imports the
Grafana-shaped HAR builder from tests/fixtures/historicalHar.ts through
Node's type stripping rather than carrying a second copy under scripts/; the
agentenv scope lists that fixture so a change to it runs the lane. The lane
proves one framework release on one hosted runner and claims nothing about
any hosted platform. Three additions to the
designed Dockerfile and file set were needed by the framework, each found by
the local run: MCP_PORT=18765 as the listener default for the port the
framework maps, Debian's python3 because the framework's local sandbox
probes readiness with python3 -c "import socket; …" inside the container and
the slim Node image has none, and env.toml naming the environment, because
the framework otherwise reads the name from Python sources the Node image does
not have.
Deferred issue criteria
This increment closes the agent-environment scope of #4933 together with the previous three. Running the lane on the maintainer's CI and any follow-up on hosted platforms are outside this PR.