Skip to content
AgentEnv Framework

Composing environments

Combine small environments into a multi env that an agent sees as one MCP server

A task often needs more than one system. Dana's email in Creating your own environment asks for the Q3 numbers to go to Sam Lee in finance, and nothing in the inbox says what Sam's address is; a contacts book does. Instead of adding contacts to EmailEnv, you write a second small environment and compose the two.

A second environment

ContactsEnv follows the pattern of EmailEnv: one tool, contacts_lookup, and the three data methods over its list of contacts:

contacts/server.py
import json
from pathlib import Path

from agentenv_protocol import (
    AgentEnvEnvironment,
    DataPart,
    FilePart,
    add_data,
    environment_card,
    get_data,
    reset_data,
    tool,
)


@environment_card(name="contacts")
class ContactsEnv(AgentEnvEnvironment):
    def __init__(self) -> None:
        self.contacts: list[dict] = []

    @reset_data
    async def _reset(self) -> None:
        self.contacts.clear()

    @add_data
    async def _add(self, parts: list) -> None:
        for part in parts:
            if isinstance(part, DataPart):
                self.contacts.extend(part.data.get("contacts", []))
            elif isinstance(part, FilePart):
                seed = Path(part.file.uri.removeprefix("file://"))
                self.contacts.extend(json.loads(seed.read_text()).get("contacts", []))

    @get_data
    async def _state(self) -> list:
        return [DataPart(data={"contacts": self.contacts})]

    @tool(name="{environment_name}_lookup")
    async def lookup(self, name: str) -> dict:
        """Find contacts whose name contains the given text."""
        n = name.lower()
        return {"contacts": [c for c in self.contacts if n in c["name"].lower()]}


if __name__ == "__main__":
    ContactsEnv().serve()

With a copy of the Dockerfile in contacts/, you register it with the command that Register it uses for email:

Terminal
agent-env env mcp-server put --id contacts --dockerfile contacts/Dockerfile

Compose the two

A multi env pins its children by id and version and gives them one name. It adds no server code of its own, so each child stays a small server that you build and version on its own. This command composes email and contacts into office:

Terminal
agent-env env multi put --id office --name office \
  --mcp-server email:1 --mcp-server contacts:1
Output
Fetching MCPServerEnv: id=email version=1...
  Found: id=email version=1
Fetching MCPServerEnv: id=contacts version=1...
  Found: id=contacts version=1
Creating MultiEnv...
Created MultiEnv: id=office version=1

The stored multi env holds the two pinned references and the name. email:1 stays at version 1 even after you put email again. To take a newer child, you put office again, which appends a new version of office.

You deploy office like any other env, with the command from Deploy it:

Terminal
agent-env env deploy --id office
Output (end)
Deployed!
Instance ID: office-jk24hzlq
Env MCP Url: http://localhost:61854/mcp
Env Gateway Url: http://localhost:61854
Env DB Web Url: http://localhost:61855/
Env DB MCP Url: http://localhost:61856/mcp
Expires At (UTC): 2026-09-29 09:25 UTC

Every env deploys in a topology, the shape its instances take. A single env such as email can be registered in any of them, as Choose the environment's topology covers, and a multi env always takes the default, the gateway topology: each child runs in its own container, side by side behind one gateway. An agent connected to the instance's MCP URL sees one MCP server named office, with every child's tools in one list: email_search, send and contacts_lookup.

Last updated on

Ask a question · Report an issue

On this page