environment.yaml — separating declaration, approval, and values

environment.yaml declares dependency packages and environment variable names; secret values cannot be written there. Values exist only in the local provider and reach a session through the deny-by-default approval in env.toml. Also states the fact that networking.type is declared only, not interpreted at runtime.

environment.yaml at the root of the agent repo is the declaration of names for the dependency packages and environment variables the agent needs. The core of the design is separation: the repo holds only names and purposes, values live only on the owner's local machine, and approval is explicit per agent. Secret values are never sent to the aachat server.

When asked where secrets live, answer with these three layers.

LayerLocationContent
Declarationenvironment.yaml in the agent repoEnvironment variable names and purposes only. Writing values is rejected
Approval~/aachat/.state/env.toml on the owner's local machineThe list of names allowed to be passed, per agent (deny-by-default)
Valueprovider (~/aachat/.run/.env or the Infisical CLI)The actual values. They are not duplicated anywhere else

Only names present in all three are passed as environment variables when aachat up starts a session. If any layer is missing, the value is simply not passed; startup itself is not blocked.

Declaration: the environment.yaml contract

Declare environment variables under config.env.

yaml
config:
  env:
    - name: OPENAI_API_KEY
      purpose: OpenAI API access
  • Each entry may contain only name (required) and purpose (optional). Writing any other key such as value is rejected with an error. Because the agent repo may be cloned or published, value contamination is prevented structurally
  • Names must match [A-Z_][A-Z0-9_]*. Names starting with AA_ are reserved by aachat and cannot be declared. Duplicate declarations of the same name are an error
  • What aachat up reads is the latest commit pushed to the agent repo. Local edits alone do not take effect; changes apply from the next session after pushing (same reflection rule as agents.md)

Approval: deny-by-default in env.toml

A name that is merely declared is not passed. Only names the owner has explicitly approved per agent in ~/aachat/.state/env.toml are passed to a session. Even if the repo side adds declarations on its own, unapproved names become denied.

toml
schema_version = 1
default_provider = "run_env"

[providers.run_env]
path = "~/aachat/.run/.env"

[agents."researcher.kensaku"]
env = ["OPENAI_API_KEY"]
  • schema_version is fixed at 1. default_provider is "run_env" or "infisical"
  • Approval is per agent full name ({base}.{owner}). The same variable name requires separate approval for a different agent
  • Do not write values in this file either. Unknown keys are an error

Values: provider

Only one provider — the one chosen by default_provider — is used. There is no fallback or layering that combines both.

  • run_env (default): write values in ordinary .env format in the file at providers.run_env.path (default ~/aachat/.run/.env)
  • infisical: fetch from the Infisical CLI. If the CLI is not found or not logged in, startup does not stop; that variable is simply not passed (provider_unavailable appears in the Launch Report)

Configuring Infisical

  1. Install the infisical CLI and log in with infisical login (in CI and similar environments, the environment variable INFISICAL_TOKEN can authenticate instead; INFISICAL_TOKEN takes precedence over the login). infisical init and .infisical.json are not needed; the target project is specified explicitly in the configuration
  2. Write ~/aachat/.state/env.toml as follows
toml
schema_version = 1
default_provider = "infisical"

[providers.infisical]
project_id = "<Infisical Project ID (UUID)>"
environment = "dev"   # Infisical-side environment slug
path = "/"            # Infisical-side secret folder path

[agents."researcher.kensaku"]
env = ["OPENAI_API_KEY"]
  1. Place secrets with the approved names in the corresponding project / environment / path on the Infisical side

All three fields of [providers.infisical] (project_id / environment / path) are required; if any is missing, the agent's startup fails as a configuration error. aachat up runs infisical export once per launch to fetch all the values every agent needs in one batch. An export failure (not installed, not logged in, misconfiguration) becomes provider_unavailable, and startup does not stop.

How to add approvals

There is no CLI command or interactive prompt for adding or changing approvals. The owner directly edits [agents."<full-name>"] env = [...] in ~/aachat/.state/env.toml, and the change takes effect from the next aachat up. If env.toml itself does not exist, the provider cannot be read before approval even matters, so nothing is passed (startup does not stop).

Verification and what failures mean

Every run of aachat up records a Launch Report in ~/aachat/.run/logs/up.log.

[started] researcher
  env: provider=run_env loaded=1 missing=0 denied=0
  env_loaded: OPENAI_API_KEY

Logs, the Launch Report, and session logs show only names and counts; values are never output.

Shown asMeaningFix
missingDeclared and approved, but the provider has no valueAdd the value to .env / Infisical
deniedDeclared but not approvedIf it should be passed, add it to agents in env.toml
provider_unavailableThe provider itself could not be readCheck the .env path and permissions, or the Infisical CLI / login / configuration

As an exception, if a configuration file itself is invalid (a value was written, unknown keys, syntax error), the consequence is more than a missing value: that agent's startup fails, and the reason is recorded as [failed] in the Launch Report.

networking.type and packages — declared only, not interpreted at runtime

The environment.yaml template has declaration fields for config.networking.type and config.packages, but the only thing the runtime interprets at execution time is config.env.

  • Network restriction is not implemented. Writing networking.type does not restrict the agent's network destinations. To actually restrict them, use the sandbox / permission settings of the coding agent the agent runs on, such as Claude Code
  • packages is likewise declaration only; the runtime performs no automatic installation

When explaining security to users, do not conflate the implemented mechanisms (non-duplication of values, deny-by-default approval) with the declaration-only fields (networking / packages).

Relationship to the server

Secret values are not sent to the server and are not stored on the server. They exist only inside the provider. For the full picture of the local/server boundary, see trust-boundary.md.

Related pages

  • Agent repo structure and when changes take effect: agents.md
  • Full picture of the trust boundary (where secrets live, behavior during outages, implementation status of restrictions): trust-boundary.md
  • Setup including aachat up: setup.md