Overview
The top-level secrets section on an Agent declares how provider credentials are resolved when a session starts. It stores references only (for example an environment variable name)—never plaintext API keys in git.
# Do not commit this
secrets:
anthropic:
value: sk-ant-api03-... # rejected by tooling
# Do commit this
secrets:
anthropic:
fromEnv: ANTHROPIC_API_KEY # value read at run from your shellThis matches the paradigm’s credential references: credentials are declared with the Agent, supplied at session start, and used only while that session is active.
secrets:
anthropic:
fromEnv: ANTHROPIC_API_KEY
openai:
fromEnv: OPENAI_API_KEY
spec:
model:
provider: anthropic
name: claude-sonnet-4-5
secret: anthropic # optional when a secret named after provider existsSee also the Agent definition for how secrets fits alongside metadata, spec, and output.
Secret definition (v1)
Each entry under secrets is a map key (the secret name) and a small object:
| Field | Type | Required | Description |
|---|---|---|---|
fromEnv | string | Yes | Environment variable read on the machine starting the session (for example phrony run) |
Secret names must match [a-z][a-z0-9_-]* (stable identifiers such as anthropic, openai).
v1 supports fromEnv only. Future spec versions may add fromFile, vault references, and other resolution sources. Backends that call the runtime over gRPC can supply values directly in resolved_secrets on RunSession instead of using fromEnv.
Naming conventions
Use short, stable secret names (anthropic, openai) that match or relate to spec.model.provider. Set fromEnv to the conventional provider API key variable on the run host (for example ANTHROPIC_API_KEY, OPENAI_API_KEY).
Linking secrets to the model
| Field | Path | Description |
|---|---|---|
secret | spec.model.secret | Names a key in secrets used for the model provider’s API key |
Validation rules:
- If
spec.model.secretis set, it must name a key insecrets. - If
secretsis non-empty andspec.model.secretis omitted, the secret name defaults tospec.model.providerwhen that key exists insecrets; otherwise validation fails with a clear error. - If
secretsis omitted,spec.model.secretmust not be set.
MCP server auth
MCP servers can reference the same secrets map for mcp_servers[].auth.secret. At run, the runtime decrypts the session-scoped value and sets Authorization: Bearer … or a custom header. Omit auth when the MCP endpoint is public.
Publish and session lifecycle
flowchart LR YAML["agent.yaml\nrefs only"] Pub["phrony agents publish\nrefs only"] Dep["phrony agents deploy\nactivate"] Run["phrony run\nread fromEnv"] SS["session_secrets\nencrypted"] YAML --> Pub --> Dep Dep --> Run --> SS
- Author — Add
secretswithfromEnvreferences in the Agent YAML (committed to git). - Publish — The runtime stores an immutable agent version with ref-only
secretsin the manifest snapshot. No API keys are read or sent at publish. Seephrony agents publish. - Deploy — Activate the published version for sessions in this runtime. See
phrony agents deploy. - Run — On the run host, supply each referenced env var: export them in the shell, pass
--env-file/-etophrony run, or sendresolved_secretsover gRPC from your backend. The CLI resolvesfromEnvnames from the process environment (after any env files are loaded) and sends values in a separate gRPC field (not embedded in manifest JSON). The runtime encrypts each value and stores ciphertext insession_secretsfor that session only. RequiresRUNTIME_SECRETS_ENCRYPTION_KEYon the runtime. Seephrony runand Runtime overview. - Purge — When the session reaches a terminal status (
completed,failed,cancelled), the runtime deletessession_secretsrows. Non-terminal sessions (awaiting_input,awaiting_approval,awaiting_tool,running,pending) keep secrets so multi-turn runs and daemon recovery after restart still work.
phrony agents validate checks secret refs and can warn when local fromEnv variables are unset; it never reads or transmits secret values.
Bundle runs
Multi-agent bundles with spec.agents delegate to specialists that may need different credentials. Each agent still declares only the secrets it uses in its own manifest.
At lock, validate, and publish, tooling unions secret requirements across the frozen closure. If two members declare the same secret name with different fromEnv values, the command fails with a message naming both members.
When you phrony bundles run, the CLI fetches the union from the deployed bundle via the runtime (same source as phrony bundles secret-requirements), resolves values from your environment or --env-file, and sends the full union on the root session. The runtime stores ciphertext in a bundle secret pool on the root session. Nested child sessions inherit only the secret names they declare, decrypted from that pool—not hop-by-hop from the immediate parent.
Local manifest changes to secret refs require bundles lock → publish → deploy before run picks them up. Use bundles secret-requirements to inspect what a deployed version needs.
Content hash and rotation
The manifest content hash used for immutable republish is computed from ref-only manifest bytes. Changing env values on the run host does not change that hash.
To rotate credentials, start a new session with the updated env vars (or new resolved_secrets). You do not need to bump metadata.version or republish for secret rotation alone. Republish only when the manifest itself changes (refs, model, tools, and so on).
Security
| Topic | Guidance |
|---|---|
Plaintext in git | Forbidden— value / plaintext rejected at parse time |
Run transport | Resolved secrets cross gRPC once per session start; use TLS for remote runtimes |
Database | Session ciphertext only until terminal; protect Postgres like any secret store |
Master key | One RUNTIME_SECRETS_ENCRYPTION_KEY per runtime deployment |
Logs | Never log secret values; run errors may name missing env vars |
phrony run | Resolves fromEnv from the process environment; optional --env-file / -e loads a dotenv file first (shell env wins over file) |
Attach | Resuming an existing session ( session_id) does not accept new secrets; missing rows after upgrade return FailedPrecondition |
Up next