Skip to content
Docs navigation: System Zero / Streaming

Streaming

DocsSystem ZeroStreaming Updated

A stream sends changing state from your server and returns structured decisions over a WebSocket connection. Append new text or array items, replace state, and read the decisions that cover accepted changes.

Connect

Streaming is server to server only. Use Node 22 or later or Python 3. Browsers cannot set the Authorization header on the WebSocket upgrade. Keep the key in the server process.

Use the API origin with WSS and /v1/system-zero/stream. Authenticate on the upgrade with Authorization: Bearer <key>. Request only system-zero.stream.v1 in Sec-WebSocket-Protocol.

Use text JSON messages. Send no browser Origin, URL credential, cookie authentication, compression, binary data, query or trailing slash.

Message flow

Read hello and its limits, then send start before its deadline. Questions, routing, scheduling and the cache key stay fixed for the connection.

Use append_text for string state, append_items for array state, or replace for a complete new state. append_attachments changes only the inline attachment list. Each accepted change receives ack.

With on_change, changes schedule decisions. With on_request, send decide for the current accepted version and read scheduled. A decision embeds the ordinary HTTP reply. Its covered_event_ids identifies the changes included in that dispatch.

Answer each JSON ping with pong and the same nonce within 10000 milliseconds of the completed ping write. Native WebSocket controls are separate. A missed pong closes with 1013 and reason slow_reader.

Idle = 120 seconds with no client application message and no decide in flight; the clock restarts when a decide settles; heartbeats do not count. The client ends normally with WebSocket close code 1000. There is no client goodbye or client ping JSON message.

A server close can send goodbye first. Durable stream resume is unsupported. Reconnect and replay the full desired state with the same per-state idempotency key and byte-identical request.

Client messages

start

idempotency_key
Optional. string. Minimum length: 1.
type
Required. "start"
event_id
Required. "evt_1"
request
Required. Start request. No extra fields.
decision_mode
Required. "on_change" or "on_request"
min_interval_ms
Optional. integer. Minimum: 0. Maximum: 60000. Default: 0.

append_text

idempotency_key
Optional. string. Minimum length: 1.
type
Required. "append_text"
event_id
Required. string
text
Required. string. Minimum length: 1.

append_items

idempotency_key
Optional. string. Minimum length: 1.
type
Required. "append_items"
event_id
Required. string
items
Required. array of JSON value. Minimum items: 1. Maximum items: 4096.

append_attachments

idempotency_key
Optional. string. Minimum length: 1.
type
Required. "append_attachments"
event_id
Required. string
attachments
Required. Inline attachment array. Minimum items: 1.

replace

idempotency_key
Optional. string. Minimum length: 1.
type
Required. "replace"
event_id
Required. string
state
Required. string, object or array
attachments
Optional. Inline attachment array. Maximum items: 16.

decide

type
Required. "decide"
state_version
Required. integer. Minimum: 1. Maximum: 100000.

pong

type
Required. "pong"
nonce
Required. integer. Minimum: 1. Maximum: 9007199254740991.

Server messages

hello

type
Required. "hello"
stream_id
Required. string
seq
Required. integer. Minimum: 1. Maximum: 9007199254740991.
protocol
Required. "system-zero.stream.v1"
request_id
Required. string. Minimum length: 1. Maximum length: 128.
limits
Required. Advertised limits. No extra fields.
rate_limit
Required. Rate limit snapshot or null

ack

type
Required. "ack"
stream_id
Required. string
seq
Required. integer. Minimum: 1. Maximum: 9007199254740991.
event_id
Required. string
state_version
Required. integer. Minimum: 1. Maximum: 100000.
state_bytes
Required. integer. Minimum: 0.
synthesized_body_bytes
Required. integer. Minimum: 0. Maximum: 3665920.
scheduled
Required. boolean

scheduled

type
Required. "scheduled"
stream_id
Required. string
seq
Required. integer. Minimum: 1. Maximum: 9007199254740991.
state_version
Required. integer. Minimum: 1. Maximum: 100000.
in_flight
Required. boolean

ping

type
Required. "ping"
stream_id
Required. string
seq
Required. integer. Minimum: 1. Maximum: 9007199254740991.
nonce
Required. integer. Minimum: 1. Maximum: 9007199254740991.

decision

