AgentEnv Framework

Core Concepts

The nine nouns agent-env is built from, and how a run threads through them

agent-env is built from a small fixed vocabulary: artifacts hold the bytes, an env turns them into a running sandbox, and a task is a DAG of steps that deploys that sandbox, drives an agent through it, and grades what happened. Every other page on this site uses these words exactly as defined here; if you have not run anything yet, read Getting Started first and come back.

Artifact

An artifact is an immutable, versioned input — a Docker image, a file, a dataset. Each artifact is a document in the configured document store, and most also carry a blob in the object store under artifacts/<type>/<id>/<version>/<name>. put() never mutates anything: it allocates the next integer version for that id (versions start at 1; an artifact that has never been stored has version = 0), inserts a new document, and relies on a unique (id, version) index to reject any write over an existing version. The concrete types are DockerImageArtifact, FileArtifact, VMImageArtifact, EnvironmentArtifact (one env's seed data), SkillArtifact, CliArtifact, and the two universes below — see Artifacts.

Universe

A universe is an artifact whose payload is a set of ArtifactRefs: frozen (id, version) pointers to other artifacts. That pinning is what makes a world reproducible — a universe is the unit of world state you hand a task, and re-running it a month later loads the same bytes. Two kinds are concrete: EnvironmentUniverseArtifact bundles EnvironmentArtifact refs plus optional metadata files, and FileArtifactUniverse maps an original filename to a pinned FileArtifact ref. The rule for choosing: an environment universe when the data must be ingested into the services an env instance runs, a file artifact universe when the payload is files to stage onto a sandbox or files a run produced. Universe itself is abstract and raises TypeError on instantiation — see Universes.

Env

env, environment, and env instance

This site uses environment as the ordinary English word — in section titles, page descriptions, and introductions — and env as the technical noun for the stored definition, matching the Env class, the envs collection, and the agent-env env command group. They name the same thing. The deployment is always an env instance, never a "deployed env".

An env is a versioned definition: an id, a version, a type key, and the artifacts it is built from. An MCPServerEnv document, for example, holds a pinned DockerImageArtifact ref and the environment_name that image serves. Loading one resolves polymorphically through the env registry on the document's type field, so an unknown type fails loud with Unknown env type; the built-in types are mcp_server, multi, service_db, website, gateway_server, remote_mcp_endpoint, and coding_task_harbor. Storing an env starts nothing — see Environment types.

Env instance

An env instance is what deploy() produces: a live sandbox running the env's docker compose stack — an MCP gateway plus a container per service the env declares (one per MCP server, plus the website or database containers an env type brings with it) — described by a DeployedEnv record with an instance_id of the form <env_id>-<8 random chars>, a gateway_url, an mcp_url, a sandbox_id, and a created_at_utc/expires_at_utc window. The TTL is handed to the sandbox provider as the sandbox's own timeout, so an instance tears itself down whether or not the run that created it finished. One definition can have many instances alive at once, and deploying never writes back to the definition.

Definition versus instance

my-env names a definition; my-env-4f2a9c1b names one deployment of it. agent-env env deploy --id my-env mints an instance and agent-env env get-instance --id my-env-4f2a9c1b reads it back. remote_mcp_endpoint envs have no instance at all — their deploy() raises NotImplementedError, because they point at a live URL you already run, and are referenced from deploy_agent. See Lifecycle.

Task

A task is a named, versioned list of TaskSteps stored inline in one document, executed as a DAG whose edges come from each step's depends_on. Task.run() walks that DAG with asyncio, so steps with no unmet dependencies run concurrently. The constructor rejects duplicate step ids and forward references — every depends_on must name a step that appears earlier in the list. A step's fail_task_on_error (default True) decides whether its failure halts the run — no further steps are scheduled, and already-running siblings are allowed to finish — or is recorded and stepped over.

Omitting depends_on is not the same as an empty list

"depends_on": [] means no dependencies — the step is eligible to start immediately. Omitting the key entirely (None) means the step depends on every prior step in the list, which makes the task sequential. This is the most common reason a task that looks parallel runs one step at a time.

TaskStep

A TaskStep is one unit of work with a single abstract method, async execute(context) -> context. Steps are polymorphic by type key through the task step registry, the same pattern envs and artifacts use, and the registry is where custom steps get plugged in (Custom types). A step may also implement preflight(), which returns one message per detectable config problem and must answer without a sandbox or an env instance. The built-in set runs to several dozen types in seven families — provisioning, data staging, env control, execution, capture, grading, and registration validators — grouped in Tasks and steps and enumerated in the Task step reference.

TaskStepContext

The TaskStepContext is the one mutable object threaded through every step in a run: agent-env hands the same object to every execute() call, and a step publishes its output by mutating it in place. execute() returns the context by convention, but Task.run() discards the return value and keeps using the object it passed in. This is how steps communicate — a step reads what an earlier step put there, and nothing else is shared. Its fields are deployed_envs, deployed_agents, deployed_sandboxes, prompt_responses, metadata, agent_model, default_agent_model, agent_artifact_id, agent_harness, and instance_id. metadata is the open dict where conventional keys accumulate — seed, user_overrides, verifications, failed_steps — and to_safe_dict() recursively strips credential keys before the context is serialized anywhere.

Several layers can name the model an agent runs with. A run-level override (Task.run(agent_model=...), or agent-env task run --agent-model) beats the model field stored on the prompt_agent step, which beats context.default_agent_model — the value deploy_agent or install_agent stamps from the agent's declared default_model or the agent model role in your config file, and only if nothing has set it yet. The full override table is in Tasks and steps; the endpoint and credential precedence behind it is in Configuration.

Eval

An eval is a versioned list of pinned task references — (task_id, task_version) pairs, where a task_version of None resolves to the latest at run time. It holds no execution logic of its own: running an eval loads each referenced task and fans out Task.run() k times per task, optionally under a concurrency limit. Reach for a task when you want one scenario, an eval when you want a suite run repeatedly for pass@k — see Evals.

Trajectory

A trajectory is the recorded agent run: the raw spans an agent emits while working, captured as OpenTelemetry GenAI spans (gen_ai.* semantic conventions, no vendor-specific attributes required). prompt_agent uploads the raw trajectory to the object store and puts its URI on the PromptResponse; judges read a compacted form, which reduces the spans to an event list and externalizes oversized or base64 tool results to side files. See LLM rubric judging.

How a run fits together

The canonical pipeline is six stages. Each one leaves its output on the context for the next:

  1. deploy_env loads the env definition by (id, version), deploys it, and appends a DeployedEnv to context.deployed_envs. It refuses an env id already in the context.
  2. load_artifact ingests a universe into that env instance, and records what it loaded under context.metadata["loaded_environment_universes"] (or loaded_file_artifact_universes when staging files onto a sandbox).
  3. deploy_agent provisions an agent sandbox wired to the named envs' MCP gateways and appends a DeployedAgent to context.deployed_agents.
  4. prompt_agent sends the message over A2A, uploads the trajectory, and appends a PromptResponse to context.prompt_responses.
  5. A verifier grades the result and writes context.metadata["verifications"][<verifier_id>] = {"results": [...], "score": ...}.
  6. collect_artifacts pulls files off a sandbox, stores each as a FileArtifact, bundles them into a FileArtifactUniverse, and records both under context.metadata.

The context after such a run, annotated with the step that wrote each part:

TaskStepContext
├── deployed_envs[0]                     DeployedEnv     <- deploy_env
│   ├── instance_id                      "support-env-4f2a9c1b"
│   ├── mcp_url                          gateway MCP endpoint the agent calls
│   └── expires_at_utc                   when the sandbox reaps itself
├── deployed_agents[0]                   DeployedAgent   <- deploy_agent
│   ├── agent_name                       "default-agent"
│   └── a2a_url                          where prompt_agent sends the message
├── prompt_responses[0]                  PromptResponse  <- prompt_agent
│   ├── response                         the agent's final text
│   ├── agent_trajectory_s3_uri          raw OTel GenAI spans
│   └── tool_call_count
└── metadata
    ├── loaded_environment_universes[]                   <- load_artifact
    ├── verifications{<verifier_id>}                     <- any verifier
    ├── artifacts, file_artifact_universe                <- collect_artifacts
    └── failed_steps[]                   every step failure, with is_fatal per entry

Nothing forces this order. A task that deploys two envs in parallel, prompts an agent three times, or skips grading entirely is equally valid; what is fixed is the medium, because every stage communicates only through the context.

On this page