---
description: Reference for reading Flue agent and workflow event streams over Durable Streams.
title: Streaming Protocol | Flue
image: https://flueframework.com/docs/og4.jpg
---

# Streaming Protocol

AI-generated, awaiting review [ View as Markdown](https://flueframework.com/docs/api/streaming-protocol/index.md) 

Flue implements the [Durable Streams](https://durablestreams.com) read protocol for agent-instance and workflow-run events. This page is for raw HTTP consumers. SDK users usually use `client.agents.stream()`, `client.runs.stream()`, or `client.runs.events()` instead.

## Stream routes

| Route                  | Purpose                                    |
| ---------------------- | ------------------------------------------ |
| GET /agents/:name/:id  | Read one agent instance stream.            |
| HEAD /agents/:name/:id | Read agent stream metadata without a body. |
| GET /runs/:runId       | Read one workflow-run stream.              |
| HEAD /runs/:runId      | Read workflow-run metadata without a body. |

Agent streams exist after the first admitted prompt for that instance. Workflow-run streams exist after workflow admission. Missing streams return `404`.

## Read modes

A plain `GET` performs a catch-up read and returns a JSON array of event payloads.

```
GET /runs/run_01JX...?offset=-1
```

Use `?live=long-poll` for one waitable read, or `?live=sse` for a continuous event stream. Live reads require `offset`.

```
GET /runs/run_01JX...?offset=0000000000000000_0000000000000005&live=long-poll
GET /runs/run_01JX...?offset=0000000000000000_0000000000000005&live=sse
```

Supported `live` values are `long-poll` and `sse`. Any other value, duplicate `offset` parameter, malformed offset, or live request without `offset` returns `400`.

A long-poll read waits up to 30 seconds for new data before returning `204`. An idle SSE connection receives `: heartbeat` comment frames every 15 seconds and a keep-alive control frame after each 30-second internal wait.

## Offsets

Offsets are opaque Durable Streams coordinates. Flue currently formats them as two zero-padded integer components, such as `0000000000000000_0000000000000005`. Consumers should treat them as opaque strings and pass back the latest returned value rather than parsing them.

| Offset          | Meaning                                                                                    |
| --------------- | ------------------------------------------------------------------------------------------ |
| \-1             | Read from the beginning of the stream.                                                     |
| now             | Read from the current tail. In live mode, wait for events appended after the current tail. |
| Returned offset | Resume strictly after that event.                                                          |

The SDK exposes a resume offset as `stream.offset`. It is batch-granular: it advances only after every event in the fetched batch has been delivered. Resuming from a checkpoint does not skip undelivered events, though it can re-deliver an in-flight batch. For per-event checkpoints on workflow-run streams use the event’s `eventIndex` (there it equals the stream sequence; agent streams restart `eventIndex` per prompt, so it is not an offset there); `flue logs --format ndjson` prints a per-event `offset` derived from it. `agents.send()` and `agents.prompt()` return an offset captured before that prompt is admitted, so reading from that offset yields that prompt’s events. `workflows.invoke()` returns the run ID plus a server-provided `streamUrl` and an `offset` captured at admission, so reading from that offset yields the run’s events from the start.

## Recent history with `tail`

`tail=N` modifies the `-1` start sentinel — “from the beginning, but at most the last N events.”

This extension applies to agent and workflow-run `GET` routes. It changes only the initial position: it has no effect with `offset=now` or a concrete offset, including the concrete offsets used when a client reconnects. A value larger than the stream returns its full history.

`tail` must occur once and be an integer greater than or equal to 1\. Zero, negative values, non-integers, and duplicate parameters return `400`. There is no upper cap; server read batching remains independent of the requested starting position.

```
GET /agents/support/ticket-42?offset=-1&tail=100
GET /runs/run_01JX...?offset=-1&tail=100
```

## Response headers

| Header             | Meaning                                                                                                                      |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| Stream-Next-Offset | Offset to use for the next read.                                                                                             |
| Stream-Up-To-Date  | true when the read reached the current tail.                                                                                 |
| Stream-Closed      | true when the stream is closed and no more events can arrive.                                                                |
| Stream-Cursor      | Cursor for long-poll continuation.                                                                                           |
| ETag               | Cache validator for catch-up and long-poll reads except offset=now.                                                          |
| Cache-Control      | no-store on catch-up reads, long-poll 200 responses, and HEAD; no-cache on SSE responses; absent on long-poll 204 responses. |

A conditional catch-up read with `If-None-Match` may return `304`. `offset=now` does not emit an ETag because its meaning is relative to the time of the request. `HEAD` returns stream metadata with the same stream headers and no body.

## Status behavior

| Status | Meaning                                                                                                                                                                                                                 |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 200    | Read returned event data. Catch-up and long-poll bodies are JSON arrays.                                                                                                                                                |
| 204    | Long-poll reached its 30-second timeout without new data.                                                                                                                                                               |
| 304    | Conditional read matched the current ETag.                                                                                                                                                                              |
| 400    | Invalid stream query.                                                                                                                                                                                                   |
| 404    | Stream does not exist or is not accessible. Agent streams use the stream\_not\_found error category; run streams use run\_not\_found. See the [Errors Reference](https://flueframework.com/docs/api/errors-reference/). |

A closed stream can still return stored history. Once caught up, its response includes `Stream-Closed: true`. Long-poll returns immediately for a closed, caught-up stream.

## SSE framing

SSE responses use named frames:

* `event: data` frames whose `data:` payload is a JSON **array** of event payloads. One frame may carry multiple events.
* `event: control` frames whose payload is a JSON object containing `streamNextOffset` and, depending on stream state, `streamCursor`, `upToDate`, and `streamClosed`. Flue emits a control frame after each data batch and as a keep-alive while an open stream stays idle.
* `: heartbeat` comment frames every 15 seconds to keep idle connections alive.

Consumers that only need event payloads can read `event: data` frames and ignore control and heartbeat frames, but should track `streamNextOffset` from control frames to resume correctly after a disconnect.

SSE is intended for live tailing, not browser caching. Flue sends `Cache-Control: no-cache` on SSE responses (matching the Durable Streams reference server) and does not rely on intermediary cacheability for stream reads.

## Docs Navigation

Current page: [Streaming Protocol](https://flueframework.com/docs/api/streaming-protocol/)

### Sections

* [Guide](https://flueframework.com/docs/getting-started/quickstart/)
* [Reference](https://flueframework.com/docs/api/agent-api/)
* [CLI](https://flueframework.com/docs/cli/overview/)
* [SDK](https://flueframework.com/docs/sdk/overview/)
* [Ecosystem](https://flueframework.com/docs/ecosystem/)

### Runtime

* [ Configuration ](https://flueframework.com/docs/reference/configuration/)
* [ Errors Reference ](https://flueframework.com/docs/api/errors-reference/)
* [ Agent API ](https://flueframework.com/docs/api/agent-api/)
* [ Provider API ](https://flueframework.com/docs/api/provider-api/)
* [ Routing API ](https://flueframework.com/docs/api/routing-api/)
* [ GitHub Channel ](https://flueframework.com/docs/api/github-channel/)
* [ Stripe Channel ](https://flueframework.com/docs/api/stripe-channel/)
* [ Notion Channel ](https://flueframework.com/docs/api/notion-channel/)
* [ Resend Channel ](https://flueframework.com/docs/api/resend-channel/)
* [ Shopify Channel ](https://flueframework.com/docs/api/shopify-channel/)
* [ Intercom Channel ](https://flueframework.com/docs/api/intercom-channel/)
* [ Zendesk Channel ](https://flueframework.com/docs/api/zendesk-channel/)
* [ Salesforce Marketing Cloud Channel ](https://flueframework.com/docs/api/salesforce-marketing-cloud-channel/)
* [ Slack Channel ](https://flueframework.com/docs/api/slack-channel/)
* [ Discord Channel ](https://flueframework.com/docs/api/discord-channel/)
* [ Teams Channel ](https://flueframework.com/docs/api/teams-channel/)
* [ Google Chat Channel ](https://flueframework.com/docs/api/google-chat-channel/)
* [ Linear Channel ](https://flueframework.com/docs/api/linear-channel/)
* [ Telegram Channel ](https://flueframework.com/docs/api/telegram-channel/)
* [ WhatsApp Channel ](https://flueframework.com/docs/api/whatsapp-channel/)
* [ Twilio Channel ](https://flueframework.com/docs/api/twilio-channel/)
* [ Messenger Channel ](https://flueframework.com/docs/api/messenger-channel/)
* [ Streaming Protocol ](https://flueframework.com/docs/api/streaming-protocol/)
* [ Events Reference ](https://flueframework.com/docs/api/events-reference/)

### Advanced

* [ Sandbox Adapter API ](https://flueframework.com/docs/api/sandbox-api/)
* [ Data Persistence API ](https://flueframework.com/docs/api/data-persistence-api/)