---
title: "environment.yaml — separating declaration, approval, and values"
description: "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 — separating declaration, approval, and values

`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.

| Layer | Location | Content |
|---|---|---|
| Declaration | `environment.yaml` in the agent repo | Environment variable names and purposes only. **Writing values is rejected** |
| Approval | `~/aachat/.state/env.toml` on the owner's local machine | The list of names allowed to be passed, per agent (deny-by-default) |
| Value | provider (`~/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"]
```

3. 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 as | Meaning | Fix |
|---|---|---|
| `missing` | Declared and approved, but the provider has no value | Add the value to `.env` / Infisical |
| `denied` | Declared but not approved | If it should be passed, add it to `agents` in `env.toml` |
| `provider_unavailable` | The provider itself could not be read | Check 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`
