Stream Chunk Protocol
Updated 10 minutes ago • May 22, 2026
The stream protocol is defined in @kortyx/stream as StreamChunk.
Core chunk types
sessionstatustext-starttext-deltatext-endmessagestructured-datainterrupttransitiondoneerror
Typical client loop
Structured Data
Simple mental model:
text-*is for text you show as textstructured-datais for objects you render as UI state
Examples:
- email drafts
- job tables
- growing result lists
- progress panels
- validation results
Structured chunk shape
Example incremental chunk:
Example final chunk:
Field meanings:
streamId: stable identity for one logical structured streamdataType: app-defined routing labelkind: how the client should apply this updateschemaIdandschemaVersion: optional schema metadataid: optional app identifiernode: workflow node that emitted the payload
kind values
set: set one field atpathappend: appenditemsto an array atpathtext-delta: appenddeltato a string atpathfinal: replace the whole object withdataand mark the stream done
That is the full public mental model. Most clients should key by streamId and apply chunks in arrival order.
Path contract
pathmeans a dot-separated location inside the object being built, such asdraft.body- dotted paths are valid in raw
structured-datachunks, manualuseStructuredData(...)calls, anduseReason({ structured: { fields } }) - numeric path segments target array indexes, such as
sections.0.body useReason({ structured: { fields } })also accepts*as a single-segment pattern and emits concrete paths, such asassessment_points.commercial_resilience.criteria_label- one
streamIdrepresents one logical object; if multiple fields in that object stream incrementally, they all use the samestreamId - use separate
streamIdvalues only when you are building separate objects
Reducer guarantees
applyStructuredChunk(...) is the canonical reducer for this protocol.
setwrites the value atpathappendappends to an existing array atpath, or creates that array when the path is unsettext-deltaappends to an existing string atpath, or creates that string when the path is unsetfinalreplaces any previously accumulated partial object completely and becomes the source of truth- no chunk is allowed after
finalfor the samestreamId
Runtime enforcement:
- invalid empty or malformed paths are rejected
- impossible container-shape conflicts are rejected
appendon a non-array target is rejectedtext-deltaon a non-string target is rejected- a chunk for a different
streamIdcannot be reduced into existing state for another stream
Producer expectations:
- emit chunks for a given
streamIdin order - use non-empty dot-separated paths or single-segment
*patterns inuseReason(... structured.fields ...) - do not rely on partial chunks being validated against the final output schema
Good to know:
useReason(...)validates the final object withoutputSchema, but incremental structured chunks are enforced only at the path and operation level. ManualuseStructuredData(...)calls can add optional schema checks fordata,value, or appended items.
Recommended React helper
For React clients, prefer useStructuredStreams() from @kortyx/react.
That gives you:
items: stable ordered structured piecesbyStreamId: map-style access for render logicget(streamId): direct lookupclear(): reset live state between runs
Good to know:
useChat()in@kortyx/reactbuilds on top of this and also handles assistant text, interrupts, and message history. UseuseStructuredStreams()directly only when you want custom UI without the full chat abstraction.
Advanced: low-level reducer
If you are outside React or want the raw protocol reducer, use applyStructuredChunk(...) from kortyx/browser.
applyStructuredChunk(...) returns:
data: current accumulated objectstatus: "streaming" | "done"streamIddataType
If you already have an array of structured chunks, you can also use reduceStructuredChunks(...).
How useReason({ structured }) uses this protocol
When you call useReason(...) with structured, there are two main modes.
Final-only mode
If you do not configure structured.fields, Kortyx emits one structured-data chunk with kind: "final" after the model output validates.
Incremental mode
If you set structured.fields, Kortyx can turn selected parts of the streamed JSON into deterministic structured updates before the final object arrives.
Example:
In that mode:
- the model still returns JSON for the full schema
- Kortyx watches the stream and emits early structured updates for declared fields
- all updates from that reasoning call share one
streamId - the final validated object still arrives as
kind: "final"
Current limits:
setfield paths- string field paths as
text-delta - array field paths as
append *wildcard patterns that match one object key or array index segment- non-interrupt flows only
The raw structured-data protocol and useReason(... structured.fields ...) both support dotted path values. useReason(...) extracts those paths conservatively from streamed model JSON, resolves wildcard matches to concrete paths in emitted chunks, and still treats the final validated object as the source of truth.
If a field should appear once and stay stable, it is often simpler to emit it from node logic with useStructuredData({ kind: "set", ... }).
Consuming text and structured data together
Many clients render both channels at once: text for conversation and structured data for UI state.
Notes
doneis terminal for a stream runerrormay be followed bydonesessionhelps clients persist conversation identity