Command admission and dispatch
Application code supplies the requested action and target. AppNode adds identity, response routing, lifecycle, trace, capture, and authorization fields before handing a serialized command to the dispatcher.
Calling forms
The user-scoped form accepts a logical client_id:
command_id = await scoped.send_command(
"primary-browser",
{
"name": "navigate",
"target": {"tab_id": 42},
"params": {"url": "https://example.com/"},
},
SendOptions(),
)
The raw form accepts an explicit trusted Routing:
command_id = await app_node.send_command(routing, command, options)
The command object requires name and can contain target and params. Browser window and tab identifiers belong under target; action arguments belong under params. Caller-supplied reserved Cheetah protocol keys are rejected in command parameters and parser options.
send_command_and_wait() takes the same command and options, forces RPC mode, and returns a terminal RpcResolution rather than the complete handler payload.
SendOptions
| Option | Default | Effect |
|---|---|---|
stream_key_suffix | None | uses _responses/{command_id}; the server adds the effective-user prefix |
mode | ResponseMode.none | does not register an RPC waiter; it does not forbid the client from returning messages |
timeout_ms | None | uses the App default, currently 30 seconds, for the registered execution observation and command envelope |
reconnect_wait_ms | None | uses the App default, currently 45 seconds, for a recently seen client to reconnect before admission |
max_client_staleness_ms | None | uses the App default, currently 3 minutes, when judging whether retained sighting evidence justifies that wait |
trace_id | None | uses the generated command ID as the root trace ID |
tags | None | adds application grouping labels that reference clients echo on returned messages |
capture | None | requests supported post-command evidence |
auto_offload | True | permits supported oversized fields to become signed payload references when storage and signing are configured |
parser_definition_delivery | "inline" | attaches active registered definitions; "cache_aware" can retry one RPC after a verified missing-parser preflight |
The helper methods with_parser() and with_imprint_parser() add parser capture requests to a copy of the options. with_cache_aware_parser_definitions() and with_inline_parser_definitions() select definition delivery without mutating the original options.
When cache-aware RPC delivery receives the specific retained evidence that a required parser was missing during preflight, send_command_and_wait() can issue one new command with inline definitions. The returned resolution then contains the retry's command ID, not necessarily the first ID.
Admission happens before a command becomes durable
The current path is deliberately ordered:
- resolve and validate the effective identity;
- validate caller-owned command and capture fields;
- run configured central authorization;
- inspect fresh connection or retained sighting evidence;
- optionally wait for a recently seen logical client to reconnect;
- prepare registered parser definitions and the command envelope;
- apply supported payload offloading if a configured WebSocket size limit requires it;
- register RPC correlation when the effective mode is
rpc; - ask the dispatcher to admit the command.
Central authorization runs before presence is exposed. A denied caller therefore cannot use the command API as a client-presence probe.
A fresh active connection proceeds immediately. A client with no retained sighting, or with sighting evidence older than the selected threshold, fails without waiting. A recently seen but disconnected logical client can use the bounded reconnect wait. That wait creates no command, RPC registration, durable reservation, or dispatcher side effect, so cancellation at this stage is safe.
Pre-admission waits are bounded both process-wide and by tenant in shared mode, or by effective user in ordinary mode. ConnectionNotFoundError.reason_code distinguishes unknown_client, stale_client, reconnect_timeout, and wait_capacity and carries the available age and wait details.
One deadline protects admission
The reconnect wait and durable handoff share an admission window. App configuration reserves at least minimum_delivery_budget_ms, currently 10 seconds, after reconnect waiting so the dispatcher still has time to accept the command. If the remaining budget expires before durable admission, dispatch fails rather than creating work with no useful delivery window.
RPC registration is initially attached to the stable logical client, not to the runtime seen during admission. The dispatcher binds the actual instance_id immediately before the first WebSocket send attempt. This lets provably unattempted work follow a logical client that reconnected through a replacement runtime without allowing an uncertain attempt to be silently replayed.
What the return value proves
On accepted admission, send_command() returns the generated command ID. Keep it for delivery inspection, tracing, terminal waiting, or history lookup.
The return does not prove that a socket attempt began, the transport accepted a write, the client acknowledged the frame, local policy allowed execution, a handler ran, or a side effect completed. Those later facts have separate evidence paths.
If ordinary dispatch rejects the command after RPC registration, App cancels that registration. If the durable backend cannot determine whether admission succeeded, App raises DispatchAdmissionUnknownError with the command ID and retry_safe=False and leaves the RPC registration alive because a result may still arrive. Automatic retry in that case could create duplicate work.
Failures at this boundary
| Exception | Meaning |
|---|---|
ConnectionNotFoundError | no fresh connection or eligible bounded reconnect was available |
AuthorizationError | central policy denied or could not evaluate safely before presence and dispatch |
DispatchError | parser preparation, size/offload preparation, delivery budget, or dispatcher handoff failed definitively |
DispatchAdmissionUnknownError | the dispatcher may have accepted the command; retry safety is not established |
RPC timeouts and terminal client errors happen after admission and are covered in Terminal results and delivery status.