Configuration reference
Every .agentenv/config.toml table, key, and precedence rule
agent-env reads one file, .agentenv/config.toml, and finds it git-style: it walks up from the
current working directory to the nearest .agentenv/config.toml. Nothing in it is required — with
no file at all agent-env runs on the local stores Getting Started
assumes — and AGENT_ENV_CONFIG overrides the walk with an explicit path, which must point at a
file that exists: a typo raises ConfigError rather than silently dropping your whole
configuration.
AGENT_ENV_CONFIG also moves your local database
Local store state is rooted at the directory holding the discovered config file. Found by the
upward walk, that directory is your project's .agentenv/, so SQLite lands in
.agentenv/document_store/documents.db. Point AGENT_ENV_CONFIG at /etc/agent-env/prod.toml
and the same default writes to /etc/agent-env/document_store/documents.db — a different, empty
database, with nothing logged about the switch. agent-env creates that directory and writes a
.gitignore into it covering document_store/ and object_store/.
A complete example
One realistic file: a shared MongoDB + S3 control plane, images on GHCR, secrets from one AWS Secrets Manager bundle, envs on E2B VMs, and one custom step.
# .agentenv/config.toml
[stores.document]
impl = "agent_env.store.document_store:MongoDocumentStore"
[stores.document.config]
uri = "secret:mongodb_uri" # read through [stores.secret], never stored here
database = "agent_env"
[stores.object]
impl = "agent_env.store.object_store:S3ObjectStore"
[stores.object.config]
bucket = "my-team-agent-env"
region = "us-west-2"
[stores.image]
impl = "agent_env.store.image_store:OciRegistryImageStore"
[stores.image.config]
registry_host = "ghcr.io"
repository_prefix = "my-org/agent-env"
[stores.image.config.credentials]
impl = "agent_env.store.image_store:SecretStoreCredentials"
secret_key = "registry_auths" # a Docker config.json-shaped JSON string
[stores.secret]
impl = "agent_env.store.secret_store:AwsSecretsManagerSecretStore"
[stores.secret.config]
secret_name = "my-team/agent-env" # one secret holding a flat name -> value mapping
region = "us-west-2"
[sandbox]
default = "e2b,local" # fallback chain: E2B first, then the host Docker daemon
agent_default = "e2b"
[sandbox.providers.e2b.config]
api_key = "secret:e2b_api_key"
base_template = "agent-env-docker-v2" # an immutable, versioned E2B template
[sandbox.attribution]
team = "platform" # filled into any dimension a caller leaves unset
[model]
base_url = "https://openrouter.ai/api/v1"
api_key = "secret:openrouter_api_key"
default = "openrouter/anthropic/claude-sonnet-4"
[model.roles]
judge = "openrouter/openai/gpt-4o"
[task_steps]
impls = ["mycorp.steps:GradeEssayStep"]
[runner]
impl = "agent_env.runner.local_runner:LocalRunner"
[runner.config]
workers = 4Every seam has the same shape: an impl pointer — the string module.path:ClassName — plus a
config table. agent-env imports the class, checks it subclasses the expected interface, and
constructs it with YourClass.from_config(**config). The default from_config forwards the table
straight to __init__. The extension model covers what each seam expects of the
class you point at, and how the failure policies differ between seams.
Value interpolation
A config value that is exactly env:NAME or secret:KEY is a reference: agent-env resolves
it when it builds the thing that needs it. Both forms take an optional ?default suffix —
env:AWS_REGION?us-west-2 — and env:NAME? resolves to the empty string. Without a default, an
unresolved reference raises ConfigError naming the reference.
[stores.object.config]
bucket = "env:MY_BUCKET" # from the process environment
region = "env:AWS_REGION?us-west-2" # with a fallback
[stores.document.config]
uri = "secret:mongodb_uri" # through the configured secret storeThe reference must be the whole value. "postgres://env:DB_HOST/agent_env" is a literal string;
agent-env does not substitute inside it. Lists and nested tables are walked recursively, and
non-string values pass through untouched.
env: resolves against the process environment everywhere. secret: resolves through whatever
[stores.secret] selects, which means secret values never appear in the file — only the key
names. Where no secret store is available, a secret: reference fails loud instead of resolving
to nothing. Not every table gets a resolver:
| Table | env: | secret: |
|---|---|---|
[stores.document.config], [stores.object.config], [stores.image.config] (including the nested credentials table) | yes | yes |
[stores.secret.config] | yes | no — the secret store cannot resolve itself; fails loud |
[sandbox.providers.<name>.config], [state.providers.<name>.config], [runner.config] | yes | yes |
[model] base_url / default / [model.roles] / [model.params] | yes, at first read | yes, at first read |
[model] api_key | yes, lazily per call | yes, lazily per call |
[sandbox.attribution] | yes | no — no resolver is passed; fails loud |
[conversations] default_human_a2a_url | yes | yes |
[sandbox] default / agent_default, any impls list, any impl pointer, [explorer] | no | no |
[model] api_key is deliberately late-bound: resolving it eagerly would make every read of the
[model] table require credentials, including reads that never call a model.
Precedence
Four layers set the same values. Later layers win.
| Layer | Example | Wins over |
|---|---|---|
| Built-in default | local stores, local sandbox, LocalRunner | nothing |
.agentenv/config.toml | [stores.document] | built-in defaults |
AGENT_ENV_* environment variable | AGENT_ENV_DOCUMENT_STORE=local | the config file |
| Explicit Python call | configure(document_store=...), set_runner(...) | everything |
Per setting, highest first:
| Setting | Precedence | Unconfigured |
|---|---|---|
| Config file path | AGENT_ENV_CONFIG → upward walk to the nearest .agentenv/config.toml | no file; every default applies |
| Document / object / image / secret store | set_<kind>_store() or configure(<kind>_store=…) → AGENT_ENV_<KIND>_STORE → [stores.<kind>] | local |
| Runner | set_runner() → AGENT_ENV_RUNNER → [runner] | local (LocalRunner, workers=2) |
| Env / CUA / general sandbox | --sandbox on a deploy, or --env-sandbox / --cua-sandbox on a run, or set_env_sandbox_provider() / set_cua_sandbox_provider() / set_sandbox_provider() in process → [sandbox] default | local |
| Agent sandbox | --sandbox on an agent deploy, or --agent-sandbox on a run, or set_agent_sandbox_provider() in process → [sandbox] agent_default | local |
| Env state provider | --env-state-type or the env_state_type deploy argument | local_postgres |
| Model endpoint | per-call base_override → LITELLM_BASE_URL → [model] base_url | native routing for a provider-prefixed model; ConfigError for a bare name |
| Model API key | caller-supplied key → LITELLM_API_KEY → [model] api_key | ConfigError where a key is needed |
| Model params | the prompt_agent step's model_params, merged per key → [model.params] | {} |
| Attribution dimension | the value a caller passes → [sandbox.attribution] | the dimension is omitted |
| Human-A2A base URL | AGENT_ENV_HUMAN_A2A_URL → configure(default_human_a2a_url=…) → [conversations] | ConfigError at the step that needs it |
Two rows deviate from the general rule and are worth reading twice. [conversations] puts the
environment variable above the explicit configure() call, so local development can repoint a
process at a locally-running endpoint and win over both the checked-in file and application code.
[sandbox] has no environment variable at all: the only overrides are the per-command sandbox
flags and the in-process setters, one of which exists per sandbox kind.
Registries are built once per process
The store, task-step, artifact, env, sandbox and state registries are memoized on first use.
Editing config.toml has no effect on a running process, and reset_config() does not clear the
type registries. Every process that reads, reconnects to, or reaps a resource — a second CLI
invocation, a worker, the explorer — needs the same config.toml and the same custom modules
importable.
Table reference
[stores]
Selects an implementation for each of the four stores. A bare string is an alias; a sub-table
is an explicit impl + config.
[stores]
document = "local"
object = "local"
image = "local"
secret = "local"local is the only alias that resolves. Each store also recognises the name of its external
backend — mongo for document, s3 for object, ecr for image, aws for secret — purely
to raise ConfigError explaining that no coordinates are compiled into the package and that the
backend needs a full [stores.<name>] table. Any other value raises too; note that the message
names the AGENT_ENV_*_STORE variable even when the value came from the file.
[stores.<name>]
name is one of document, object, image, secret. Each table takes impl plus a config
table whose keys are the class's from_config parameters. The built-ins:
| Store | impl | config keys |
|---|---|---|
document | agent_env.store.document_store:LocalSqliteDocumentStore | path (str) |
document | agent_env.store.document_store:MongoDocumentStore | uri (str, required), database (str, required) |
object | agent_env.store.object_store:LocalFilesystemObjectStore | root (str) |
object | agent_env.store.object_store:S3ObjectStore | bucket (str, required), region (str, optional) |
image | agent_env.store.image_store:LocalRegistryImageStore | registry_host (str, default localhost:5000), repository_prefix (str, default ""), credentials |
image | agent_env.store.image_store:OciRegistryImageStore | registry_host (str, required), repository_prefix (str, default ""), credentials |
secret | agent_env.store.secret_store:LocalSecretStore | values (table), file_path (str), use_env (bool, default true) |
secret | agent_env.store.secret_store:AwsSecretsManagerSecretStore | secret_name (str, required), region (str, required), ttl_seconds (float, default 300.0), min_refresh_interval (float, default 10.0) |
Any class subclassing DocumentStore, ObjectStore, ImageStore, or SecretStore on your
PYTHONPATH works the same way — see Storage backends.
A key beside impl and config is ignored with a log warning, not an error.
[stores.image.config.credentials]
A nested impl pointer resolved by the image store itself, against the OciRegistryCredentials
interface. It mints docker-login material lazily, at authentication time, so secret rotation is
observed without a restart.
[stores.image.config.credentials]
impl = "agent_env.store.image_store:SecretStoreCredentials"
secret_key = "registry_auths" # default: "registry_auths"SecretStoreCredentials reads a Docker config.json-shaped JSON string from the secret store.
It must stay a JSON string — a nested YAML mapping raises ConfigError. Inline base64 auth
values work; credsStore and credHelpers entries are ignored, because the process that pulls the
image has no helper binaries. The alternative built-in is
agent_env.store.image_store:EcrCredentials, which requires region, access_key, and
secret_key and mints a short-lived ECR token per pull.
[task_steps], [artifacts], [envs]
Three lists of impl pointers that register classes agent-env has never seen, so a stored
document referencing an unknown type still deserializes.
[task_steps]
impls = ["mycorp.steps:GradeEssayStep"]
[artifacts]
impls = ["mycorp.artifacts:RubricArtifact"]
[envs]
impls = ["mycorp.envs:MyCustomEnv"]Each class registers under its own type. None of the three takes a config table — a step's,
artifact's, or env's runtime configuration comes from its stored document, not from the TOML.
agent-env fails loud on an unimportable pointer, a class that does not subclass the expected base,
a class that inherits the base type instead of declaring its own, and a type that collides with
a built-in or another entry. Without registration, reading such a document raises
Unknown task step type: … / Unknown artifact type … / Unknown env type: …. See
Custom types.
[sandbox]
Process-wide compute defaults. default covers env, CUA, and general sandboxes; agent_default
covers the agent sandbox. Both default to local. A comma-separated value is a fallback chain,
tried left to right.
[sandbox]
default = "e2b,local"
agent_default = "e2b"Built-in names, always available: modal, modal_vm, e2b, local. Values here are read
literally — env: references do not work in this table.
[sandbox.providers.<name>]
Registers a sandbox provider. The entry is either a bare impl pointer string or a table with
impl and an optional config.
[sandbox.providers.my_cloud]
impl = "mycorp.compute:MyCloudSandboxProvider"
[sandbox.providers.my_cloud.config]
region = "env:AWS_REGION?us-west-2"
ssh_key = "secret:my_cloud_ssh_key"A built-in name may appear here with a config table only; its implementation is fixed, so
setting impl on a built-in name raises ConfigError, as does any key other than config. For a
custom provider, the registry name must equal the .type of the Sandbox objects it produces —
that string is persisted and is how a later process rebuilds the provider to reconnect or tear
down. agent-env enforces this at deploy: a mismatched sandbox is terminated and
SandboxProviderTypeError names both values. See Providers.
A built-in may still need config of its own: e2b takes an api_key and a base_template
naming an immutable, versioned E2B template, and raises ConfigError without the latter.
[sandbox.attribution]
Default cost-attribution dimensions. The map is open — core threads it through without reading it, and each provider picks out the keys its billing backend understands.
[sandbox.attribution]
team = "platform"
project_id = "env:MY_PROJECT_ID"Any dimension a caller leaves unset is filled from here; with no table, unset dimensions are
omitted entirely. Values take env: references only.
[state.providers.<name>]
Registers a state provider: where an env's data lives. The only built-in is local_postgres, a
co-deployed database container.
[state.providers.my_warehouse]
impl = "mycorp.state:MyWarehouseStateProvider"
[state.providers.my_warehouse.config]
region = "env:AWS_REGION?us-west-2"The registry name must equal the class's type attribute, and — unlike sandbox providers — this is
checked at registration, so the process fails before it provisions a store nothing can reattach
to. Colliding with a built-in name raises ConfigError; keys beside impl and config are
dropped with a warning. A deploy picks a state provider with --env-state-type or the
env_state_type deploy argument. The extension model explains why the two
provider seams check their names at different moments.
[model]
The model gateway: endpoint, credential, and the model names agent-env picks by role. Every key is
optional, and the allowed set is closed — an unknown key raises ConfigError listing what is
accepted.
[model]
base_url = "https://openrouter.ai/api/v1"
api_key = "secret:openrouter_api_key"
default = "openrouter/anthropic/claude-sonnet-4"
[model.roles]
agent = "openrouter/anthropic/claude-sonnet-4"
judge = "openrouter/openai/gpt-4o"
[model.params]
aws_region_name = "us-west-2"| Key | Type | Default |
|---|---|---|
base_url | str | none; LITELLM_BASE_URL wins over it |
api_key | str (usually a secret: reference) | none; LITELLM_API_KEY wins over it |
default | str | none |
roles | table of role → model | {}; an unset role falls back to default |
params | table | {} |
[model.roles] is an open map; agent-env itself asks for agent and judge. [model.params] is
schema-free provider configuration — Azure's api_version, Bedrock's credentials — splatted onto
the in-process model call and passed to a deployed agent that advertises support for it. It may not
set a key agent-env already puts on the call: model, messages, api_key, api_base, user,
metadata, timeout, response_format. Doing so raises ConfigError.
There is no default model endpoint
With no [model] table and no LITELLM_* variables, a provider-prefixed model such as
openai/gpt-4o routes natively through LiteLLM, and a bare model name raises
ConfigError: No model endpoint configured for …. agent-env compiles in no proxy URL, no API key,
and no default model. Configure base_url + api_key, or use provider-prefixed names with that
provider's own credentials. See Model gateway.
[conversations]
The base URL a human-in-the-loop step parks its conversation on. There is no default.
[conversations]
default_human_a2a_url = "https://hub.example.com/api/v1/a2a/human"A non-string or blank value raises ConfigError at the config seam rather than inside a step.
Resolution happens only where the URL is consumed, so runs that never contact a human work with
this unset. The deploy step run-scopes what you configure by appending /instance/{instance_id},
so configure the base URL.
[runner]
Selects the dispatcher behind a submitted run. local is the only alias that resolves;
temporal is recognised and raises, because an address, namespace, and certificates are deployment
facts rather than package defaults.
[runner]
impl = "agent_env.runner.local_runner:LocalRunner"
[runner.config]
workers = 4 # default 2; must be >= 1The runner's type is persisted as each run record's runner field, so a control plane restarted
under a different [runner] logs a warning when it is asked about a run it did not submit.
[explorer]
Knobs for the local control plane that agent-env up serves.
[explorer]
port = 8234 # default 8234
# cors_origins = ["http://localhost:3000"] # default: loopback origins only
# allowed_hosts = ["explorer.internal"] # extra Host header values to accept
# static_dir = "path/to/built/ui" # omit for API-only or the packaged UIhost is deliberately not configurable: the explorer is unauthenticated, so it always binds
127.0.0.1. Reach it from elsewhere over an SSH tunnel, or run your own server and opt hostnames
in through allowed_hosts. [explorer.plugins].impls mounts additional API routers the same way
[envs].impls registers env classes.
What is not validated
There is no whole-file schema check. An unknown top-level table — [task_step] instead of
[task_steps] — is silently ignored: nothing registers and nothing is logged. Inside a store
section, a key other than impl and config produces a log warning only. The fail-loud behaviour
begins once agent-env is inside a table it recognises.
Environment variables
Every variable that changes agent-env's behaviour. agent-env never loads a .env file; export
what you need yourself.
| Variable | Overrides | Default |
|---|---|---|
AGENT_ENV_CONFIG | config-file discovery, and the root of local store state | the nearest .agentenv/config.toml walking up from the CWD |
AGENT_ENV_DOCUMENT_STORE | [stores.document] | local |
AGENT_ENV_OBJECT_STORE | [stores.object] | local |
AGENT_ENV_IMAGE_STORE | [stores.image] | local |
AGENT_ENV_SECRET_STORE | [stores.secret] | local |
AGENT_ENV_RUNNER | [runner] | local |
AGENT_ENV_HUMAN_A2A_URL | [conversations] default_human_a2a_url and configure() | unset; resolution raises |
AGENT_ENV_FIXTURE_PREFIX | nothing; prepends <prefix>/ to artifact object keys so a fresh object store can share a bucket | "" |
AGENT_ENV_LOCAL_SANDBOX_DIR | the local provider's work-directory root | ~/.agent-env-sandboxes |
AGENT_ENV_MODAL_REGION | the region the modal provider pins a gateway's sandboxes to | us-east-1 |
AGENT_ENV_MODAL_APP_NAME | the Modal app base name | agent-env |
AGENT_ENV_LOAD_CONCURRENCY | the derived per-load concurrency cap; must be >= 1 | derived from the sandbox's core count |
AGENT_ENV_SNAPSHOT_AFTER_LOAD | the default for snapshot_after_load when a caller does not set it (1/true/yes) | off |
AGENT_ENV_INSECURE_TLS | disables TLS verification in the MCP spec-conformance verifier (1/true/yes) | verification on |
AGENT_ENV_RDS_HOST, _PORT, _DBNAME, _USERNAME, _PASSWORD, _SSLMODE, _AUTH, _REGION | a state provider's own admin-credential lookup. Setting _HOST selects the whole group; there is no per-key merge | unset; the provider looks up its own |
Variables agent-env reads that are not AGENT_ENV_-prefixed: LITELLM_BASE_URL and
LITELLM_API_KEY (both above [model]), MODAL_TOKEN_ID / MODAL_TOKEN_SECRET (above
~/.modal.toml and the secret store), and GITHUB_TOKEN (above the secret store). The AWS-backed
stores and providers additionally use boto3's own credential chain.
AGENT_ENV_ENVIRONMENT appears in .env.example but agent-env itself reads nothing from it; it
exists for a CLI plugin that chooses which config file AGENT_ENV_CONFIG lands on.
Defaults with no configuration
With no config.toml and no environment variables, agent-env is fully functional and entirely
local. Paths below are relative to the discovered .agentenv/ directory, or to .agentenv/ under
the current directory when no config file exists at all.
| Concern | Default | Where it lands |
|---|---|---|
| Document store | LocalSqliteDocumentStore | .agentenv/document_store/documents.db (stdlib SQLite, WAL mode) |
| Object store | LocalFilesystemObjectStore | .agentenv/object_store/ |
| Image store | LocalRegistryImageStore | an OCI registry at localhost:5000; the push path brings up a registry:2 container if nothing is already serving that port |
| Secret store | LocalSecretStore | process environment variables; no file until you configure file_path |
| Sandbox | local for both default and agent_default | containers on the host Docker daemon, work dirs under ~/.agent-env-sandboxes |
| Env state | local_postgres | a database container co-deployed with the gateway |
| Runner | LocalRunner(workers=2) | in-process asyncio; a run left behind by a previous process is failed at startup, not resumed |
| Explorer | port 8234 | http://127.0.0.1:8234 |
| Model | none | any bare model name raises ConfigError |
agent-env also creates .agentenv/ and writes a .gitignore there ignoring document_store/ and
object_store/, so generated state stays out of git while config.toml stays tracked.
The local sandbox has no VM boundary
local runs env instances and agents as containers on your own Docker daemon, sharing your kernel
and your network. It is the right default for developing a task and the wrong one for executing
anything you do not trust. Point [sandbox] default and agent_default at a remote sandbox
provider before running untrusted code or untrusted agents.
Related docs
- Core concepts — the nouns these tables configure
- Storage backends — writing a document, object, image, or secret store
- Compute and state providers — sandbox and state providers in depth
- Security — secrets, isolation, and network policy
- The extension model — what each
implpointer is checked against, and when - Custom types — the
[task_steps],[artifacts], and[envs]contracts - Tasks and steps — where
[task_steps]and[model]land at run time