type
Required. "decision"
stream_id
Required. string
seq
Required. integer. Minimum: 1. Maximum: 9007199254740991.
state_version
Required. integer. Minimum: 1. Maximum: 100000.
covered_event_ids
Required. array of string. Maximum items: 4096. Items are unique.
reply
Required. HTTP decision reply. No extra fields.
request_id
Required. string. Minimum length: 1. Maximum length: 128.
http_status
Required. 200
server_timing
Required. string or null
latency_ms
Required. number or null. Minimum: 0.
rate_limit
Required. Rate limit snapshot or null
queue_ms
Required. number. Minimum: 0.
decide_ms
Required. number. Minimum: 0.

error

type
Required. "error"
stream_id
Required. string
seq
Required. integer. Minimum: 1. Maximum: 9007199254740991.
phase
Required. "protocol" or "decide" or "lifecycle"
state_version
Required. integer. Minimum: 0. Maximum: 100000.
event_id
Required. string or null
covered_event_ids
Required. array of string. Maximum items: 4096. Items are unique.
http_status
Required. integer. Minimum: 400. Maximum: 599.
envelope
Required. HTTP error envelope. No extra fields.
retry
Required. Retry fields. No extra fields.
rate_limit
Required. Rate limit snapshot or null
server_timing
Required. string or null
fatal
Required. boolean

goodbye

type
Required. "goodbye"
stream_id
Required. string
seq
Required. integer. Minimum: 1. Maximum: 9007199254740991.
reason
Required. "idle" or "max_lifetime" or "auth" or "policy" or "shutdown" or "maintenance" or "overload" or "slow_reader" or "lease_lost" or "store_unavailable" or null
state_version
Required. integer. Minimum: 0. Maximum: 100000.
last_decision_state_version
Required. integer. Minimum: 0. Maximum: 100000.
resume
Required. object. No extra fields.

Shared fields

State is a string, object or array. An object changes through replace. Each decision follows the ordinary decide API's validation, availability, billing and limits. An ack accepts state into connection memory; it grants no input availability and confirms no decision, charge or durable save.

Question names use at most 64 ASCII letters, digits and underscores, starting with a letter or underscore. Attachment names use at most 64 ASCII letters, digits, underscores or hyphens, starting with a letter. Attachment names must be unique.

If start omits prompt_cache_key, the server uses stream_id. For byte-identical replay across connections, send an explicit stable prompt_cache_key. Do not combine route committee with mode deliberate.

Omitting replace.attachments retains the current list. An empty list removes every attachment. Text and array appends keep attachments unchanged. Inline data has no separate MIME field.

Start request

model
Required. string. Minimum length: 1. Maximum length: 200.
state
Required. string, object or array
questions
Required. Named questions. Minimum fields: 1. Maximum fields: 1024.
attachments
Optional. Inline attachment array. Maximum items: 16.
prompt_cache_key
Optional. string. Minimum length: 1. Maximum length: 256.
route
Optional. "standard" or "committee"
mode
Optional. "standard" or "deliberate"

Boolean question

type
Required. "boolean"
instructions
Required. nonempty string, object or array
criteria
Optional. object. No extra fields.

Choice question

type
Required. "choice"
instructions
Required. nonempty string, object or array
criteria
Required. object with named entries of string or null. Minimum fields: 1. Maximum fields: 255. Maximum key length: 256.

Score question

type
Required. "score"
instructions
Required. nonempty string, object or array
criteria
Required. array of string. Minimum items: 2. Maximum items: 10. Items: Minimum length: 1. Items must be nonblank strings.

Inline data

name
Required. string
type
Required. "image" or "audio"
data
Required. string. Must begin with data:.

Advertised limits

max_duration_ms
Required. 3600000
idle_timeout_ms
Required. 120000
start_timeout_ms
Required. 10000
heartbeat_interval_ms
Required. 30000
native_heartbeat_interval_ms
Optional. 10000
receipt_timeout_ms
Optional. 240000
max_control_queue_frames
Optional. 2
pong_timeout_ms
Required. 10000
max_message_bytes
Required. 4194304
max_decide_body_bytes
Required. 3665920
max_pending_events
Required. 4096
max_events
Required. 100000
max_events_per_second
Required. 5
max_send_queue_bytes
Required. 4194304
max_send_queue_frames
Required. 64
min_interval_ms
Required. 0
event_burst
Required. 10
max_messages_per_second
Required. 40
message_burst
Required. 80
max_streams_per_process
Required. 4
max_native_control_frames_per_second
Required. 8
native_control_frame_burst
Required. 16
max_native_control_bytes_per_second
Required. 512
native_control_byte_burst
Required. 1024

Rate limit snapshot

