Skip to main content

Agent Manifest Reference

Every agent Tempest can launch is described by a JSON entry in config/agents.json. It is the single source of truth for the agent list — there is no hardcoded roster. The same entries drive the New Session menu, the Automations headless runner, the Chat pane bridges, and the agent-aware clear in the Message Queue. This page documents the entry schema, how placeholders are substituted, and how the manifest reaches your install. For per-agent launch commands and troubleshooting, see Supported Agents; for how sessions consume the flags at spawn time, see Agent System Overview.

The bundled agents

config/agents.json ships inside the app build and defines 16 agents: Agents you add yourself under Settings → Agents → Add merge on top of this list (see custom agents). They use the same schema with two extras: their icon may also be a lucide:name reference from the bundled icon set, and the entry is stamped custom: true so it stays freely editable and removable in the UI.

Entry schema

The file has three top-level keys: schema (integer, currently 1), updatedAt (an ISO date), and agents (the entry array). Each entry follows this shape:

Flags

Capture

Some agents mint their own session id and print it to output instead of accepting one. For those, capture supplies: The spawn path requires both a compiled pattern and resume args, so a capture spec without resume does nothing.

Placeholder substitution

Substitution happens on every flag group. The known placeholders: An unknown placeholder is the one failure mode worth knowing about: there is no substitution lookup for it, so it passes through verbatim and reaches the CLI as a literal string. A typo like {UID} in a commit is therefore silent at runtime — which is why src/lib/agentsManifestFile.check.ts rejects unknown placeholders at check time (validation below).

Flag semantics and traps

Two conventions trip up new manifest entries, and both are guarded in the checks:
  • autoApprove applies to the interactive spawn as well as the headless one. A flag that only exists on a headless subcommand — Droid’s --auto is the shipped example, Crush’s --yolo the other — breaks every launch of that agent the moment the user turns Auto on. Droid’s entry therefore ships without autoApprove, and the guard in agentsManifestFile.check.ts rejects a well-meaning “add the yolo flag” edit that would reintroduce it.
  • session needs a CLI that accepts an id we supply. A --resume flag that only takes an id the CLI minted itself belongs in capture.resume instead, or nowhere — declaring it under flags.session passes Tempest’s id to a CLI that does not expect it.

The signed distribution channel

The manifest travels on two layers:
  1. The bundled floor. config/agents.json is imported at build time, so the agent list works instantly and offline, even before any network call.
  2. The signed remote patch. At startup, src/lib/remoteAgents.ts fetches both agents.json and its detached signature agents.json.minisig from
    and verifies the signature in Rust via the verify_minisign command against AGENTS_PUBKEY, the public key bundled in src-tauri/src/lib.rs (the minisign-verify crate; both legacy and prehashed signature formats are accepted). Only after verification is the raw file parsed, the minAppVersion gate applied, and the surviving entries merged by id onto the floor. A new agent added upstream via a PR shows up in existing installs this way — no app release needed.
Why is this the one signed file? Every entry carries a command that gets spawned and reaches a shell unquoted, plus the autoApprove flag that disables an agent’s own sandbox. An unsigned channel — reachable by a repo write, a merged PR, or a CDN cache-poisoning attack — would turn any of those into remote command execution on every client. On any verification failure the app keeps only its bundled agents; the fetch is deliberately silent about failures, because the bundled list already works.
Icon references are resolved two ways: bundled keys resolve to local assets, while file references are downloaded from Tempest’s own repo through jsDelivr and cached as data URLs so they survive offline starts (never a third-party host).

Key ceremony and publishing

1
Generate a signing key pair with minisign -G -p tempest-agents.pub -s tempest-agents.key. Keep the private key offline or as a CI secret.
2
Embed the public key. Paste the RW… line from tempest-agents.pub into AGENTS_PUBKEY in src-tauri/src/lib.rs and ship it in a release — the key is bundled in the app. While AGENTS_PUBKEY is empty, the channel stays disabled and apps use only bundled agents.
3
Sign and publish updates with minisign -S -s tempest-agents.key -m config/agents.json, which produces config/agents.json.minisig. Commit both files and push; clients verify and pick the update up on next startup, after the ~12h jsDelivr cache TTL or a cache purge.
At the repo root, the helper script sign-agents.ps1 performs the ceremony in one shot: it signs the manifest, verifies the result before committing anything, commits and pushes, then purges the jsDelivr cache for both files so clients see the update immediately.

The other config files, for contrast

agents.json

Fetched at runtime and minisign-verified. Carries spawned commands and flag templates — the only file that does.

models.json

Fetched at runtime but unsigned. Pure display data (model ids, labels, context sizes); nothing here is executed. See Chat models.

providers.json

Never fetched. Provider presets rewrite an API base URL and name the variable a key is exported into, so they ship bundled with each release — see provider presets.

Validation

src/lib/agentsManifestFile.check.ts runs under npm test and asserts things about the shipped file that are invisible at runtime. A malformed entry is dropped silently — the correct behavior for an untrusted downloaded manifest — which otherwise looks exactly like “my agent never showed up”. The checks make it a build failure instead:
  • every shipped entry survives sanitization (a dropped entry is an agent that silently never appears);
  • agent ids are unique;
  • every command is a bare-token regex match, and every name is non-empty;
  • each icon resolves either to a bundled key or to a real file in config/agent-icons/ (anything else degrades to the fallback glyph with no error);
  • capture patterns compile, and every capture spec carries resume args so it is usable;
  • every placeholder is a known one, since a typo reaches the CLI literally;
  • modelArgs actually reference {MODEL} and printArgs reference {PROMPT}, so slots that carry a value carry its placeholder;
  • downloadUrl is https;
  • and Droid’s entry must not declare autoApprove, because its --auto flag is exec-only and would break interactive launches.

Worked example

A complete entry for a new resumable agent:
Add the icon to config/agent-icons/, verify each flag against the CLI’s own --help, run npm test, and — because the channel is signed — expect the entry to reach existing installs once a key holder regenerates config/agents.json.minisig (or ship it in the bundled floor with the next release). Status detection options, including lifecycle hooks, are covered in Lifecycle hooks.