Open in
Prompt Revisions
Every change to an agent
A prompt revision is one version of an agent's prompt text. ThunderPhone records one every time the text changes, from every place that can change it: the dashboard builder, the copilot, the REST API, the MCP server, a version restore, an investigation fix, a deploy, a discarded draft, a duplicate or an import. Version history records deployed configurations only; prompt revisions also cover every draft edit in between, so a test run, a test conversation or a call can name the exact text it ran.
Revisions form lines. The agent's main line is its draft and deployed
prompt. Each A/B variant with its own prompt
has its own line, and a prompt that a replay run
tested without it ever being the draft is recorded as a candidate, off the
main line. A text that comes back later (a rule removed, then added back) is
a new revision linked to the one it replaced.
The agent iteration toolkit
These endpoints are part of the agent iteration toolkit: prompt revisions,
test conversations,
replay runs, prompt
review and
refactor, and
talk links. The toolkit is enabled per
organization while it is in beta. Without it, every toolkit endpoint answers
403 with code: "feature_not_enabled" and a next_step; ask
support to enable it.
Three rules run through the toolkit:
- The test split is hidden from API keys. Each test conversation belongs
to a dev split, for tuning, or a test split, for measuring a change once.
API keys and OAuth tokens (and so MCP clients) see a test-split item as
its id, split, source, status and counts only. People signed in to the
dashboard see everything. Prompt revisions are the agent's own
configuration, not call content, so this rule does not apply on this page.
In an organization with the toolkit, a call whose caller is in an
agent's test split is held out: the calls API and the MCP call tools
return it to API keys and OAuth tokens as its id, start time and
held_out: trueonly (see test conversations). Leave those calls out when tuning. dry_runquotes before anything is spent. Every endpoint that spends model or replay credits takesdry_run: trueand returns an estimate without running anything. Conversation jobs (generate, import, refresh) and replay runs also return aquote_id, and a real run must send it back: it is good for 15 minutes, for the same arguments only, and for one run. It is used up once, durably, as the run starts; a start that is refused or fails before then leaves it usable, and a run that fails after it is repeated with a new dry run for a newquote_id.max_cost_centsis a hard cap. A real run needs a spend cap in US cents. Before each request the run sets aside the most that request can cost and stops instead of sending one that does not fit, keeping what it already did. So a conversation job or replay run can stop with part of its cap unspent: leave about twice the estimate'slargest_reservation_centsof headroom above the expected cost. A run is charged what its requests really cost, not what it set aside. Prompt review and refactor need a cap for any call not answered from the cache, one that covers the dry run'sworst_case_cost_cents.
With OAuth, the toolkit's endpoints need these scopes: prompt review and
refactor agents:write (their dry runs too) and prompt revisions
agents:read; test conversations, conversation jobs and replay runs
testing:read to read and testing:write to start; talk links
calls:write. Without the scope the response is 403 with
error: "insufficient_scope".
Reading prompt revisions spends nothing, so these endpoints take neither
dry_run nor max_cost_cents.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/agents/{agent_id}/prompt-revisions | List an agent's prompt revisions, newest first |
GET | /v1/agents/{agent_id}/prompt-revisions/{revision_id} | Retrieve one revision with its full text |
Prompt revision object
{
"id": 4182,
"agent_id": 123,
"sha256": "9c1f0b6e2a…",
"source": "draft",
"actor": "mcp",
"author_name": null,
"author_staff": false,
"parent_id": 4177,
"variant_id": null,
"config_version_revision": null,
"created_at": "2026-10-19T17:02:11.204Z",
"text_length": 2214,
"rule_inventory": [],
"diff": "--- revision 4177\n+++ revision 4182\n@@ -3 +3 @@\n-Ask for their name and their phone number.\n+Ask for their name. Once they answer, ask for their phone number.\n"
}| Field | Type | Description |
|---|---|---|
id | integer | Revision id |
sha256 | string | SHA-256 of the text. Review a prompt returns the same value as prompt_sha256, and the MCP update_agent tool takes it as expected_prompt_sha256 |
source | string | draft (a draft edit), deploy, variant (an A/B variant's prompt), import, backfill (recorded from older version history), or candidate (a prompt a replay run tested) |
actor | string | Where the write came from: dashboard, zeus, api (API keys and OAuth), mcp, copilot, replay, or empty for writers with no surface |
author_name | string | null | The person who made the change, when a person did |
author_staff | boolean | true when a ThunderPhone staff member made the change |
parent_id | integer | null | The revision this one replaced in the same line |
variant_id | integer | null | The variant whose line this is, for source: "variant" |
config_version_revision | integer | null | The deployed version that carried this text, when one did |
text_length | integer | Characters in the text |
rule_inventory | object[] | The behaviors the prompt asks for, with stable ids, once a review has read this text; empty before that |
diff | string | Unified diff against the parent revision (against an empty text for the first) |
text | string | The full text. Only on the single-revision endpoint |
Agents created before prompt revisions existed get their history recorded
from their deployed versions (source: "backfill") the first time their
prompt changes.
List prompt revisions
curl 'https://api.thunderphone.com/v1/agents/123/prompt-revisions?source=draft&limit=20' \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"page = requests.get(
"https://api.thunderphone.com/v1/agents/123/prompt-revisions",
headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
params={"limit": 20},
).json()
for revision in page["results"]:
print(revision["id"], revision["source"], revision["actor"])Query parameters
| Param | Type | Description |
|---|---|---|
source | string | Only revisions from this source |
limit | integer | 1 to 100, default 20. Values outside the range are clamped |
offset | integer | Default 0 |
Returns a page, newest first. The revisions in it have no text; fetch one
to read it.
{
"count": 37,
"limit": 20,
"offset": 0,
"next_offset": 20,
"results": [{ "id": 4182, "source": "draft", "diff": "…" }]
}next_offset is null on the last page.
Retrieve a prompt revision
curl https://api.thunderphone.com/v1/agents/123/prompt-revisions/4182 \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"Returns the revision object with text.
Errors
| Status | Condition |
|---|---|
400 | An unknown source, or a limit or offset that is not an integer |
403 | feature_not_enabled: the organization does not have the agent iteration toolkit |
404 | The agent, or the revision, is not in this organization |
MCP
The MCP server exposes the list as
list_prompt_revisions.