Streaming
The server-sent stream events every agent run produces, and how to consume them.
Next release
The streaming operations answer 501 not_implemented until the agent runtime ships. The event
shapes below are final for contract 0.5.0, and the TypeScript types are available today.
Two operations stream: POST /v1/sessions/{session_id}/messages and GET /v1/runs/{run_id}/stream.
Both answer 200 with text/event-stream. Each event is an id: <seq> line and a data: <JSON>
line.
id: 1
data: {"type":"run_started","seq":1,"run_id":"…","at":"…","agent":{"name":"support","version":"1.4.0"},"release_id":"…","session_id":"…"}
id: 2
data: {"type":"token","seq":2,"run_id":"…","at":"…","text":"Your refund"}Every event has type, seq (also the SSE id), run_id and at.
Events are durable except token. Durable events are stored: their seq rises by exactly 1
within a run, and a replay returns every one of them. token events are best effort: a token
carries the seq of the message it belongs to and a delta that counts fragments within that
message, and it may be lost. message_snapshot events make the text whole: each one carries the
full text of the message so far, and you replace the message's text with it.
Event types
type | Carries | Render it as |
|---|---|---|
run_started | agent, release_id, session_id | A start marker |
step | index, kind | Progress |
token | text, delta | Append to the open message (best effort) |
message_snapshot | message_id, text, delta, attempt | Replace the message's text with text |
plan | items | A checklist |
task | task_id, title, status, progress | A task row |
tool_started | tool_call_id, tool | "Looking up …" |
tool_completed | tool_call_id, tool, outcome, duration_ms | Done or failed |
tool_outcome_unknown | tool_call_id, tool, reason | "Checking whether this happened": the tool's effect is unknown, it is not retried automatically, and it is reconciled |
question | question | A question card; answer with POST /v1/runs/{id}/resume |
approval_required | approval | An approval card; decide with POST /v1/approvals/{id} |
artifact | artifact | A link or preview |
citation | source | A footnote |
completed | usage, status, output | Final state and cost |
error | status, problem, usage | Final state with a problem object |
stream_gap | after_seq, reason | Nothing; reconnect from your last durable seq |
reconnect | after_seq, retry_ms | Nothing; reconnect after retry_ms with after=after_seq |
Skip event types you do not recognise. New types may be added in a minor version and are not a breaking change.
Reconnecting
Keep the seq of the last durable event you processed. To resume, call
GET /v1/runs/{run_id}/stream?after=<seq>: you receive every durable event with a larger seq,
and a message_snapshot for a message that is still being written.
- A stream lasts at most 15 minutes. Before closing it, the server sends
reconnect; reconnect afterretry_ms. stream_gapmeans a stored event could not be read: reconnect from your last durableseq.- If
afteris older than what is kept, the stream answers410withreplay_expired: read the run's current state instead of replaying it.
Typed events in TypeScript
import type { Schema } from "@sparq/client";
type StreamEvent = Schema<"StreamEvent">;
function handle(event: StreamEvent) {
switch (event.type) {
case "token":
appendText(event.text);
break;
case "message_snapshot":
replaceText(event.message_id, event.text);
break;
case "approval_required":
showApproval(event.approval);
break;
case "completed":
finish(event.usage);
break;
default:
// Unknown or unhandled types are skipped.
break;
}
}