Declare them
list of strings
Unique UPPER_SNAKE environment variable names, never their values. Omit for no explicit
secret inventory. Required credentials are still inferred from providers, tools, and
connections; missing declarations warn rather than making those credentials optional at
run time.
agent.yaml
The refusal does not repeat what you wrote. This slot is where a
pasted credential lands when somebody mistakes a name for a value. A message
that quotes it back puts the key in a terminal, a CI log, and any bug report
copied from either. The file and the line are enough to find it. The same is
true of every other place a name belongs: a connection’s
environment: value,
a destinations: number, a tool’s token_env. The rule: declare what the generated project reads
secrets: is the package’s explicit inventory of environment names the
generated project reads. Some names appear directly in package files. The
compiler infers others from a provider choice:
Declare all of those names. A declared name also joins the generated environment
requirements when the compiler cannot infer its use, for example when a local
handler builds an environment lookup dynamically.
Do not declare names supplied by the driver or deployment platform:
These names are not package secrets, but you may still have to supply some of
their values when you deploy.
.env.example lists the values you supply. The
compile report and README cover the complete required set and who supplies each
one.
Leaving out a name the compiler inferred is a warning, not an error. The inferred
name still stays in the generated environment instructions and checks; the
warning says the explicit secrets: inventory is incomplete:
How generated files use the inventory
The compiler combines declared secrets with environment names inferred from models, tools, tracing, destinations, and the selected phone route. The environment template.build/<target>/.env.example is the operator’s
checklist, not a copy of secrets:. It lists the values you supply for this
build. Every line in it is a line to fill in:
What it holds depends on the build:
compile-report.json carries the complete list under
required_env, and the emitted README.md explains where each value comes
from.
Runtime checks. The generated project checks the values the current session
needs and names what is missing. The set comes from declared and inferred names,
so a package with no secrets: block still checks names the compiler can infer:
Secrets never flow through templates
{{...}} renders variables only. Naming a secret in a
template is a compile error, not a value that leaks at run time:
The seams a secret travels through
tools/reschedule_appointment.yaml
token_env in particular is checked:
Where the values come from locally
unmute dev builds the run’s environment from your shell, then .env and
.env.local in the current directory, then .env and .env.local in the
package directory. Later files win, so a repository wide .env can hold
shared keys while a local or package file overrides a value.
Both .env and .env.local are gitignored. Neither is generated with values
or committed.
Where the values come from in production
On a code target, use your platform’s secret store. LiveKit secret updates restart instances without rebuilding. Pipecat secret updates need a separate rollout after the set is ready. Both accept a file of values; neither needs a new image just to replace an existing value.The SLNG vault
Anslng target has no .env.example and no build/<target>/ project tree:
it compiles to a deployment body, not a project, so there is no file for a
secret to sit beside.
Instead, unmute deploy works out what the vault must hold from the compiled
body itself: a tool’s authentication block, a hosted tool’s mirrored secret
names, and every {{$NAME}} vault token in a prompt, a greeting, or a tool
field. It reads the package’s secrets: list nowhere, so a name declared
there that no tool authenticates with, and that no {{$NAME}} token uses, is
never checked here. Declare it anyway if the same package also targets
livekit or pipecat: those two do read secrets:.
A name the vault already has is left alone; a missing name can be created on
the spot, by running voiceai secret create <name>, which asks for the value
on its own masked terminal prompt. The value never enters unmute’s own
process, so it never reaches argv or either output stream.
Creating a missing secret this way is the only write a deploy makes. No tool,
MCP server, or trunk is ever created, changed, or deleted from here.
Where to go next
CLI reference
Every command, flag, and exit code.
Going live
Deploying with real credentials.