v1.20.64see what's new →

Secrets and deploy

Updated June 24, 2026

Touch-ID-gated bundles inject keys into a single command. Nothing touches disk in plaintext.

Why secrets exist as a CLI surface

Agents need credentials. To deploy, they need cloud API keys. To open a PR, they need a GitHub token. To hit a third-party API, they need that API's key. The wrong design is the line every engineer has typed at least once: export STRIPE_SECRET_KEY=sk_live_... in a dotfile that gets committed by accident next Tuesday.

The right design is named bundles stored in macOS Keychain, owner-readable only, that the user unlocks with Touch ID exactly once per session. The agent never reads the file. The shell history never sees the value. The dotfile contains nothing.

Create a bundle

agents secrets create stripe-prod
agents secrets add stripe-prod STRIPE_SECRET_KEY

A bundle is a named group of environment variables. The first command registers the name. The second prompts for the value — it is read off the tty with echo disabled, written straight to Keychain, and never appears in your shell history. Add as many keys to a bundle as the deploy needs: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, DATABASE_URL all under stripe-prod.

Inject the bundle into a single command

agents secrets exec stripe-prod -- bun run deploy.ts

This invocation prompts for Touch ID. Keychain releases the bundle to the exec process; the keys are materialized as environment variables for the lifetime of the child process and nothing else. The child — bun run deploy.ts — runs with full credentials. When it exits, the keys are gone from memory. They were never on disk.

Each command that resolves a bundle is its own process, and macOS will not cache a biometry-protected item across processes — so the prompt fires once per bundle per process. Three commands, or three agents running at once, means three prompts. The secrets-agent below collapses that to one.

One prompt for the whole session

agents secrets unlock stripe-prod   # one Touch ID; held for 24h
agents teams start checkout         # every teammate reads it silently
agents secrets status               # what's held, and when it locks
agents secrets lock                 # wipe it; next read re-prompts

unlock reads the bundle once and holds the resolved values in a local broker behind a user-only socket — the ssh-agent model. Every later run reads from memory with no prompt. The hold ends on its TTL (default 24h), when you lock, or when the screen locks or the machine sleeps. Nothing touches disk. Mark a bundle session tier and set secrets.agent.auto: true and the first prompt of a run populates the broker for you — no unlock step at all. The trade-off is deliberate: while unlocked, any process running as you can read the bundle from the socket without a prompt, so keep high-value keys on the default biometry tier, which is never auto-held.

List and audit

agents secrets list
agents secrets show stripe-prod

list prints every bundle name and the count of keys in each. show prints the key names in one bundle — never the values. To inspect a value, you would have to call exec with printenv, and that requires Touch ID. The audit surface is intentionally cheap; the read surface is intentionally gated.

Rotate without restarting the agent

agents secrets add stripe-prod STRIPE_SECRET_KEY

Adding a key that already exists overwrites the value. No flag, no confirmation prompt beyond Touch ID, no config file to edit. The next agents secrets exec picks up the new value. The agent's prompts, scripts, and AGENTS.md do not change because they never referenced the value — only the bundle name.

Why this belongs in the harness

Hashimoto's framing of the harness is: programmed tools the agent can invoke, one per failure mode you have seen. The failure modes around credentials are well known — leaked keys in commits, agents that paste a token into the wrong file, mocked deploys that pass and then break in production because the real keys were never wired up.

agents secrets exec is the programmed tool that closes all three. The agent calls a real bun run deploy.ts against real production credentials, then verifies the deploy with a real health check. There is no mocked path. There is no plaintext on disk. The mistake is structurally impossible because the surface that would have allowed it does not exist.