Rewinding Agent Channels
Rewind takes an agent channel back to one of its top-level messages. Every message created after that message is hidden from every read, and the conversation continues from there. With inclusive, the message itself is hidden too. This is how an agent backend lets a user regenerate an answer or edit an earlier prompt.
Rewound messages are hidden, not deleted. They cannot be restored.
Rewind is in beta. It works on agent channels only, and Stream enables it per app: contact support to turn it on. Until it is enabled, the endpoint returns 403.
Who can rewind
Rewind is a server-side operation. It needs the RewindChannel permission, which only server-side requests and users with the admin role have. Users and guests cannot rewind an agent channel, not even one they created.
On a server-side request, user_id names the user the rewind is attributed to. It is optional.
Rewind a channel
| Field | Required | Description |
|---|---|---|
message_id |
Yes | The top-level message to rewind to. It must be in the channel, visible, not deleted and not a reply. Every message created after it is hidden. |
expected_last_message_id |
Yes | The newest visible message your backend saw. The rewind fails with 409 rewind_conflict when it is no longer the newest one (see Concurrent changes). |
inclusive |
No | When true, message_id is hidden too and the message before it becomes the newest visible message. Defaults to false. |
user_id |
No | Server-side only. The user the rewind is attributed to. |
var response = chat.rewindChannel("agent", "conversation-1", RewindChannelRequest.builder()
.messageID("prompt-2")
.expectedLastMessageID("answer-3")
.inclusive(false)
.build()).execute();
System.out.println(response.getData().getRewind().getHiddenCountAtRewind());curl -X POST "https://chat.stream-io-api.com/channels/agent/conversation-1/rewind?api_key=${API_KEY}" \
-H "Authorization: ${SERVER_TOKEN}" \
-H "stream-auth-type: jwt" \
-H "Content-Type: application/json" \
-d '{"message_id": "prompt-2", "expected_last_message_id": "answer-3", "inclusive": false}'The response carries the channel after the rewind and the rewind itself:
{
"rewind": {
"id": "8d1f0c7e-3a52-4b8e-9f61-2c4d7b0e5a19",
"message_id": "prompt-2",
"inclusive": false,
"hidden_count_at_rewind": 2,
"created_at": "2026-11-03T10:15:42.128Z"
},
"channel": { "cid": "agent:conversation-1", "...": "..." },
"duration": "18.42ms"
}hidden_count_at_rewind is the number of messages this rewind hid that were not hidden already.
Concurrent changes
expected_last_message_id protects the rewind from changes your backend did not see. If a message was sent, or another rewind landed, after you read the channel, the newest visible message is no longer the one you expected and the rewind fails with 409 and reason rewind_conflict. The error's exception_fields.last_message_id carries the actual newest visible message. Reload the channel, decide again, and retry.
Rewinding repeatedly
A channel can hold at most 100 separate hidden ranges. Overlapping and adjacent ranges count as one, so rewinding to the same message again and again, as a "regenerate" button does, never reaches the limit. Only a rewind that would add a 101st separate range fails, with reason too_many_rewinds.
Each rewind can only target a message that is still visible, so the visible history is always a single line. To keep the history that a rewind would hide, fork the channel first.
What a hidden message does
A hidden message is treated as if it did not exist, everywhere a client can see it:
- Reads. It is left out of the channel's messages, pagination, pinned messages, replies and search. Getting it by id returns
404. - Unread counts. The rewind takes the hidden messages out of the members' channel unread counts.
- Threads. A thread whose parent message is hidden is treated as missing by Query Threads and Get Thread. A Query Threads page can therefore hold fewer threads than
limitand still have anextcursor.latest_repliesleaves hidden replies out. - Quotes. A message that quotes a hidden message keeps its
quoted_message_id, but is returned without thequoted_messageobject. Sending, updating or scheduling a new message that quotes a hidden message is rejected, as for a quote of a message that does not exist. - Reminders. A reminder on a hidden message is kept and returned without its
message. When it is due, it sends no push, event or webhook. - Drafts. A draft whose quoted or parent message is hidden is kept and returned without
quoted_messageorparent_message. - Message history. The edit history of a hidden message is not returned.
- Offline sync.
/syncdrops the events of hidden messages and replayschannel.rewound, so a client that was offline during the rewind removes the messages it already holds. - Updates. Updating a hidden message returns
404. - Deletes. A hidden message can still be deleted, soft or hard, by a server-side request or by the message's own author, so that you can honor an erasure request. Any other user, an admin included, gets
404. A soft-deleted hidden message stays hidden. The delete sends nomessage.deletedover the WebSocket and no push. Webhooks still receive it. - New messages. A message sent after a rewind is always placed after the hidden messages, so it can never fall into the hidden range.
Known limitations
message_counton the channel andreply_counton a parent message still count hidden messages.- Thread unread counts are not recomputed after a rewind.
- A member's last read message can point to a hidden message.
- A channel export includes hidden messages, with nothing marking them as hidden.
- For a moment after a rewind, a read can still return the messages it just hid.
Events
| Event | Sent to | Description |
|---|---|---|
channel.rewound |
Clients watching the channel, and webhooks | Carries channel (after the rewind), rewind and user. Clients should reload the channel's messages when they receive it. It never carries a hidden message, and the range of hidden timestamps is not exposed. |
notification.channel_rewound |
Connected clients of members who are not watching the channel, WebSocket only | Carries channel, rewind and the member's unread counts after the rewind, so a channel list can refresh its preview. It is sent only when read_events is enabled on the channel type, which it is on agent by default, and to the first 100 members. |
Neither event is sent as a push notification.
Errors
| HTTP | Code | exception_fields.reason |
When |
|---|---|---|---|
403 |
17 | Rewind is not enabled for the app, or the caller does not have the RewindChannel permission. |
|
400 |
4 | The channel is not an agent channel, or message_id is a reply, deleted, hidden or not in the channel. |
|
400 |
4 | too_many_rewinds |
The rewind would give the channel more than 100 separate hidden ranges. exception_fields.limit carries the limit. |
400 |
4 | nothing_to_rewind |
message_id is the newest visible message and inclusive is not set, so nothing would be hidden. |
409 |
125 | rewind_conflict |
The newest visible message is not expected_last_message_id. exception_fields.last_message_id carries the actual one. |
409 |
126 | rewind_point_ambiguous |
Another visible message has the same created_at as message_id, so the two cannot be told apart. |
409 |
131 | fork_in_progress |
The channel is a fork whose history is still being copied. Rewind it once channel.forked arrives or its fork.status is no longer backfilling. |