9.1 KiB
Runa Protocol
runa-protocol defines versioned request, response, and event data contracts
for clients and the runa dispatcher. It is independent of transport
and runtime implementation. runa accepts WebSocket text messages bound to an IPv4 or
IPv6 loopback address, defaulting to 127.0.0.1:3737 without arguments.
Clap parses --listen / RUNA_LISTEN on the server and --connect /
RUNA_CONNECT on runa-cli; explicit flags override environment variables.
The listener is unauthenticated and intended only for
trusted local development. Both the REPL and VS Code use WebSockets.
Version 3
Every request and response carries version: 3. Requests also carry a
non-empty request_id used to correlate the response. A server must reject
unsupported versions with unsupported_version rather than interpreting a
request using a different contract. Events carry the protocol version and do
not have a request ID.
Requests have a tagged command object:
{
"version": 3,
"request_id": "req-1",
"command": {
"operation": "send",
"parameters": {
"actor_ref": "xK8_pQ2-Lm5_012345678",
"message": {
"type": "record",
"data": {
"op": { "type": "string", "data": "send" },
"value": { "type": "integer", "data": 9 },
"target": { "type": "actor_ref", "data": "Hj2_9sM0-pQ4_7aBcD3eF" }
}
}
}
}
}
The version 3 operations are behavior publication, rollback, lookup, source retrieval, actor spawn, send, tick, inspect, actor listing, event retrieval, stop, restart, pause, resume, cancellation, and behavior installation. Migration closures are intentionally excluded: they are host callbacks, not portable wire values.
Responses echo the request ID and carry either a typed reply or a protocol
error. Events use a separate versioned envelope. Actor and behavior snapshots
are wire DTOs, not serialized runtime objects; implementation identities are
represented by their stable display strings. Public actor identity is a
21-character URL-safe NanoID string carried in actor_ref. The server maps known service
failures to the defined error codes and otherwise uses runtime_failure
rather than claiming a more specific category.
Portable values
Spawn state, send messages, snapshot state/mailbox/reservations, and migration event states use the same tagged value shape:
| Value | JSON |
|---|---|
| Null | {"type":"null"} |
| Boolean | {"type":"bool","data":true} |
| Signed 64-bit integer | {"type":"integer","data":42} |
| String | {"type":"string","data":"hello"} |
| List | {"type":"list","data":[<value>, ...]} |
| Record | {"type":"record","data":{"key":<value>, ...}} |
| Actor reference | {"type":"actor_ref","data":"<21-character-NanoID>"} |
References are distinct from strings. Their shape is validated even when
nested; existence is checked when a reference is used as a send destination,
not merely stored as data. The old message envelope and protocol v2 are not
accepted. The REPL's natural JSON and $actor/$record conveniences are
client syntax, not wire encodings.
Each state/message permits a maximum nesting depth of 32 (root depth 0),
4,096 nodes, and 1 MiB of UTF-8 string/key bytes. Requests are also subject to
the transport's 1 MiB total message limit, including tags and envelope.
Unsupported value tags, malformed references, floats, and out-of-range integers
fail decoding; well-formed requests exceeding value limits receive
invalid_request. Invalid Rune transition output fails the actor without
acknowledging the reserved message, changing committed state, or enqueueing
any of its sends.
get_events drains and returns the events currently accumulated by this
single-worker image. It is not a durable subscription or replayable event log.
The transport limits each request to 1 MiB before parsing. Malformed
JSON or an oversized message terminates that connection with an error; no protocol
response is emitted when a request cannot be decoded. A WebSocket disconnect
closes only that client connection. The listener
allows up to 16 simultaneous clients and serializes each dispatch through the
same image. WebSocket response payloads are capped at 1 MiB, with one
in-flight dispatch per client and a bounded shared dispatch queue. Every local client can use the full development
API; loopback binding is not authentication or authorization. Remote access
must not be enabled until appropriate authentication and authorization exist.
The runa-client crate validates response protocol versions and request IDs.
Its WebSocketClient connects to
loopback endpoints using bounded WebSocket messages and correlation checks,
validating interleaved notifications while waiting for responses.
Connection establishment has a 5-second timeout; WebSocket writes and response
waits have 30-second timeouts. A failed network request invalidates that client connection.
No automatic retry is performed: loss of a response does not imply that the
operation failed to commit.
The interactive REPL uses WebSockets and never owns the image. Disconnecting clients
does not stop actors or reset behavior revisions; reconnecting clients can
retrieve them through list_actors and get_behaviors. The network executable
publishes the bundled counter behavior on image startup, not on connection.
The stdio transport and child-process client have been removed.
The TypeScript VS Code client issues list_actors, inspect_actor,
get_behaviors, get_behavior_source, spawn, send, tick, and
pause/resume/stop/restart operations.
Behavior source lookup uses an explicit immutable revision, not a mutable
latest-source alias. VS Code caches read-only source documents by connection,
behavior name, and revision while refreshing behavior listings on notifications.
It preserves i64 inputs and bounds the complete outgoing envelope as well as
incoming messages. It enforces loopback endpoints, bounded UTF-8 text messages,
version/request-ID/reply-kind checks, and portable snapshot value validation.
It decodes JSON integers as bigint and prints them losslessly, including
signed 64-bit state and unsigned 64-bit telemetry. It uses the same 5-second
connect and 30-second request timeouts, closes on transport/protocol corruption,
and never retries automatically. A well-formed server error is surfaced without
invalidating the stream. Server notifications trigger automatic refreshes of
the actor tree and open snapshot documents. Manual refresh remains available.
WebSocket transport and notifications
Connect to ws://127.0.0.1:3737/ (or a configured numeric loopback endpoint)
and offer the runa-debug WebSocket subprotocol. The server requires and echoes
that subprotocol. Plain TCP JSON Lines is no longer accepted by the network
listener. The request/response contract remains version 3.
Each WebSocket text message contains exactly one JSON request, response, or
notification, without newline framing. Binary messages and malformed requests
terminate the connection. Fragmented text is supported with a 1 MiB aggregate
message limit; individual frames are also limited to 1 MiB. Compression is not
enabled. Upgrade handshakes have a 5-second timeout and writes have a
30-second timeout so stalled peers do not hold client slots forever.
The endpoint accepts only the root path and rejects requests carrying an
Origin header: browser-origin access is not enabled for this unauthenticated
local API. This is not authentication; local non-browser clients retain full
development privileges.
Every upgraded connection automatically receives non-destructive invalidations:
{
"version": 3,
"event": {
"event": "image_changed",
"data": { "revision": 42 }
}
}
Successful mutating requests (spawn, send, tick, lifecycle controls,
publication/rollback, and installation) increment a server-session revision
and notify all connected clients, including the initiator. Read-only requests,
failed mutations, and get_events do not generate invalidations. A tick
invalidates snapshots even when it only advances execution telemetry or causes
an actor failure. A successful send invalidates mailbox snapshots before any
handler runs. Notifications may appear between requests and their replies;
clients must distinguish them from correlated responses.
Revisions increase within one server session, not across server restarts.
Slow subscribers retain only the latest unsent revision; gaps are expected.
These notifications are hints to re-fetch authoritative data, not individual
runtime events, a durable log, an atomic snapshot revision, or replay.
On connection/reconnection, fetch a full listing first. The VS Code client
coalesces bursts and schedules another refresh if an invalidation arrives
during an in-flight read. It never advances the scheduler or calls get_events;
the REPL's explicit events command still drains the separate runtime queue.
The current validation checks the protocol version, request ID, actor-reference shape, structured-value limits, positive revisions where required, non-empty behavior names, mailbox capacity, and instruction budget. Behavior-name grammar, actor existence, current behavior revisions, mailbox capacity at send time, and all runtime invariants remain authoritative at the application-service boundary.