174 lines
8.7 KiB
Markdown
174 lines
8.7 KiB
Markdown
# Runa Protocol
|
|
|
|
`runa-protocol` defines versioned request, response, and event data contracts
|
|
for clients and the `runa-server` dispatcher. It is independent of transport
|
|
and runtime implementation. `runa-server` accepts JSON Lines over stdio or,
|
|
when started with `--listen`, as WebSocket text messages bound to an IPv4 or
|
|
IPv6 loopback address. 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:
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
Both transports limit 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. Stdio EOF terminates the
|
|
process, while 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 launches the stdio server locally and 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 executable continues to start with an empty image.
|
|
|
|
The TypeScript VS Code client currently issues only `list_actors` and
|
|
`inspect_actor`. 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; stdio is unchanged.
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"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.
|