0003. WebSockets in the actor world (wasmkeep:actor@0.2.0)
- Status: accepted
- Date: 2026-11-02
- Milestone: M1
- Supersedes: nothing; extends ADR 0002, which deferred WebSockets
Context
M1 calls for WebSockets in and out, with connections parked across hibernation, and the chat-room example needs them. ADR 0002 deferred them because an exported message handler without a send API is dead weight.
Decision
Package version 0.2.0 adds, to actor-core:
- An imported
websocketsinterface:send(conn, message),close(conn, code, reason),connections(), withmessage=text(string) | binary(list<u8>)andsend-error=unknown-connection | backpressure. - Three exports:
on-websocket-open(conn, path) -> result<_, string>,on-websocket-message(conn, message),on-websocket-close(conn, code, reason).
Semantics:
- The host accepts a GET with
Upgrade: websocketon the actor’s path, answers 101, and asks the guest withon-websocket-open; an error closes the socket with 1008 and the error as reason. The guest never sees a socket, only a connection id. - Every message and the close are mailbox events, serialized with the actor’s requests and alarms, in arrival order per connection. The reader waits for each event before reading the next frame: that is the backpressure.
- Sockets are owned by the host, so they stay open while the actor hibernates; the next message (or any other event) wakes it. This is the “park on the host” option from the initial design’s open questions; the alternative (close and ask clients to reconnect) is kept for actor migration in M2.
- A connection id is only valid in the actor it was opened on:
send,closeandconnectionsare scoped to the calling actor. sendnever blocks the guest: 256 queued messages per connection, thenbackpressure(the message is dropped; the guest decides what to do).
Why not wasi:http upgrades
wasi:http 0.2 has no upgrade or bidirectional streaming story, and
handling sockets inside the instance would pin it in memory, which defeats
hibernation.
Consequences
- Breaking for guests at the binary level: components built for 0.1.0 lack
the new exports and must be rebuilt. Source compatibility is kept by
wasmkeep-sdk: the newActorhooks have defaults (refuse the upgrade, ignore messages and closes), and every example rebuilt unchanged. - Messages are text or binary; ping/pong is handled by the host. Close codes and reasons follow RFC 6455 (reasons truncated to 123 bytes).
- Migration note: rebuild your component against
wasmkeep-sdk(or regenerate bindings againstwasmkeep:actor@0.2.0) and implement the three hooks to accept WebSockets.