Skip to content

feat(validate): deploy the mail-mcp example's env, named by an env alias, in place of an internal env - #325

Open
earakely-scale wants to merge 10 commits into
mainfrom
feat/validation-env-alias
Open

earakely-scale wants to merge 10 commits into
mainfrom
feat/validation-env-alias

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Why. agent-env a2a-agent validate deploys multi-caf64d, an env only Scale's stores hold, beside the agent it checks. Anywhere else the MCP check can't run, and validate exits 1. OpenHands hit this validating their agent.

Change

  • The env is an example. examples/mail-mcp is an MCP server, built from its Dockerfile like echo-mcp, with a search_emails tool over a fixed inbox: the tool the MCP check prompts the agent to call. Its smoke task checks a search for "invoice" finds the invoice.
  • Env aliases. aliases.toml takes [envs] as it takes [agents], and the example names its env mail.
    • In a bundle, the planner pulls the env in and rewrites the field to its id (Plan.env_aliases). An env definition naming an alias, such as a MultiEnv's servers, is refused: an alias names an env only in a task's steps.
    • Outside a bundle, Env.get resolves the alias, with namespace routing on. The env it returns answers to the alias's id, so a deployment records, and steps look it up by, the id the task names. Its metadata writes go to the env the alias names (Env.stored_id).
    • No store may start an env on an installed env alias's id (EnvStore.put_document), so a deployment recorded under an alias always reloads the alias's env. An env stored under such an id before keeps taking versions.
  • The validator brings its own env, everywhere. A2AAgent.validate() holds namespace routing for its run. When the store has no env of the alias's id, it first writes the example (with the new bundle.write_bundle, run_bundle's writes and infra without a run).
    • Locally, the image is built with Docker.
    • On a remote sandbox (the hub's modal_vm), the image is context-only and built in the VM, so the hub's worker needs no Docker and no puts.
    • The CLI's validate only reports a failure to write the env.
  • bundle.write_bundle is new and public. STORE_GETTERS reads envs exactly when it weighs an alias against the store, as it already did for agents.

Rollout for the hub

  • Pin: the hub worker's validate activity already calls agent.validate(), so it needs only the pin to this release.
  • XDG_STATE_HOME: the worker must point this at a writable directory, such as /tmp/agent-env, because the validator keeps the example in the local @local store. The worker image runs as uid 65532 and assumes a read-only root.
  • No put into any store.

Tests

  • Env.get: an alias resolves under the alias's id, including a versioned reload, and its metadata writes land on the aliased env. An env stored under the id before wins. Also covers a refused alias, the hint for a near miss, and routing off.
  • The store refuses to start an env on an alias's id, and an env already stored under it keeps taking versions.
  • aliases.toml [envs]: parsing, refusals and bundle checks. The planner pulls an aliased env and rewrites the task's fields. A store env shadows the alias. An env definition naming an alias is refused.
  • write_bundle writes and sets up infra without running anything.
  • The validator writes the example when the store has no env of the id, and not when it has one. It runs with routing on throughout. The CLI reports a write failure.
  • Shipped bundles: the env alias table is exactly mail.
  • Integration (slow tier, about 1 min): agent-env run mail-mcp passes. A task stored outside any bundle that names mail deploys the env, recorded as mail.
  • Unit tier passes; the plugin API check reports no break.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no new actionable issues were found.

Summary

The PR replaces the validator’s internal env with the shipped mail-mcp example and adds env aliases.

  • Tasks can name bundled environments with short aliases.
  • Validation supplies its own mail environment when needed.
  • Process shutdown tests wait for cleanup to finish.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["A2AAgent.validate()"] --> B["Turn namespace routing on"]
    B --> C{"Store has mail?"}
    C -->|No| D["write_bundle prepares mail-mcp"]
    C -->|Yes| E["Run validation"]
    D --> E
    E --> F["Deploy mail beside the agent"]
Loading

Reviews (9) · Last reviewed commit: "test: treat a process reaped while its /..." · Reviewed by Greptile

…ias, in place of an internal env

The validator deployed multi-caf64d, an env only Scale's stores hold, so anyone else's validate failed its MCP check.
It now deploys mail-mcp: an example MCP env with the search_emails tool the check calls, named by the alias its
bundle's aliases.toml gives it, which now takes [envs] as it takes [agents]. validate writes the example's env into
local stores when the store has none of that id; a store's own env of that id wins, which is how a service gets it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner October 23, 2026 21:20
Comment thread src/agent_env/env/env.py Outdated
earakely-scale and others added 2 commits October 23, 2026 14:31
…lias names

Env.get gave an alias's env under the alias's id, so update_metadata and merge_metadata wrote to an id the store
doesn't hold. The env keeps the id it's stored under (stored_id) for them, and the alias for the runs that name it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…lias

# Conflicts:
#	tst/unit/bundle/shipped_bundles_test.py
@earakely-scale

earakely-scale commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator Author

End to end at 1a257b0. After it, the merge of main brings only agentenv-protocol's A2A proxy, and b690ab5 renames a slow-tier test's store env from mail to inbox, since the store now refuses to start an env on the alias's id. 31e98e3 fixes a gemini bridge test that read a reaped process's /proc stat.

(a) As someone outside Scale runs it. One m5.2xlarge (linux/amd64) admin VM: a clean venv with this branch's wheels, local stores only, --sandbox local, claude-haiku-5-5 via LiteLLM.

Step Result
Env.get("mail") before anything ran 'mail' is an alias of @local/agentenv-framework/mail-mcp/mail, … isn't in the store yet
agent-env run agents --task claude-code-smoke --task pi-smoke --task openhands-smoke writes the three shipped agents
a2a-agent validate --id claude-code prints mail → @local/agentenv-framework/mail-mcp/mail (the alias agentenv-framework's mail-mcp bundle gives it): writing it, builds the example's image with Docker, registers service-db and the gateway, then validates. Exit 0; all 7 extensions validated, mcp-config/v1 included
validate --id pi, validate --id openhands both exit 0 with mcp-config/v1 validated
Env.get("mail") after mail v1 MCPServerEnv mail: the alias's env, under the alias's id
env mcp-server put --id mail … refused: env id 'mail' is an alias of @local/agentenv-framework/mail-mcp/mail, … give this env another id

For pi and OpenHands, peer-agents/v1 (pi) and install/v1 (both) came out false. Neither check deploys an env: the peer copy and the installed copy get no model, and unlike Claude Code these agents have no built-in default. That's a follow-up, not this PR.

(b) Dev stage, as the hub's worker would run it. agentenvhub-sdk 0.34.20 with this branch, AGENT_ENV_ENVIRONMENT=dev (database agent_env_dev), the hub's default sandboxes (envs on modal_vm), and XDG_STATE_HOME pointed at an empty directory. No put into any store.

  • Test agent: fd4305-validate-test v1, on claude-code-cli v71's image, so no shared agent's results are overwritten.
  • The run: validate printed the mail → …: writing it line and wrote the example to the local @local store. The image is context-only, so no Docker build ran on the writer; the modal_vm sandbox built it, and deploy_env took 44 s. Exit 0.
  • Comparison, same agent: the released 0.9.1353 (multi-caf64d) against this branch (mail). Both print 57 checks, identical except the peer-agents reply excerpt, which embeds a random token. 52 of 55 pass in both, the MCP check included. The other 3 are optional agent-card fields Claude Code doesn't set (authentication, documentationUrl, provider).

Cleanup. The VM, security group and key pair are deleted, no results contain secret text, and no containers were left on the VM. Dev keeps mail-mcp v1 and fd4305-validate-test v1 from an earlier round, since stores are append-only. Nothing reads mail-mcp now that the alias is mail.

Comment thread src/agent_env/env/env.py
Comment thread src/agent_env/bundle/plan.py
Comment thread src/agent_env/cli/a2a_agent/validate.py Outdated
earakely-scale and others added 7 commits October 23, 2026 15:21
…dation deploys it on

--sandbox picks the agents' backend; the validation deploys the env on the configured env backend, so writing the
example checked the wrong one. The installer journey also lists agent-env's four bundles by count now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… refuse env aliases in env definitions

A deployment of an env got by an alias recorded only the alias, so a reload after the store gained an env of that id
read the wrong one. Its record now keeps the id the env is stored under, and the reloads go through
Env.of_deployment, which reads that env under the alias. An env definition naming an env alias, as a MultiEnv's
servers might, is refused with what to name instead, since an alias names an env only in a task's steps.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rdown, rather than checking at once

On a loaded CI host a SIGKILL lands, and an abandoned run's teardown ends, a moment after the call returns, so the
local sandbox's teardown tests and the episode abandon test failed by turns. They poll for the outcome now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…re may start an env on an alias's id

The hub would otherwise need its own put of the env, kept in step with the example by hand. A2AAgent.validate()
now holds namespace routing and writes the mail-mcp example itself when the store has no env of the alias's id, so
the hub's worker gets it too: on a remote sandbox its image is context-only and built in the VM. The alias is mail.

An env store refuses to create an env under an installed env alias's id, so a deployment recorded under the alias
always reloads the alias's env, and Env.of_deployment and the stored id on deployments go.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nv alias's id

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant