Living document. Update this, don't create new versions.
Every user secret lives in one file: ~/.agentwire/.env. One key per
line, classic dotenv format. Keys never go in config.yaml, shell profiles,
or .agentwire.yml — config holds env var names where needed, never
values.
# ~/.agentwire/.env
RESEND_API_KEY=re_...
QUO_API_KEY=...
OPENAI_API_KEY=sk-...chmod 600 ~/.agentwire/.envThis is checked, not just documented (#887). It used to be convention only — and convention lost: the file was found at 0644, world-readable, holding every key. See File permissions below.
agentwire/__main__.py calls load_dotenv(~/.agentwire/.env) on every
entry point — CLI commands, the portal, the MCP server, the scheduler. Any
code reading os.environ sees the keys; so does any feature added later.
That universality is why this is the one blessed spot.
Two consequences:
-
Long-running processes read it at startup. After editing the file, restart what needs the new key:
agentwire portal restart. -
The file is dotenv format, not shell. Values may legally contain
&, spaces, or quotes unescaped, so neversourceit — an unquoted&backgrounds half a line and silently corrupts your shell state. To pull a single value out in a script:grep '^RESEND_API_KEY=' ~/.agentwire/.env | cut -d= -f2-
| Feature | Env var(s) | Notes |
|---|---|---|
| Email channel (Resend) | RESEND_API_KEY |
Channels |
| Quo / OpenPhone SMS channel | QUO_API_KEY (or legacy OPENPHONE_API_KEY) |
Channels |
| Cloud STT | var named by stt.cloud.api_key_env — OPENAI_API_KEY by default; GROQ_API_KEY, MISTRAL_API_KEY, … per provider |
Cloud STT |
| PyPI publish (maintainers) | PYPI_TOKEN |
release workflow only |
agentwire doctor reports, for each configured feature, whether its
expected var is present — names only, never values.
Cloud STT (#280) set the shape every new integration should copy: config names the env var, the env var holds the key.
stt:
cloud:
api_key_env: "OPENAI_API_KEY" # the NAME — the key itself never lives in configWhy indirection instead of a hardcoded var name: multi-provider features (one OpenAI-compatible endpoint among many, multiple pi providers) need the user to pick which key applies without agentwire knowing every provider in advance. Single-provider features (Resend, Quo) just hardcode their var name — same convention, no indirection needed.
What this buys, in either form:
config.yamlstays shareable/committable without a redaction pass.- The portal's config editor never round-trips a secret to the browser.
- One file to
chmod 600, back up, or rotate.
Four paths under ~/.agentwire/ must never be readable beyond their owner:
| Path | Mode | What leaks if it isn't |
|---|---|---|
~/.agentwire/ |
0700 |
the filenames of everything below it |
~/.agentwire/.env |
0600 |
every API key |
~/.agentwire/portal.token |
0600 |
the portal auth token — full access to every session |
~/.agentwire/machines.json |
0600 |
remote hosts, users and paths |
Two mechanisms keep them there:
- Enforced on write. Every owner-only file agentwire writes goes through
core.write_owner_only: the mode is set on the file descriptor before any bytes land, and the file is renamed into place, so there is no window where the content is world-readable — not even on first creation under a permissive umask. A rewrite therefore also heals a file that had already drifted wide. This coversportal.token, therole-prompts/store andmachines.json; before #887 the registry was minted with a barewrite_textand inherited the umask, which is how a 0644 registry ended up on a live machine. - Checked by
agentwire doctor..envis the exception to the above — nothing in agentwire writes it (it's hand-authored, only ever read viaload_dotenv), so the check is the only thing standing between it and a slow drift to 0644. Doctor reports every path that is group- or world-readable, naming the exactchmodto run;agentwire doctor --yestightens them. Healing is opt-in rather than automatic — tightening a file is safe in a way loosening never is, but these are your files on your machine. A path that is tighter than required (0400, say) is never reported and never "fixed" back open.
- Damage-control gives
.envzero access for agents — agent sessions can't read, edit, or even mention the file in shell commands (damage control). Keys flow to features through the process environment only. --envcaveat: secrets passed viaagentwire new --env KEY=VAL(orrecreate/fork --env) are injected withtmux set-environmentat session creation. That keeps them out ofps auxwwwand shell history, but anything with tmux access on the box can runtmux show-environment -t <session>. Acceptable on a single-user box; know the trade-off.- Server-side only: no key is ever sent to the browser or echoed by a portal endpoint.