Skip to main content

The Environment plane

The Environment plane is the stateful world the agent acts in — Han’s “S” in E = {T, H, V, S, C}. It is one of BenchFlow’s four swappable planes (Sandbox, Agent, Environment, Reward). See architecture.md, “The Environment plane & the manifest”. A benchmark author never subclasses the framework. They write one file — an environment.toml manifest — and the default adapter (ManifestEnvironment) runs it on any Sandbox provider. The manifest is the entire integration surface.

The manifest schema

The manifest’s keys live under an [environment] table.

[environment]

Exactly one of image / base_image must be set. When owns_lifecycle is false the manifest must declare [[environment.services]]; when it is true it must not.

[environment.task_selection]

[[environment.services]]

An array — one table per service the framework starts (only when owns_lifecycle = false). It is the declarative replacement for the hard-coded SERVICES dict in benchflow/sandbox/services.py.

[environment.readiness]

[environment.forward_env]

[environment.state]

Present only for an environment that supports roll-backsnapshot / restore. Absent this table, the environment is treated as stateless and snapshot/restore raise RuntimeError.

Worked example — ClawsBench

benchmarks/clawsbench/environment.toml — the internal-dogfood stateful multi-service benchmark (mock Gmail / Slack / Calendar / Docs / Drive):
One manifest serves the whole benchmark even though smolclaws builds a per-task image carrying only a subset of the services: ManifestEnvironment probes each service’s entry point with --help and starts only the services whose package is actually installed in this per-task image.

Worked example — chi-bench

benchmarks/chi-bench/environment.toml — the other topology, and the external proof that a heavy environment onboards untouched. chi-bench is a ~25k-LOC healthcare simulator that ships one ready-to-run image whose entrypoint starts its own services, so the manifest declares no [[services]]:
This ~25-line manifest is the entire framework-integration surface: chi-bench’s image, Dockerfile, and entrypoint are unmodified, and the ~920 LOC of Harbor coupling it previously carried collapses into the manifest. ClawsBench (base_image + framework-started [[services]]) and chi-bench (image + owns_lifecycle = true) are the two topologies behind one contract. See benchmarks/chi-bench/README.md for the field-by-field mapping.

How it runs

ManifestEnvironment runs the in-sandbox topology (the architecture’s core): the services run inside the rollout’s own sandbox, so the agent reaches them on localhost. During a rollout:
  1. Rollout.start() provisions the environment — starts the declared services inside the sandbox.
  2. It gates on readiness() — the agent never runs before the environment is healthy.
  3. Rollout.cleanup() tears the environment down.
Run one task or a task directory against an environment manifest with bench eval create --tasks-dir .... --environment-manifest applies the Environment-plane manifest to every rollout in the Job pipeline.
YAML configs may declare the same seam with environment_manifest: <path> at the top level so the batch run is reproducible from disk. --environment-manifest is distinct from --sandbox: the sandbox is where it runs (the Sandbox plane); the environment manifest is the world (the Environment plane).

Exporting for training

A scored rollout’s trajectory exports to the Verifiers / ORS dataset format that prime-rl ingests — benchflow.trajectories.export:
Each line is one record: prompt, completion, reward, metrics, is_completed, is_truncated, example_id, info — the shape pinned against the Verifiers RolloutOutput type.

Roll-back — snapshot / restore

snapshot / restore are real. For an environment that declares an [environment.state] table, snapshot() copies each declared SQLite file with sqlite3 .backup (a consistent online backup) into a per-snapshot directory inside the sandbox, and restore(snap) copies the captured files back over the live paths. This is the substrate Rollout.branch() runs on: a branch quiesces the agent and services, restores a snapshot, and explores an alternative continuation. An environment with no [environment.state] table is stateless — snapshot/restore raise RuntimeError.

Reset — reset

reset returns the environment to the per-task baseline so it can be reused for a fresh episode without tearing down the sandbox (distinct from restore, which rolls back to an arbitrary snapshot). For an environment that declares an [environment.state] table, provision captures a baseline; reset then stops the framework-started services, restores the baseline, and restarts the services. For an owns_lifecycle = true manifest the framework cannot restart entrypoint-owned services; reset is then a no-op (and the host must recycle the container for a hard reset).

Not yet implemented

ManifestEnvironment does not exercise:
  • Sidecar / shared-fleet topology — host-exposed ports, AccountBroker.