limit
Required. integer. Minimum: 0.
remaining
Required. integer. Minimum: 0.
reset_epoch_seconds
Required. integer. Minimum: 0.

HTTP decision reply

id
Required. string
model
Required. string
served_by
Required. string
created
Required. integer. Minimum: 0. Maximum: 9007199254740991.
context
Required. object. No extra fields.
answers
Required. object
usage
Required. object. No extra fields.

HTTP error envelope

error
Required. HTTP error. No extra fields.
request_id
Required. string

HTTP error

message
Required. string
type
Required. "invalid_request_error" or "authentication_error" or "permission_error" or "not_found_error" or "rate_limit_error" or "overloaded_error" or "api_error" or "server_error"
param
Required. string or null
code
Required. string or null
retryable
Required. boolean
details
Optional. object with named entries of JSON value

Retry fields

should_retry
Required. boolean
retry_after_ms
Required. number or null. Minimum: 0.
retry_after
Required. string or null

Named questions

<question name>
Required. Boolean question, choice question or score question. Minimum fields: 1. Maximum fields: 1024.

Inline attachment array

<item>
Optional. Inline data. Maximum items: 16.

goodbye.resume fields

supported
Required. false
action
Required. "reconnect_and_replay"
retry_after_ms
Required. integer. Minimum: 0.

Boolean question.criteria fields

true
Optional. string
false
Optional. string

HTTP decision reply.context fields

route
Required. "direct" or "long"
tokens
Required. integer. Minimum: 0. Maximum: 9007199254740991.

HTTP decision reply.usage fields

input_tokens
Required. integer. Minimum: 0. Maximum: 9007199254740991.
cached_input_tokens
Required. integer. Minimum: 0. Maximum: 9007199254740991.
media_tokens
Required. integer. Minimum: 0. Maximum: 9007199254740991.
long_input_tokens
Required. integer. Minimum: 0. Maximum: 9007199254740991.
media_items
Required. integer. Minimum: 0. Maximum: 9007199254740991.
thought_tokens
Required. integer. Minimum: 0. Maximum: 9007199254740991.

Ordering and sequence

Each accepted mutation receives ack and increments state_version once.

IDs are precisely evt_1, evt_2 and so on; a rejected update leaves the next ID available for a corrected retry.

Text appends concatenate exactly, arrays append complete values, and replace changes the complete string, array or object state.

Use replace to change an object state; attachments can change independently.

An ack means accepted state in gateway memory, not a decision, bill or durable save.

Every accepted mutation, including an attachment append or replacement, creates a new version with its own supplied key; an unchanged version retains its key.

Rejected mutations leave the previous key intact; an unkeyed mutation clears it.

The gateway maintains one decide in flight and one pending intent.

Dense changes coalesce into the latest state.

In on_request mode, decide must name the current accepted version; its snapshot is chosen when dispatch becomes eligible.

state_version is its cumulative coverage watermark; covered_event_ids names only the mutation batch newly frozen into that dispatch.

An ack precedes any decision that covers it.

Only a successful decision, never an ack or error, resolves an event-to-decision observation.

During shutdown, valid mutations are applied and acknowledged with scheduled: false; they never start another decide on this connection.

Server seq starts at 1 and increases for every server message.

Idempotency keys contain 1 to 255 printable ASCII characters after trimming surrounding ASCII spaces. An unkeyed mutation clears the previous key.

Reconnect creates a new stream with new event numbering.

Replay the full desired state in start, then append only mutations absent from that replay.

Use full-jitter reconnect backoff, honoring any longer server retry delay.

Heartbeats neither bill inference nor extend idle/lifetime.

Limits

Read the limits advertised in hello. Stream limits do not enlarge ordinary HTTP limits. Idle time excludes heartbeats and time spent waiting for a decision. Account stream capacity is max(1, floor(max_concurrent / 2)).

receipt_timeout_ms is a legacy wire hint, not a complete receipt deadline. Continue checking peer liveness and receipt inactivity.

