Here's a prompt. It has an id, a model, a set of variables typed with Zod, and a template. It's a normal export in a normal file, sitting next to the task that sends it.
The template is Mustache, so {{customerName}} fills in at resolve time and {{#plan}}...{{/plan}} blocks turn on and off. The variables schema types the fill-in call, so a missing or wrong-typed variable is a compile error, not a broken generation you find in production.
Resolve it, then hand it to the model
resolve() interpolates the variables and returns the finished text, plus the model and the version it came from. Spread toAISDKTelemetry() into your call and the generation is wired to observability in the same line.
Versioned on every deploy
Every deploy snapshots your prompts, so each one has a history the dashboard lists with the commit that shipped it. resolve() uses the current deployed version by default, and you can pin a specific one with { version: 3 } or a named { label: "current" } when you want to.
Override without a redeploy
Sometimes you need to change the wording or swap the model right now, before the next deploy. An override does that from the dashboard or the SDK, and resolve() returns it while it's active.
await prompts.createOverride("customer-support", { model: "claude-opus-4-8",});
One override is active per prompt per environment, and it's scoped to that environment, so a change you pin in staging doesn't touch production. Remove it and the next resolve is back on the deployed version.
The management SDK is the whole surface: prompts.list(), prompts.versions(), prompts.promote(), and createOverride / updateOverride / removeOverride / reactivateOverride. It runs inside a task or from a plain script with an API client.
Every generation traces back
Because resolve() carries the version and toAISDKTelemetry() attaches it, each generation records which prompt version produced it. The dashboard shows the generations and the cost, tokens, and latency per version, so when an output goes bad you can see the exact template behind it and, if you overrode the model, which model actually ran.
It's the same wiring chat.agent uses: store a resolved prompt with chat.prompt.set() and chat.toStreamTextOptions() folds the text, model, config, and telemetry into every turn.
Try it
Read the Prompts docs for the full guide: templates, variable schemas, dashboard overrides, and the management SDK.
