Quick example
Stream modes
Control what data you receive by settingstream_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 usingget_stream_writer():
custom in the stream modes:
Using multiple modes
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:POST /threads/{thread_id}/runs/stream under the hood.
Stream an existing run
If you created a background run, you can stream it later: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: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: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 whenREDIS_BROKER_ENABLED=true, in-memory list in dev mode) for replay. If your connection drops:
- Track the last event ID you received
- Reconnect to
GET /threads/{thread_id}/runs/{run_id}/streamwithLast-Event-IDheader - You’ll receive all events from where you left off
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: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. Passsince (the last seq you saw) to resume after a dropped connection.
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-start → content-block-delta → message-finish); lifecycle reports started / completed / failed / interrupted.
Human-in-the-loop interrupts
When you subscribe to theinput 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.