Skip to main content
Prompts are code. When you hardcode a system prompt in a handler file, changing it means a full redeploy, you lose history, and rolling back means reverting a commit. PromptRegistry treats prompts as first-class, versioned artifacts backed by your existing StateStore. You can pin a route to a specific version, diff two versions line-by-line, and roll forward or backward entirely independently of code changes.

Setup

Create a PromptRegistry backed by any StateStore:
import { PromptRegistry } from 'anvil/prompt';
import { SqliteStateStore } from 'anvil/store';

const store = await SqliteStateStore.open(); // .anvil/state.db
const registry = new PromptRegistry(store);
For tests or one-off scripts, pass a MemoryStateStore instead — it requires no file I/O or native dependencies.

Registering prompts

Call registry.register(name, template, note?) to append a new immutable version. Version numbers start at 1 and increment automatically.
const v1 = await registry.register(
  'support-system',
  'You are a helpful customer support agent. Answer questions concisely and accurately.',
  'Initial version',
);
// → { version: 1, template: '...', note: 'Initial version', createdAt: 1718000000000 }

// Registering again creates v2 — v1 is never modified
const v2 = await registry.register(
  'support-system',
  'You are a friendly customer support agent. Answer questions concisely, accurately, and with empathy. Always end with "Is there anything else I can help you with?"',
  'Add empathy tone and closing question',
);
// → { version: 2, ... }
register returns a PromptVersion object:
FieldTypeDescription
versionnumberMonotonically increasing version number
templatestringThe prompt template (may contain {{placeholder}} variables)
notestring | undefinedOptional human-readable change description
createdAtnumberUnix timestamp (ms) when this version was created

Retrieving prompts

registry.get(name, version?)

Returns the latest version when version is omitted, or the specific version if provided. Returns undefined if the prompt or version does not exist.
// Get the latest version
const latest = await registry.get('support-system');

// Pin to a specific version
const pinned = await registry.get('support-system', 1);

registry.list(name)

Returns all versions for a named prompt as a PromptVersion[] array, oldest first.
const versions = await registry.list('support-system');
// → [{ version: 1, ... }, { version: 2, ... }]

registry.names()

Returns an array of all prompt names registered in the store.
const names = await registry.names();
// → ['support-system', 'onboarding-intro', 'safety-guardrail']

Diffing versions

registry.diff(name, from, to) returns a line-level diff between two versions. Use this in a deploy pipeline or admin UI to review prompt changes before promoting them.
const diff = await registry.diff('support-system', 1, 2);
// diff.from    → 1
// diff.to      → 2
// diff.added   → lines present in v2 but not v1
// diff.removed → lines present in v1 but not v2
PromptDiff fields:
FieldTypeDescription
fromnumberThe base version number
tonumberThe target version number
addedstring[]Lines added in the target version
removedstring[]Lines removed from the base version

Rendering templates

renderPrompt(template, vars) fills {{varName}} placeholders in a template string with values from a plain object. Missing variables are replaced with empty strings.
import { renderPrompt } from 'anvil/prompt';

const template = 'You are a support agent for {{company}}. Today is {{date}}.';

const rendered = renderPrompt(template, {
  company: 'Acme Corp',
  date: new Date().toDateString(),
});
// → 'You are a support agent for Acme Corp. Today is Mon Jun 10 2025.'
Placeholders support optional whitespace: {{ varName }} and {{varName}} are both valid.

Using a registry in an agent

Fetch the prompt template at startup, render it with runtime variables, and pass it as the agent’s system prompt.
1

Register your prompt (once, at setup time)

import { PromptRegistry } from 'anvil/prompt';
import { SqliteStateStore } from 'anvil/store';

const store = await SqliteStateStore.open();
const registry = new PromptRegistry(store);

await registry.register(
  'support-system',
  'You are a support agent for {{company}}. Be concise and empathetic. Today\'s date is {{date}}.',
  'Initial version with date and company vars',
);
2

Fetch and render the prompt in your agent definition

import { defineAgent } from 'anvil/agent';
import { renderPrompt } from 'anvil/prompt';

// Fetch the latest version (or pin with a version number)
const promptVersion = await registry.get('support-system');

export default defineAgent({
  name: 'support-agent',
  model: 'gpt-4o-mini',

  system: renderPrompt(promptVersion!.template, {
    company: process.env.COMPANY_NAME ?? 'Acme',
    date: new Date().toDateString(),
  }),

  async run(ctx) {
    return ctx.complete();
  },
});
3

Roll back without redeploying

Pin a different version number in registry.get() and restart the process — no code change required.
// Roll back to v1 by changing the version argument
const promptVersion = await registry.get('support-system', 1);

Reviewing changes programmatically

Run a diff in CI to surface prompt changes alongside code changes:
const history = await registry.list('support-system');
const lastTwo = history.slice(-2);

if (lastTwo.length === 2) {
  const diff = await registry.diff(
    'support-system',
    lastTwo[0].version,
    lastTwo[1].version,
  );
  console.log('Added lines:');
  diff.added.forEach((line) => console.log(' + ' + line));
  console.log('Removed lines:');
  diff.removed.forEach((line) => console.log(' - ' + line));
}
Store the PromptRegistry instance as a singleton module export so every route and agent shares the same registry and the same underlying StateStore connection.