edit_of_message_id, ignores branch,
and never calls the branch route keeps exactly today’s behavior: the same
payloads, the same insertion-ordered transcript.
1. Revise a message
Take a normal turn, and addedit_of_message_id naming the user message the
new text replaces. message is required with it.
- Stores the new text as a sibling version of the target message, not as a new message at the end of the transcript.
- Leaves the superseded exchange out of the history the model sees, so the reply answers the revised message alone.
- Streams the reply exactly like any other turn, over the same Server-Sent Events frames.
- Serves the new branch from then on.
conversation frame at the start of the stream carries user_message_id,
the id of the message the turn just stored. Keep it if you want to offer a
second revision without refetching the conversation.
Only a user message can be revised. An assistant message target returns 400.
2. Render the version switcher
Fetch the conversation as usual. Every message now carriesparent_message_id, and a message that has sibling versions also carries
branch.
Draw
branch as a version switcher under the message: the pair index and
count reads as 2/2, and the previous and next controls address
siblings[index - 2] and siblings[index]. Absence of the field is the whole
“no fork here” signal, so branch on presence rather than counting anything
yourself.
The transcript a fetch returns is the branch the conversation currently
serves, not every version. Superseded versions are reachable through
branch.siblings and are kept in full by the conversation export, which is an
audit artifact and stays version-complete.
3. Switch versions
Post the id of any message on the branch you want served. The server resolves that message’s ancestry, follows the newest reply from there, records the selection, and returns the whole conversation detail payload for that branch.conversation, messages, truncated), already assembled for the selected
branch. One round trip both switches and renders, so you never preload a tree
and never reconcile an optimistic swap.
What persists
The selection is stored on the conversation, not in your client:- A later fetch returns the selected branch.
- The next turn continues the selected branch, and its messages are stored there.
- A reload, a second device, or a different client of the same conversation all see the branch that was selected last.
Errors
Two client-side consequences follow from the last two rows. A turn cannot be
cancelled, so disable the revise action while a turn streams rather than
sending into a
turn_in_progress refusal. And a confirmation gate must be
resolved before a revision is accepted, so let the user answer the gate first.
The branch route takes no turn claim, so a switch and a running turn are not
serialized against each other. Disable the switcher while a turn streams.
Compatibility
Both new fields and the new route are additive:edit_of_message_idis optional on the turn request.parent_message_idis new on every message; treat it as optional if you parse responses strictly and want to keep reading older stored payloads.branchis present only at a fork.- The branch route is new. Not calling it means the conversation keeps serving the branch its last edit created, which for a conversation with no edits is the original single thread.