Skip to main content

Stream task events

Do this when you want to render or follow a run as it happens. Do not build a polling loop against GET /v1/agents/{agent_id}/tasks/{task_id} for this — the stream already replays everything from the beginning, so attaching late loses nothing.

Prerequisites

  • A task id and its agent id. See Start a task.
  • An API key.
  • A client that can hold an open HTTP response. curl -N works; curl without -N buffers and looks hung.

Choose an endpoint

Steps

Stream live

Each frame is a standard SSE event. The SSE event name and the payload’s event_type are the same dotted string, so a consumer can key on either:
The feed replays the task’s full durable history first, then tails live events, de-duplicating by event_id across the hand-off. It closes after task.completed, task.failed, or task.cancelled.

Drop token-level chunks

llm.call.chunk events are high volume — one per token. They are included by default. Turn them off when you only need structural progress:

Read history instead

Filter with ?event_type=tool.call. Chunks never appear here — they are stream-only and are not persisted.

Write the consumer correctly

Two rules keep a consumer from breaking when the vocabulary grows:
  1. Do not switch on every event name. Pass unknown types through. New event types are added without a version bump.
  2. Only three types are terminal: task.completed, task.failed, task.cancelled. Stop on those and nothing else.
To collapse a model call or a tool call into one UI element rather than four lines, group by part id: tool_call_id for tool events, {execution_id}:{iteration} for LLM events. A later event with the same part id replaces the earlier one.

Verify

Confirm the stream terminates on its own with a terminal event rather than hanging:
The command exits as soon as the terminal frame arrives. If it exits with no output, the task produced no terminal event within the feed’s limits — see below.

Troubleshooting

The connection opens and nothing appears. Without -N, curl buffers the response. Add -N. If frames still do not arrive, confirm the task actually dispatched: a task with execution_id: null never started a workflow and will never emit events. The stream closes after about 30 minutes with no terminal event. The feed has a 30-minute wall-clock limit so a stuck task does not tail forever. This is the feed giving up, not the task ending. Re-attach, or check GET .../tasks/{task_id} for the current status. A completed task streams its history and then hangs briefly before closing. Expected. The reader replays from the database, then attaches to the live tail and waits for a terminal event it may have already replayed. It closes on the replayed terminal event. Events are missing from history but were seen live. The event was published to the live stream but its database write failed; that failure is recorded and does not retry. The live tail is also best-effort in the other direction — a failed publish leaves the event in history only. History is the durable copy; prefer GET .../events when completeness matters. A blocked task never emits task.blocked. There is no such event type. A blocked task emits task.failed on the feed while its row reads blocked. Read failure_reason from the task to tell them apart.