Skip to main content
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.
Looking for the event-loop watchdog? This page covers connection-level heartbeats that reap silent clients. To detect a wedged asyncio loop (the process is up but the loop isn’t running), see Event-Loop Watchdog.
Liveness is on out-of-box: the gateway pings peers on an interval, refreshes activity on every inbound frame, and reaps any session — or any stalled handshake — that misses too many beats.

Quick Start

1

On by default

2

Tune the cadence

3

Opt out with interval_ms=0

LivenessConfig(enabled=False) alone does not disable liveness. The runtime overrides the config default and synthesises an enabled policy from the config’s own window. Set interval_ms=0 to fully opt out.

How It Works

The gateway emits a PING on each interval; any inbound frame (a PONG, a peer PING, or a normal message) refreshes activity. A silent peer misses beats until the reaper closes it. A connection is reaped once now > last_activity + interval_seconds × missed_beats_before_reap. Half-open peers with no bound session yet (a stalled pre-hello/join handshake) are evaluated against a per-connection _client_last_seen clock, so silent handshake sockets age out instead of leaking forever. The runtime is on by default: the config’s enabled=False no longer disables it. Only interval_ms=0 yields a disabled policy, after which the loop stays inert and nothing is reaped.

Health Output

WebSocketGateway.health() surfaces the reaper’s state when the task is running:

LIVENESS_TIMEOUT close code

The reaped peer sees a WebSocket close with:
  • Code: 4002 (application-defined, private-use 4000–4999 range)
  • Reason: "liveness_timeout" (equal to GatewayCloseCode.LIVENESS_TIMEOUT.value)
Custom clients should treat this as expected and reconnect. The reference client’s heartbeat loop usually force-reconnects first — after 2 × heartbeat_ms of silence it closes its own socket so the backoff/resume path re-establishes the connection before the server reaps it.

Configuration Options

LivenessConfig is the user-facing config; to_policy() bridges it to the pure LivenessPolicy the reaper consumes.
Full field, type, and default reference

Common Patterns

Liveness runs at the defaults; pick a cadence from the peer’s network profile, or opt out entirely with interval_ms=0.

Out-of-box defaults

Aggressive reaping for high-turnover realtime

Fully disabled


Best Practices

enabled=False alone does not disable liveness — the runtime overrides it and synthesises an enabled policy from the config’s window. To fully turn liveness off, set interval_ms=0, which yields a disabled policy and keeps the reaper loop inert.
health() exposes a cumulative liveness.reaped_connections counter. A sudden burst of reaps signals an upstream network problem (flaky peers, NAT timeouts, or a proxy dropping idle sockets) — watch the rate, not just the total.
The server advertises heartbeat_ms in hello_ok; when liveness is enabled (the default) this equals the policy’s interval_ms. The reference client already runs _heartbeat_loop: it pings every interval and force-closes the socket after 2 × heartbeat_ms of silence so backoff/resume reconnects. Custom clients should derive the same 2× silence window from the advertised value — miss it and the server closes you with 4002.
A reaped peer sees GatewayCloseCode.LIVENESS_TIMEOUT (value "liveness_timeout", close code 4002) in the WebSocket close reason. Match on it to reconnect and resume — don’t surface it as a fatal error to the user.

Doctor Plugins

Surfaces liveness state in gateway diagnostics

Reliability Preset

Related resilience knobs

Session Continuity

What survives a reap

Event-Loop Watchdog

Detect a wedged asyncio loop (not just a silent connection)