A voice agent has two kinds of state: the working copy that the editor writes, and an ordered history of published snapshots. Callers only ever reach a published snapshot. Saving in the editor does not change what a caller hears. The Versions screen exists to show that split as a state rather than as a toast, together with the two actions that change it: Publish and Restore.

The working copy and the published snapshot

The working copy lives in the agent’s agent_config row. Every field in the editor writes there — prompt, model, voice, turn detection, call flow, and the rest. It has no version number. Publishing copies the working copy into an immutable, numbered snapshot. agentVersions.status reports the relationship between the two: The console renders these as two tags: Live: v<n> or Never published, and Unpublished changes or Up to date. An agent with no published snapshot cannot be reached by callers at all — there is nothing for a call to resolve to.

Publishing: what gets snapshotted and when callers see it

agentVersions.publish takes { orgId, agentId, note? }. It snapshots the entire working copy definition, not a field selection and not a diff — the snapshot is self-contained, which is what makes restoring one later a complete operation. The optional note is free text; the console’s input caps it at 500 characters and sends nothing when it is left empty. The response drives what the screen says: The Publish button is only rendered while status.unpublished is true. When the working copy matches the live snapshot there is nothing to publish and the control is not offered. After either mutation the console refetches both status and list, so the history and the live tag update together.

Rollback: republishing an older definition

agentVersions.rollback takes { orgId, agentId, versionId }. Note that it identifies the target by the history row’s id, not by its version number. Rollback does not move the version pointer backwards and does not reopen an old number for writing. It republishes the older definition as a new snapshot at the next version number. The response carries both numbers:
  • rolled_back_to — the version whose definition was restored.
  • version_number — the new version that now holds it.
The console reports this as Rolled back to v<a>, published as v<b>. Version numbers only ever move forward, so the history stays a straight append-only log and you can roll back a rollback. In the history list, Restore only appears on rows whose status is not published. There is no point restoring the snapshot that is already live. A rollback can also come back with ready: false, for the same reason a publish can.

Reading the change list

status.changed and each history row’s changed array contain raw field names. The console maps them to the labels an operator recognises from the editor: Any field name without a mapping is shown verbatim. On the live-status card the list reads Changed since v<n>: …; on a history row it reads Changed: … for that snapshot.

Reading the side-by-side diff

The change list tells you which fields moved. Compare on a history row tells you how. The console serialises both definitions to indented JSON and runs a line-level longest-common-subsequence diff, then renders two columns:
  • Left, red-tinted — lines present in the selected snapshot’s definition and absent from the comparison target (deletions).
  • Right, green-tinted — lines present only in the target (additions).
  • Untinted lines are common to both. A greyed-out cell means that side has no line at that position.
The right-hand side is status.live, so a comparison is always “this snapshot versus what the console reports as current”. The header line above the diff reads No differences. or <n> changed lines. Line-level diffing is the reason this view beats the field list for prose changes: a reworded system prompt shows up as one field name in changed, but as the specific sentences that moved in the diff.

Publishing an agent that cannot run yet: missing provider credentials

Publishing validates that a snapshot exists and is well-formed. It does not require that every provider the snapshot names has usable credentials in your org. Those are two separate concerns, and TeleQuick lets the publish succeed so you are not blocked from versioning a configuration you are still wiring up. When the snapshot references a provider with no credential, publish and rollback return ready: false together with missing_providers. The console treats this as an error condition even though the mutation succeeded, and shows:
If missing_providers is empty the provider list is omitted from the message. The version is live either way: the pointer has moved, the history row exists, and incoming calls will resolve to it and fail on the missing credential. Add the credential for each named provider to clear the condition — the snapshot itself does not need to be republished for the credential to take effect. ready: false is reported for both mutations, so a rollback to an older snapshot that names a provider you have since removed lands in the same state.

Calls already in progress when you publish

Publishing swaps which snapshot is live. Be precise about what the screen tells you and what it does not:
  • publish returns the new version_number, unchanged, ready and missing_providers. It returns no information about calls in progress, and the console does not display any.
  • status reports which snapshot is live and whether the working copy has drifted from it. It is a statement about the agent’s configuration, not about any particular call.
So a green Published v<n>. Callers are getting it now. confirms that the pointer moved — not that any specific connected call has been re-read. To confirm end-to-end behaviour of a new version, place a fresh call after publishing rather than inferring it from a call that was already up. This is also the failure mode the screen was built to replace. Previously a save wrote the row and a toast told the operator the agent was “still answering with the old config” — a state, surfaced once, with no way to act on it. Now that state is the Unpublished changes tag, and the action is the Publish button next to it.

The agentVersions API

All four procedures take orgId and agentId. Notes on list: rows are the history, newest first as rendered. status is published for the live row and something else for every other row — the console keys the Live tag and the availability of Restore off that single value. published_at is preferred over created_at for the displayed timestamp. definition is the full snapshot, which is what makes client-side diffing possible without a second round trip. The console requests limit: 30.