Expand description
WebSocket Operator Callback IF (/v1/operators*).
§WebSocket Operator Callback IF
Path for seating an external HTTP/WS caller as an Operator role inside
the Engine. One WS connection = one session = three traits co-hosted
(Operator / SeniorBridge / SpawnHook); a single sid is registered into
all three registries simultaneously.
§Architecture overview
┌─────────────── External Operator (Human / Agent / other process) ────────┐
│ WS /v1/operators/:sid/ws (Bearer required) │
│ S→C: Ask{req_id,task_id,question} │
│ S→C: HookBefore{req_id,task_id,agent,attempt} │
│ S→C: HookAfter{req_id,task_id,agent,attempt,result} (fire-and-forget)│
│ S→C: Spawn{req_id,task_id,agent,attempt,capability_token} │
│ C→S: Answer{req_id,value} (SeniorBridge.ask reply) │
│ C→S: HookAck{req_id,ok,reason?} (SpawnHook.before reply) │
│ C→S: SpawnAck{req_id,value,ok,error?} (Operator.execute reply) │
└────────────────────────────────┬────────────────────────────────────────┘
│ axum WebSocket
┌────────────────────────────────▼────────────────────────────────────────┐
│ login.rs │
│ operators_ws_connect — `GET /v1/operators/:sid/ws` upgrade, │
│ Bearer token check against minted sid │
│ handle_operator_socket — write task / read task / disconnect path │
└────────────────────────────────┬────────────────────────────────────────┘
│
┌────────────────────────────────▼────────────────────────────────────────┐
│ session.rs : WSOperatorSession │
│ sid + auth_token + tx (Mutex<Option<>>) + pending (Mutex<HashMap>) │
│ impl SeniorBridge { ask → send Ask + wait Answer } │
│ impl SpawnHook { before → send HookBefore + wait HookAck / │
│ after → send HookAfter fire-and-forget } │
│ impl Operator { execute → send Spawn + wait SpawnAck, │
│ thin-forward capability_token to MainAI } │
└────────────────────────────────┬────────────────────────────────────────┘
│ same sid → registered into 3 registries at once
▼
engine.senior_bridges / spawn_hooks / operators (SoT)
│ dispatch_attempt → resolve_operator_info
│ looks up session.bridge_id / hook_id / operator_backend_id
▼
Ctx.operator (= read by SeniorEscalationMiddleware / MainAIMiddleware /
OperatorDelegateMiddleware)
protocol.rs : ServerMsg / ClientMsg / PendingReply (= wire format + internal reply IR)§Thin-control discipline for Spawn (the Spawn thin-control axis)
The server sends only Spawn{capability_token}; the MainAI (WS Client) forwards the
token to the SubAgent, and the SubAgent hits /v1/worker/prompt +
/v1/worker/result itself with Authorization: Bearer <capability_token>
(= heavy payloads go over HTTP; WS stays purely thin control). See
protocol::ServerMsg::Spawn and mlua_swarm::Operator::execute
for details.
§Design rationale (= for future re-constructors)
- 3 traits co-hosted: Holding all 3 faces of the Operator role
(judgment =
SeniorBridge/ observation =SpawnHook/ execution =Operator) in a single session gives 1 WS connection = 1 Operator that answers ask/before/after/spawn — the natural shape. Registering the same sid into three registries preserves “same Operator” semantics on the Registry axis as well. Mutex<Option<Sender>>for tx swap-in:Noneon disconnect,Some(new_tx)on reconnect. The pendingHashMappersists on the session side, so a client that held answer/ack values during a disconnect can reconnect and resend them. (In v1.5, sends during a disconnect fail immediately — the client is responsible for remembering its own pending.)- req_id naming:
<sid>-<ask|hb|ha|spawn>-<uuid>covers both the trait axis and uniqueness. Clients can identify the trait from the req_id. parent_req_idfield: Schema for representing nesting (e.g. a hook firing inside an ask). In v1.5 the engine-side middleware does not fire nested calls, so this is alwaysNone; v2 will re-introduce nesting viatask_local.
§Out of scope for v1.5 (carry)
- Buffering / replay of ask/spawn/hook_before during a disconnect (= sends
currently just return
Erron failure). - Automatic session-TTL cleanup (= session leaks after disconnect wait for
the admin
DELETEendpoint). - True nested ask (= depends on a middleware extension; the
parent_req_idschema is already carried). - Multi-Blueprint scope separation (= a single WS Operator currently serves as the Operator for all tasks).
CapTokenconsistency between the Operator session and the engine attach session.
§REST-like login flow (login.rs) — sole Operator session entry point
POST/GET/DELETE /v1/operators + WS /v1/operators/:sid/ws (login.rs) is
the only Operator session route. The login flow mints the sid server-side,
requires Bearer auth (no empty-string default), and enforces a
roles-exclusivity 409 at mint time. See the login module doc for details.
Re-exports§
pub use login::operators_create;pub use login::operators_delete;pub use login::operators_info;pub use login::operators_ws_connect;pub use login::OperatorSessionEntry;pub use login::OperatorsCreateReq;pub use login::OperatorsCreateResp;pub use login::OperatorsInfoResp;pub use protocol::ClientMsg;pub use protocol::ServerMsg;pub use session::WSOperatorSession;
Modules§
- login
- REST-like Operator session resource (
POST/GET/DELETE /v1/operators+ WS upgrade). REST-like Operator session resource. - protocol
- Wire format (
ServerMsg/ClientMsg) forWS /v1/operators/:sid/ws. Wire format (= S↔C JSON message schema) forWS /v1/operators/:sid/ws. - session
WSOperatorSession: the 3-trait (SeniorBridge/SpawnHook/Operator) WS session object.WSOperatorSession: 1 sid = 1 session = 3 traits co-hosted (SeniorBridge/SpawnHook/Operator). Registered simultaneously into 3 registries under the same sid — the canonical pattern where 1 WS connection covers all 3 faces of the Operator role (judgment / observation / execution).