Product

One foreign key carries the whole product.

A chat stores agent_version_id, not agent_id. Point it at the agent and you have rebuilt a mutable Space. Point it at an immutable version and reproducibility, honest diffs, audit trails and safe upgrades all fall out for free.

What a version actually is

Five fields, hashed together into the body hash that gives the version its identity. Keys are sorted and JSON whitespace normalised before hashing, so cosmetic reordering never mints a version and a real edit always does.

FieldWhat it controls
system_promptThe instructions, loaded into a fresh context window on every single run.
tool_configWeb search on or off, maximum searches per run, and a domain allowlist.
output_schemaA JSON Schema, or null for free text. Validated by Coldcache on every provider, so a schema means the same thing everywhere.
model_settingsProvider, exact model id, temperature, max tokens, effort. Never a floating alias.
memory_policyoff, chat or scoped. Defaults to off. Changing it is a version bump, visible in the diff and in the chat header.

The version lifecycle

Four states, and only one of them can be edited.

01

Draft

The only mutable object. One per agent per user, created from stable when you hit Edit. No number, no history entry, runnable only in a scratch chat labelled not reproducible.

02

Published

Frozen. Gets the next integer and a changelog line. Retrievable, runnable and diffable forever. Publishing an identical body is refused with the version it duplicates.

03

Stable

A pointer, not a state. What a chat resolves when no version is named. Moved explicitly by a human, logged with actor and timestamp, and rolled back by moving it again.

04

Deprecated

Hidden from new chats. Still serves the chats already pinned to it, still readable, still diffable. Reversible. A signal to humans, never an execution control.

What happens to a running chat when you publish? Nothing. Open chats keep streaming against their pin, batch jobs holding a version id are unaffected, and nobody gets a banner. You are told how many chats are open on the previous version, with a link to fork any of them — an offer, never an action.
Replay mode

Hold the web still while you vary the prompt.

Live search is the enemy of a clean comparison. You rewrite the prompt, the answer changes, and two things moved at once. Every source a run cites is stored with a fetch timestamp and a content hash — replay hands the newer version that exact evidence instead of searching again.

ModeWhat it doesUse when
replayRe-serves the exact sources the original run read. The web is frozen.Isolating a prompt change. The default for version diffs.
refreshRe-runs search live for both sides, in the same minute.Comparing two versions on today's web.
liveA normal run. Only side B is fresh; side A is historical.Never, for diffs. Available for honesty.

Replay is a prompt construction, not a provider feature, so it behaves identically on Claude, ChatGPT and Gemini.

The run record

Every answer produces a record, not a scrollback entry. This is the unit you attach to a ticket, feed to a regression suite, or hand to a reviewer.

// GET /api/runs/r_4812c3
{
  "agent_version_id": "av_9f2c…",           // the pin — never resolved at read time
  "requested_model_id": "claude-opus-5",
  "resolved_model_id": "claude-opus-5",      // a substitution can never be silent
  "source_mode": "replay",
  "input_hash": "9f2c…a71b",
  "output_hash": "4de0…c882",
  "schema_valid": true,
  "citations": [{ "url": …, "content_hash": …, "fetched_at": … }],
  "latency_ms": 1204, "prompt_tokens": 1842, "cost_usd": 0.008,     // what the provider charged us
  "billed_usd": 0.01,   // what this run drew from your balance, displayed to you as €0.01
}

Read the full schema and the invariants that keep it honest in Chats and runs.

Pin an agent. Run it in a year. Get the same thing back.

Claude, ChatGPT and Gemini included — no provider account to open. Build one agent, publish v1, run it, then change the prompt and diff the two.