# Rewinding Agent Channels

Rewind takes an [`agent` channel](https://getstream.io/chat/docs/go-golang/channel-features/#the-agent-channel-type) 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](https://getstream.io/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](#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.                                                                                                                       |

```go label="Go"
resp, err := client.Chat().RewindChannel(ctx, "agent", "conversation-1", &getstream.RewindChannelRequest{
	MessageID:             "prompt-2",
	ExpectedLastMessageID: "answer-3",
	Inclusive:             getstream.PtrTo(false),
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(resp.Data.Rewind.HiddenCountAtRewind)
```

```bash label="cURL"
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:

```json
{
  "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](https://getstream.io/chat/docs/go-golang/agent-channel-fork/) 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 `limit` and still have a `next` cursor. `latest_replies` leaves hidden replies out.
- **Quotes.** A message that quotes a hidden message keeps its `quoted_message_id`, but is returned without the `quoted_message` object. 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_message` or `parent_message`.
- **Message history.** The edit history of a hidden message is not returned.
- **Offline sync.** `/sync` drops the events of hidden messages and replays `channel.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 no `message.deleted` over 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_count` on the channel and `reply_count` on 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](https://getstream.io/chat/docs/go-golang/exporting-channels/) 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](https://getstream.io/chat/docs/go-golang/agent-channel-fork/) whose history is still being copied. Rewind it once `channel.forked` arrives or its `fork.status` is no longer `backfilling`. |

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/go-golang/agent-channel-rewind/](https://getstream.io/chat/docs/go-golang/agent-channel-rewind/).