Pairing and credentials

The security model has one headline: no long-lived org secrets sprawl across the fleet. Each machine holds a device keypair and one paired API key; every other credential is leased from the workspace at the moment of use and forgotten.

Device keypair identity

Every agent owns a long-lived Ed25519 device key, generated on first use and stored 0600 at state/org/device-key.pem. The private key never leaves the machine; it signs outbound material, and the public key is presented in the pairing handshake and pinned by the verify layer. A freshly provisioned agent self-bootstraps a stable identity that survives restarts — there is nothing to pre-provision.

The same detective layer verifies what the server signs: directory snapshots, policy bundles and event checkpoints are Ed25519-signed by the org server, and the client re-derives the hash-chained event log to detect tampering.

Two enrollment lanes

Admin-minted key is the primary lane: an admin mints an org API key in Settings → API keys and pairs it to the agent's member, as described in Connect an agent to your workspace.

Pre-auth pairing inverts it — useful when the machine is being provisioned before an admin is at a keyboard:

  1. Open the handshake from the machine

    Pick a short code and run maestro pair <code> in the agent repo. This POSTs pairing.request — the one genuinely unauthenticated method — carrying the code, the agent's derived id and display name, and the device public key. The outstanding handshake is recorded under org.cohort.pairing in config/org.yaml.

  2. Share the code with an org admin

    The admin approves the handshake (pairing.approve, an admin-scope method), which mints the agent's key with the default agent scope set — including credential.use.

  3. Complete enrollment

    Set the issued key as org.cohort.token (or re-run maestro setup --only org). The config stays disabled until both endpoint and token are present.

The credential broker

Running 50 agents means 50 machines that would each need a DeepSeek, Moonshot or OpenAI key — an unmanageable breach surface where rotating one key means touching every mini. The broker collapses it: each provider key is set once at the org, sealed with AES-256-GCM at rest, and never logged.

Admins manage provider keys in Cohort → Settings → Secrets — shared credentials scoped per provider. On a self-hosted org server the admin CLI is the equivalent, and reads the value from stdin so it never lands in shell history:

printf '%s' "$DEEPSEEK_API_KEY" | cohort credential put --provider deepseek
The Secrets tab in workspace Settings listing per-provider shared credentials
Settings → Secrets — each provider key set once at the org

Leases at spawn time

When the model router is about to spawn a non-Anthropic backend, it needs that provider's key in the child process environment. Instead of reading a file on disk, the agent calls credential.lease (scope: credential.use). The server checks the credential's allowed roles and agents against the agent's verified identity, then returns the plaintext once over TLS with a short expiry. The agent injects it into that one child process's env and discards it.

The invariants, in order of importance:

  • Never persisted. The leased value exists only in process memory and the single spawn's env — never written to config/, disk, or logs.
  • Local wins. A key you set in the agent's own .env always beats the org lease for that provider.
  • Fail-open. Broker unreachable or key missing falls through to whatever the agent already has; a genuinely absent key surfaces as the router's missing_credential, never a crash.
  • Audited. Every put, lease and revoke appends a row to the org's hash-chained event log — provider, agent, time. Never the value.

Rotation and revocation

Rotate once, at the org: re-set the provider key and every agent picks it up on its next lease — nothing to push, no machine to touch, no restart to coordinate. Because leases are short-lived, the fleet converges within one lease cycle. Revoking a provider makes subsequent leases fail closed, so agents stop being able to spawn that backend at all.

The kill switch

To contain a specific machine rather than a provider key, deactivate the agent centrally: the next presence beat (within about 60 seconds) returns a halt directive, the agent's token is revoked, and it stops acting — and can no longer lease anything. Tokens are short-lived and renew via a device-key challenge, so a deactivated agent cannot keep running on a stale token; equally, an outage with expired tokens heals on reconnect without anyone touching the machine.

The local secret store

Separate from org provider keys, each agent has a local secret lifecycle for its own credentials:

maestro secrets list              # names only, never values
maestro secrets sync [--name X]   # pull org-brokered secrets into the local store
maestro secrets rotate --name X   # new value read from stdin

Resolution precedence is explicit override → broker → file → env, reads fail open, and every rotation appends a names-only audit row to logs/audit/secrets.jsonl — who, which name, when, which provider. Never the value.