Skip to main content
Aegra streams agent execution to clients using Server-Sent Events (SSE). This gives you real-time token-by-token output, tool call updates, and state changes as they happen.

Quick example

Stream modes

Control what data you receive by setting stream_mode on the run. You can pass a single mode as a string or multiple modes as a list.
debug and updates are used internally on every stream for checkpoint tracking and interrupt detection, but their events are only sent to the client if you explicitly request them in stream_mode. When updates is not requested, only interrupt events (human-in-the-loop) are forwarded — remapped as values events for compatibility.

Custom streaming

Send custom data from inside graph nodes using get_stream_writer():
Receive custom events by including custom in the stream modes:

Using multiple modes

When multiple modes are active, the event field tells you which mode each chunk comes from.

Event types

During streaming, you’ll receive these event types:

Serialized graph values

Graph state and streamed events are converted to JSON-compatible values before they are sent over SSE or stored in the run output. Dataclasses are serialized recursively by field, and common Python values use these representations: Nested values inside Pydantic or LangChain model dumps are normalized using the same rules. Mapping values are serialized recursively, and non-JSON-native keys such as UUID or Path are converted to JSON property names with the same scalar rules. Key collisions after conversion raise a serialization error.

Streaming endpoints

Create and stream

The most common pattern — create a run and stream its output in one call:
This calls POST /threads/{thread_id}/runs/stream under the hood.

Stream an existing run

If you created a background run, you can stream it later:
This supports reconnection via the Last-Event-ID header. If the connection drops, the client can reconnect and receive events from where it left off.

Wait for completion

If you don’t need streaming but want to wait for the result:
This calls POST /threads/{thread_id}/runs/wait and returns the final output.

Subgraph streaming

If your graph uses subgraphs, you can stream events from them too:

Disconnection behavior

By default, when a client disconnects during streaming, the run is cancelled. You can change this:
The server sends SSE keepalive comments (: heartbeat) every KEEPALIVE_INTERVAL_SECS (default 5s, truncated to whole seconds, minimum 1s), so idle proxies (Nginx 60s, AWS ALB, Cloudflare) won’t drop long-running silent agents — for example, a graph node holding an upstream WebSocket without emitting events. The wire-format matches LangGraph Platform, so existing SSE clients (the LangGraph SDK, browser EventSource, httpx-sse) silently ignore these lines per the W3C spec. Only a real client disconnect — not an idle proxy timeout — triggers automatic cancellation.

Background runs

For long-running tasks, you can create a run in the background and check on it later:

Cancelling runs

Cancel or interrupt a running execution:
Cancellation is asynchronous while a live worker owns the run. The cancel response may therefore still show pending or running; poll, join, or stream the run until it reaches a terminal status. Use the REST endpoint with wait=1 when the caller needs the request to wait briefly for the worker to settle.

SSE reconnection

Aegra stores streaming events in a replay buffer (Redis Lists when REDIS_BROKER_ENABLED=true, in-memory list in dev mode) for replay. If your connection drops:
  1. Track the last event ID you received
  2. Reconnect to GET /threads/{thread_id}/runs/{run_id}/stream with Last-Event-ID header
  3. You’ll receive all events from where you left off
Events are retained for 1 hour after the run completes.
In production deployments, SSE events are delivered via Redis pub/sub from workers — the client’s SSE connection and the worker executing the run can be on different instances. See the worker architecture guide for details on how this works.

Agent Protocol v2 event streaming

The latest LangGraph JS and Python SDKs (including @langchain/langgraph-sdk and the Vue/React useStream() composables) speak a newer streaming protocol with a dedicated event envelope and a content-block message model. Aegra serves this protocol natively.
v2 streaming is on by default — it’s a new endpoint set the SDK targets and has no v1 to break; the legacy runs/stream endpoints are unchanged. FF_V2_EVENT_STREAMING is a kill switch: set it to false to disable v2 serving (requests return 503 with an enable hint) and roll back without a redeploy. The endpoints also require a langgraph / langchain-core new enough to emit native v3 events; the server returns 503 with an upgrade hint if the runtime is too old.

Using the SDK

The stock LangGraph SDK drives both endpoints for you. Streaming is thread-scoped: open the stream, start a run, and consume the events:
The same flow backs the Vue/React useStream() composables.

Endpoints

The stream is scoped to the thread, not a run — you open it, then issue run.start, and the events of whatever run executes flow through. The client mints the thread id, and run.start creates the thread if it doesn’t exist yet.

Starting a run

Streaming events

The SSE filter is a POST body listing the channels you want — no run id. Pass since (the last seq you saw) to resume after a dropped connection.
The client reads each frame’s data: line — a protocol event envelope. seq is the cursor you echo back as since; event_id dedups across reconnects; params.data is the payload and params.namespace the subgraph path:

Channels

values, updates, messages, tools, lifecycle, input, checkpoints, tasks, and custom (plus custom:<name>). Message streams arrive as content-block events (message-startcontent-block-deltamessage-finish); lifecycle reports started / completed / failed / interrupted.

Human-in-the-loop interrupts

When you subscribe to the input channel, a graph interrupt arrives as an input.requested event. Its params.data includes the interrupt id and two identical content fields:
payload is the Agent Protocol field consumed by the JavaScript SDK and Vue/React useStream() integrations. value mirrors it for current Python SDKs, including ts.interrupts[].value. If the source interrupt has no value, both content fields are omitted; explicit null, empty, and falsy values are preserved under both fields.
This first release covers the HTTP SSE + commands path the JS/Python SDKs use, verified end-to-end against the stock langgraph-sdk. The WebSocket transport and the agent.getTree / state.fork / subscription.* commands are not yet implemented; SSE filtering via the POST body covers the useStream() path without them.