max_duration_ms
3600000 milliseconds
idle_timeout_ms
120000 milliseconds
start_timeout_ms
10000 milliseconds
heartbeat_interval_ms
30000 milliseconds
native_heartbeat_interval_ms
10000 milliseconds
receipt_timeout_ms
240000 milliseconds
max_control_queue_frames
2 frames
pong_timeout_ms
10000 milliseconds
max_message_bytes
4194304 UTF-8 bytes
max_decide_body_bytes
3665920 UTF-8 bytes
max_pending_events
4096 events
max_events
100000 events
max_events_per_second
5 events per second
max_send_queue_bytes
4194304 bytes
max_send_queue_frames
64 frames
min_interval_ms
0 milliseconds
event_burst
10 events
max_messages_per_second
40 messages per second
message_burst
80 messages
max_streams_per_process
4 streams
max_native_control_frames_per_second
8 frames per second
native_control_frame_burst
16 frames
max_native_control_bytes_per_second
512 bytes per second
native_control_byte_burst
1024 bytes
maximum_decision_interval
60000 milliseconds
mutation_burst
10 events
application_message_rate
40 messages per second
application_message_burst
80 messages
max_pending_probes
2 probes
write_progress_timeout
5000 milliseconds
decision_settlement_timeout
5000 milliseconds
close_handshake_timeout
5000 milliseconds
consecutive_retryable_failures
3 failures per state version
protocol_refusal_limit
3 refusals per window
protocol_refusal_window
60000 milliseconds
account_decision_burst
5 starts
account_decision_ceiling
60 starts per minute

Errors and close codes

Read error.envelope and error.retry. Decision failures keep the ordinary HTTP status and error envelope. A rejected mutation leaves state, version and its next event ID unchanged. A fatal error requires a following close.

CodeStatusMeaning
stream_invalid_message400The stream message is invalid.
stream_state_type400The stream state does not support this mutation.
stream_event_order409The stream event does not name the next state version.
stream_version_mismatch409The requested stream version is not current.
stream_not_started400The stream has not started.
stream_already_started409The stream has already started.
stream_upgrade_required426This endpoint accepts WebSocket connections only.
stream_limit_exceeded429The stream limit has been reached.
invalid_request400Invalid attachment list, item or repeated name.
media_unsupported400An attachment count exceeds its kind limit.
payload_too_large413The complete decision request exceeds its byte limit.
limiter_unavailable503Rate-limit state cannot be read. Honor the retry delay.
service_unavailable503Opening capacity is full or the server is stopping. Honor the retry delay.

In on_change mode, a 429 or 503 other than maintenance, or HTTP 409 idempotency_conflict, with retry permitted by the HTTP response schedules the latest state again after its Retry-After delay.

The gateway emits an error frame for each failure and stops automatic retries at three consecutive retryable failures for the same version.

The third error has fatal: true and retains the HTTP retry fields.

It is followed by goodbye with reason overload, the Retry-After delay in resume.retry_after_ms, and close 1013 so the client reconnects and replays.

The same fatal outcome applies immediately when the next automatic retry would reach or exceed the stream's remaining lifetime.

On_request never retries itself.

Retaining the key does not guarantee recovery of an original reply after a dropped connection.

Retry-After-Ms takes precedence over Retry-After.

CodeConditionReasonReconnect
1000Normal client close or application idle for 120 secondsidlelazy
1001Maximum lifetime of one hourmax_lifetimeimmediate
1002RFC 6455 framing protocol errornullnever
1003Binary data framenullnever
1007Invalid UTF-8 or malformed JSONnullnever
1008Refused key/account or 404 unknown_endpoint, start timeout, message/event-rate, native-control frame/byte budget or total-event limit, 3 protocol refusals within 60 seconds (outside shutdown drain), or WebSocket fragment/chunk policyauth, policynever
1009WebSocket data-frame/message size, queued incoming bytes, or pending-event count bound exceedednullnever
1011Unexpected internal failurenullbackoff
1012Server restartshutdownafter hint. retry after ms: 500 milliseconds
1013Maintenance, overload, slow reader (including a missed native or JSON pong), lost/unrenewable lease or unavailable storemaintenance, overload, slow_reader, lease_lost, store_unavailableafter hint or backoff. maintenance retry after ms: 300000 milliseconds. slow reader retry after ms: 1000 milliseconds

Honor goodbye.resume.retry_after_ms and any longer server retry delay. Use reconnect backoff. A goodbye is best effort; a transport close can arrive without it.

The server never sends 1006; clients can observe it after abrupt loss and reconnect with backoff.

Example exchange

Illustrative JSON messages from a server runtime. Replace MODEL_ID and SERVED_BY with values from authenticated discovery. Timing, counts and probabilities illustrate the reply shape. In this exchange the server restarts after the decision, sends goodbye, then closes with code 1012. Wait at least 500 milliseconds before reconnecting.

