Firstpass v0.2.1
DocsGuaranteeBenchmarksCLIGitHub

Docs / How Agents Talk to Models / Streaming

Part A2 · Beyond the core pathModule 8 of 13

Streaming

Add "stream": true to any request and the answer arrives token by token as Server-Sent Events instead of one big JSON blob at the end. Cost is identical — same tokens, same price — streaming changes delivery, not the bill. The gain is perceived latency: your UI can start rendering before the model finishes writing. The catch: anything that must inspect the whole output first — a JSON parser, a verification gate — still has to buffer the stream to completion.
Two horizontal timelines for the same response. Top row non-streaming: one long amber wait bar spanning the full generation time, then the complete response arrives at the end. Bottom row streaming: a short accent bar marking time-to-first-token where the user sees the first text, then tokens stream steadily to the same endpoint. Both rows end at identical wall-clock time, illustrating that streaming changes when you see tokens, not cost or total duration. Same response · same tokens · same cost — only delivery differs non-streaming streaming waiting… complete response held until generation finishes response arrives TTFT tokens streaming — render as they arrive user sees first text stream complete
Streaming changes when you see tokens, not how many — cost is identical either way.

The wire call changes by exactly one field. Point the same POST at the same endpoint, keep the same headers, add "stream": true:

bash
# same endpoint, same headers — one extra field
curl https://api.anthropic.com/v1/messages \
  -H 'content-type: application/json' \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H 'anthropic-version: 2023-06-01' \
  -d '{
    "model": "claude-haiku-4-5-20251001",
    "max_tokens": 1024,
    "stream": true,
    "messages": [{"role": "user", "content": "Scan orders.py for validation gaps."}]
  }'

The connection stays open and events arrive in a fixed sequence, each a pair of lines (event: <type> then data: <json>). Four matter most:

sse
# ① message_start — input_tokens known immediately; output_tokens not yet final
event: message_start
data: {"type":"message_start","message":{"id":"msg_01XA9g...","role":"assistant",
      "model":"claude-haiku-4-5-20251001","stop_reason":null,
      "usage":{"input_tokens":842,"output_tokens":1}}}

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}

event: ping
data: {"type":"ping"}

# ② content_block_delta — one or a few tokens per event; render as they arrive
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"I"}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'ll scan"}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" orders.py"}}

  ... more content_block_delta events ...

event: content_block_stop
data: {"type":"content_block_stop","index":0}

# ③ message_delta — final usage: output_tokens now settled; stop_reason set
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},
      "usage":{"output_tokens":312}}

# ④ message_stop — stream closed; safe to release the connection
event: message_stop
data: {"type":"message_stop"}
Key idea The usage object is split across two events in a stream. message_start reports input_tokens immediately — the model read your whole prompt before writing a word, so input is known at the start. message_delta reports output_tokens at the end, once the model finishes. The bill is the same as if you hadn't streamed: those token counts, those prices.
Common mistake Trying to parse or verify partial stream output. A JSON parser fails on a half-written object. A semantic gate can't rule on an answer the model hasn't finished. If your code needs the complete text — to validate, to parse structured output, to decide on tool calls — you must accumulate every content_block_delta and wait for message_stop before acting. Rendering can happen token by token; reasoning about the output cannot.
Under the hood Firstpass sits between your agent and the provider and forwards stream: true transparently — but it must buffer the full stream before the gate can run. Once the gate passes, Firstpass re-emits the buffered content as SSE to your client, so your agent still sees a stream. The practical effect: time-to-first-token is slightly higher through Firstpass (gate latency on top of generation), but the wire contract your agent code observes is identical. See how it works for the full flow.
Streaming changes when tokens arrive, not how many — cost is identical, time-to-first-token drops, but anything that reasons about the output still needs the complete stream first.