The gateway now ships in the
praisonai-bot package. praisonai serve gateway still works exactly as documented here; for a standalone install see praisonai-bot Migration.Quick Start
How It Works
Negotiated protocol version:min(client_max, GATEWAY_PROTOCOL_VERSION) where server version is 1 and minimum accepted client version is 1.
Legacy join → joined still works for existing clients. New clients should prefer hello.
HelloParams (client → server)
| Field | Type | Default | Description |
|---|---|---|---|
agent_id | str | — | Agent to connect to |
protocol_min | int | — | Minimum protocol version client supports |
protocol_max | int | — | Maximum protocol version client supports |
capabilities | List[str] | [] | e.g. streaming, presence, ack |
session_id | Optional[str] | None | Existing session to resume |
since | Optional[int] | None | Event cursor for replay |
protocol: {min, max} is also accepted; missing values fall back to 1.
HelloResult (server → client, hello_ok)
| Field | Type | Description |
|---|---|---|
protocol | int | Negotiated protocol version |
features | Dict[str, List[str]] | Supported methods and events |
policy | Dict[str, int] | max_payload, max_buffered_bytes, max_queued_frames, heartbeat_ms |
session_id | str | Session ID (new or resumed) |
resumed | bool | True if an existing session was resumed |
cursor | int | Current event cursor |
methods: message, leave only (abort is not implemented). Base events: message, error.
HelloError (server → client, hello_error)
| Field | Type | Description |
|---|---|---|
code | ConnectErrorCode | Structured, machine-readable error code |
message | str | Human-readable explanation (display only) |
next_step | Optional[ConnectRecoveryStep] | Machine-readable recovery hint |
retry_after_seconds | Optional[int] | Backoff hint, only meaningful with wait_then_retry |
next_action | Optional[str] | Deprecated free-text hint; prefer next_step |
Wire frame also includes a legacy
next key for backward compatibility — it mirrors next_action (or falls back to next_step.value). The next_step, retry_after_seconds, and next keys are omitted entirely when no recovery hint is set — they are never null.ConnectErrorCode
| Code | Meaning | Typical next_step |
|---|---|---|
auth_required | Authentication missing on a non-loopback bind | reauthenticate |
auth_unauthorized | Invalid credentials or wrong agent for session | reauthenticate |
protocol_unsupported | Client/server version mismatch | upgrade_client or downgrade_client |
pairing_required | Client must complete pairing first | repair |
agent_not_found | Unknown agent_id | do_not_retry |
rate_limited | Too many connection attempts — driven by an injectable RateLimitPolicyProtocol | wait_then_retry (+ retry_after_seconds) |
origin_not_allowed | Origin not in allowed list (CSWSH defence) | do_not_retry |
configuration_error | Server is misconfigured (e.g. external bind with no allowlist) | do_not_retry |
The
rate_limited code is driven by an injectable RateLimitPolicyProtocol — see Gateway Rate Limit Policy for SlidingWindowRateLimitPolicy, YAML gateway.rate_limit, and custom policies.ConnectRecoveryStep
Machine-readable recovery hint. Clients branch on(code, next_step) instead of parsing message.
| Value | Meaning |
|---|---|
reauthenticate | Obtain fresh credentials, then reconnect |
repair | Re-run device pairing, then reconnect |
upgrade_client | Client protocol too old — update the client |
downgrade_client | Client protocol newer than server — use an older client |
wait_then_retry | Back off (retry_after_seconds), then reconnect |
do_not_retry | Terminal — reconnecting will not help |
Capability Matrix
| Capability | Events unlocked | Server prerequisite |
|---|---|---|
| (none) | message, error | — |
streaming | token_stream, tool_call_stream, stream_end | — |
presence | presence_join, presence_leave, presence_update | server has _presence_tracker |
ack | message_ack, message_nack, delivery_retry | server has _delivery_tracker |
Server-side state after handshake
Afterhello_ok is sent, the gateway records the negotiated protocol version and the client’s advertised capabilities on the session itself. Both are read-only and survive resume.
| Property | Type | Notes |
|---|---|---|
session.protocol_version | int | Read-only. Set by the hello handler from the negotiated value. |
session.capabilities | List[str] | Read-only. Returns a copy — mutating the returned list does not change session state. Empty list when the client advertised no capabilities. |
hello path and the legacy join path, so server code can branch on session.capabilities without checking which handshake the client used.
Policy Limits
| Key | Default | Description |
|---|---|---|
max_payload | 1048576 (1 MB) | Maximum message payload size |
max_buffered_bytes | 8388608 (8 MB) | Maximum buffered bytes per connection |
max_queued_frames | 1000 | Maximum queued outbound frames per client. Clients can read this to pace their own sends. |
heartbeat_ms | 30000 | Heartbeat interval (heartbeat_interval * 1000, default 30 s) |
policy object in hello_ok. See Gateway Flow Control for tuning max_buffered_bytes and max_queued_frames.
Persisted Session State
Afterhello_ok, the gateway records both the negotiated protocol version and the client-advertised capabilities on the GatewaySession. These values survive disconnect and resume.
| Property | Type | Description |
|---|---|---|
session.protocol_version | str | The negotiated protocol version agreed during hello. |
session.capabilities | list[str] | The capabilities the client advertised in the hello message. |
session.to_dict() and restored by from_dict(), so they survive server restarts and session persistence backends.
Server-side hooks and custom routing code can read these off any live session:
If
to_dict() / from_dict() encounters a non-list value for capabilities (e.g. from an older snapshot), it is safely restored as [] to avoid errors.Best Practices
Always advertise protocol_min and protocol_max
Always advertise protocol_min and protocol_max
Even when you only support version 1 today, send an explicit range so future servers can negotiate.
Only request capabilities you handle
Only request capabilities you handle
Requesting
streaming without handling token_stream events wastes bandwidth and confuses clients.Use session_id + since on reconnect
Use session_id + since on reconnect
Resume cleanly after disconnect and process
replay envelopes before sending new messages.Branch on (code, next_step) from hello_error
Branch on (code, next_step) from hello_error
Use the structured
(code, next_step) pair instead of parsing message. The legacy next key is still emitted for older clients but new code should branch on next_step.Related
Gateway & Control Plane
Unified gateway architecture
Gateway Overview
WebSocket gateway features
Session Protocol
Session message format
Error Handling
Structured connection errors