Server
JSON
{
  "type": "hello",
  "stream_id": "szws_0123456789abcdef0123456789abcdef",
  "seq": 1,
  "protocol": "system-zero.stream.v1",
  "request_id": "example-opening",
  "limits": {
    "max_duration_ms": 3600000,
    "idle_timeout_ms": 120000,
    "start_timeout_ms": 10000,
    "heartbeat_interval_ms": 30000,
    "native_heartbeat_interval_ms": 10000,
    "receipt_timeout_ms": 240000,
    "max_control_queue_frames": 2,
    "pong_timeout_ms": 10000,
    "max_message_bytes": 4194304,
    "max_decide_body_bytes": 3665920,
    "max_pending_events": 4096,
    "max_events": 100000,
    "max_events_per_second": 5,
    "max_send_queue_bytes": 4194304,
    "max_send_queue_frames": 64,
    "min_interval_ms": 0,
    "event_burst": 10,
    "max_messages_per_second": 40,
    "message_burst": 80,
    "max_streams_per_process": 4,
    "max_native_control_frames_per_second": 8,
    "native_control_frame_burst": 16,
    "max_native_control_bytes_per_second": 512,
    "native_control_byte_burst": 1024
  },
  "rate_limit": null
}
Client
JSON
{
  "type": "start",
  "event_id": "evt_1",
  "request": {
    "model": "MODEL_ID",
    "state": "Initial log\n",
    "questions": {
      "ready": {
        "type": "boolean",
        "instructions": "Is the job ready?"
      }
    },
    "prompt_cache_key": "example-affinity"
  },
  "decision_mode": "on_request",
  "min_interval_ms": 0,
  "idempotency_key": "example-state-1"
}
Server
JSON
{
  "type": "ack",
  "stream_id": "szws_0123456789abcdef0123456789abcdef",
  "seq": 2,
  "event_id": "evt_1",
  "state_version": 1,
  "state_bytes": 15,
  "synthesized_body_bytes": 158,
  "scheduled": false
}
Client
JSON
{
  "type": "append_text",
  "event_id": "evt_2",
  "text": "Job is ready.\n",
  "idempotency_key": "example-state-2"
}
Server
JSON
{
  "type": "ack",
  "stream_id": "szws_0123456789abcdef0123456789abcdef",
  "seq": 3,
  "event_id": "evt_2",
  "state_version": 2,
  "state_bytes": 30,
  "synthesized_body_bytes": 173,
  "scheduled": false
}
Client
JSON
{
  "type": "decide",
  "state_version": 2
}
Server
JSON
{
  "type": "scheduled",
  "stream_id": "szws_0123456789abcdef0123456789abcdef",
  "seq": 4,
  "state_version": 2,
  "in_flight": false
}
Server
JSON
{
  "type": "ping",
  "stream_id": "szws_0123456789abcdef0123456789abcdef",
  "seq": 5,
  "nonce": 1
}
Client
JSON
{
  "type": "pong",
  "nonce": 1
}
Server
JSON
{
  "type": "decision",
  "stream_id": "szws_0123456789abcdef0123456789abcdef",
  "seq": 6,
  "state_version": 2,
  "covered_event_ids": [
    "evt_1",
    "evt_2"
  ],
  "reply": {
    "id": "szd_0123456789abcdef0123456789abcdef",
    "model": "MODEL_ID",
    "served_by": "SERVED_BY",
    "created": 1790000000,
    "context": {
      "route": "direct",
      "tokens": 32
    },
    "answers": {
      "ready": {
        "type": "boolean",
        "probability": 0.9,
        "confidence": 0.9,
        "calibration_version": "unpublished",
        "decision": true,
        "alternatives": [
          {
            "decision": false,
            "confidence": 0.1
          }
        ],
        "calibrated": false
      }
    },
    "usage": {
      "input_tokens": 32,
      "cached_input_tokens": 0,
      "media_tokens": 0,
      "long_input_tokens": 0,
      "media_items": 0,
      "thought_tokens": 0
    }
  },
  "request_id": "example-decision",
  "http_status": 200,
  "server_timing": null,
  "latency_ms": null,
  "rate_limit": null,
  "queue_ms": 0,
  "decide_ms": 40000
}
Server
JSON
{
  "type": "goodbye",
  "stream_id": "szws_0123456789abcdef0123456789abcdef",
  "seq": 7,
  "reason": "shutdown",
  "state_version": 2,
  "last_decision_state_version": 2,
  "resume": {
    "supported": false,
    "action": "reconnect_and_replay",
    "retry_after_ms": 500
  }
}