The gateway ships in the
praisonai-bot package. praisonai serve gateway works exactly as documented here; for a standalone install see praisonai-bot Migration. No operator action is required to opt in beyond running the gateway.Quick Start
1
Run an agent that blocks on approval
An agent that already elicits approval gets this path automatically once a running gateway binds the request channel — no extra flags.
2
Handle the frame from a client
The client receives a
server_request when a turn blocks and posts back a server_reply with the answer.3
Survive a reconnect
Restart the client mid-prompt. On the next
hello (or legacy join), the gateway re-issues every still-open request after replaying events, so the prompt re-renders on the new socket.4
Timeout fallback
A request defaults to a
120.0s timeout. On timeout the waiting turn receives None, the open request is cleared, and the elicitation code decides the fallback — typically deny/default.How It Works
A turn blocks, the gateway tracks the open request, delivers a frame, and resolves the waiting future when the correlated reply arrives — even across a mid-prompt reconnect. The core reaches the channel through a task-local slot:get_request_channel() returns the bound implementation (or None in CLI/one-shot, falling back to the existing elicitation path). The gateway registers the channel with register_request_channel(...) at the start of a turn and restores the previous slot with clear_request_channel(token) — the same pattern as the outbound messenger and conversation requester.
Wire Frames
Copy these frames verbatim — integrators code straight against them. Server → client — delivered when a turn blocks, and re-issued on reconnect:kind is one of "approval", "choice", "input". options is present only when kind == "choice".
Client → server — the correlated answer:
unknown_request ack is a safe no-op: an unknown or already-answered id acks without leaking state.
Server → client — structured error frames, each carrying a code:
Request Kinds and Value Contract
Three request kinds map to three value contracts, validated at the handler.
An unrelated value is rejected with
{"code": "invalid_value"} and the request stays open.
Reconnect Replay
Still-open requests are re-issued whenever a client resumes, which is the whole reason this channel exists. Replay runs at the end of both thehello (protocol v2+ resume with since) and legacy join handlers, right after event replay. Only entries still present in the session’s open-request set are re-sent, so an already-answered or timed-out request is never re-issued. The join path also rebinds the session’s preferred delivery target (session._client_id = client_id) so the joining client actually receives the next server_request — without this rebind, a new request after join would be routed to the stale client. This puts join on parity with hello for this feature.
Security Model
Every
server_reply passes three checks, in order: ownership → scope → value.Session ownership
Only a client bound to the request’s ownsession_id may answer it. A WRITE client in another session that guesses a request_id is rejected with {"code": "session_mismatch"} — the victim turn stays open.
Scopes
server_reply classifies as write in the central registry. The handler escalates to approvals when the request’s kind == "approval" (parity with approvals.resolve). Choice and input still require write. A WRITE-only client can answer choice/input but gets {"code": "insufficient_scope", "required_scope": "approvals"} for an approval. See Gateway Operator Scopes.
Value contract
The reply value is enforced per the table above; an invalid value is rejected with{"code": "invalid_value"} and the request stays open.
Timeout
request() defaults to a timeout_s of 120.0. On timeout it returns None and clears the open-request entry, so a later reconnect does not re-issue a timed-out prompt. The blocked turn’s elicitation code interprets None as its fallback — typically deny/default.
Backward Compatibility
Unbound (CLI / one-shot) behaviour is unchanged —get_request_channel() returns None and existing elicitation paths run as before. The praisonai-bot import of the two core shapes is guarded, so it keeps working against an older core. There is no new user-authored surface, no new Agent parameter, and no new dependency.
Building a Client
The minimum a client needs: receive aserver_request, render the prompt, post back a server_reply, and clear on the ack — keyed idempotently on request_id.
- Python
- JavaScript
Best Practices
Set a sensible timeout_s
Set a sensible timeout_s
The default
120s is safe for a human-in-the-loop web UI. Interactive shells that expect a fast answer may want a shorter timeout so a blocked turn does not sit idle.Re-render on server_request at any time
Re-render on server_request at any time
A reconnect can re-issue a prompt the user has already seen. Render idempotently, keyed on
request_id, so a replayed request updates the existing prompt instead of duplicating it.Grant APPROVALS to any role that answers approvals
Grant APPROVALS to any role that answers approvals
A WRITE-only client can answer choice and input but gets
insufficient_scope for approval prompts. Size tokens so any role that resolves approvals over the transport holds the approvals scope.Never guess a request_id
Never guess a request_id
The ownership check rejects cross-session replies with
session_mismatch. Treat an unknown_request ack as a signal that client state has drifted, not as a retry cue.Interpret None from request() as a decline path
Interpret None from request() as a decline path
The timeout returns
None and clears the request. Do not retry the same request_id after that — start a fresh turn if the action is still needed.Related
Gateway Session Continuity
How event replay and this open-request replay compose on
hello / join.Gateway Frame Codec
The inbound frame codec that decodes
server_reply.Approval Protocol
The elicitation surface the blocked turn calls into.
Gateway Operator Scopes
Where
server_reply sits in the scope table, and the approval escalation.Interactive Approval
The terminal-side sibling capability.
Gateway Scoped Approvals
Durable allow-always grants that compose with an approval-kind reply.

