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.
| Field | What it controls |
|---|---|
| system_prompt | The instructions, loaded into a fresh context window on every single run. |
| tool_config | Web search on or off, maximum searches per run, and a domain allowlist. |
| output_schema | A JSON Schema, or null for free text. Validated by Coldcache on every provider, so a schema means the same thing everywhere. |
| model_settings | Provider, exact model id, temperature, max tokens, effort. Never a floating alias. |
| memory_policy | off, 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.
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.
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.
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.
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.
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.
| Mode | What it does | Use when |
|---|---|---|
| replay | Re-serves the exact sources the original run read. The web is frozen. | Isolating a prompt change. The default for version diffs. |
| refresh | Re-runs search live for both sides, in the same minute. | Comparing two versions on today's web. |
| live | A 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.