Private Channels
Problem
Section titled “Problem”Multiple clients communicate with one server. How does each client receive only its own replies without building separate channels?
Solution: tag replies with client id
Section titled “Solution: tag replies with client id”The server tags each reply with the originating client id. Clients filter the shared outbox by their own id.
sequenceDiagram
participant Bus as shared-bus
participant S as server-c
participant A as client-a filter
participant B as client-b filter
Bus->>S: {id="a", value=5}
Bus->>S: {id="b", value=3}
Bus->>S: {id="a", value=10}
S-->>A: {id="a", result=10}
S-->>B: {id="b", result=6}
S-->>A: {id="a", result=20}
server-beh = msg: reply { id = msg.id; result = msg.value * 2; };server-c = actor server-beh;
shared-bus = st { id = "a"; value = 5; } { id = "b"; value = 3; } { id = "a"; value = 10; };
server = server-c { inbox = shared-bus; };
client-a = server.outbox.filter (r: r.id == "a");client-b = server.outbox.filter (r: r.id == "b");
client-a.toList# [ { id = "a"; result = 10; } { id = "a"; result = 20; } ]client-b.toList# [ { id = "b"; result = 6; } ]Each client sees only its slice. The server needs no routing logic — it just echoes the id back.
With Either results
Section titled “With Either results”Combine the tagged bus with reply.right / reply.left for per-client error separation:
compute = msg: if msg.divisor == 0 then { left = "div-by-zero"; } else { right = msg.dividend / msg.divisor; };
server-c = actor (msg: reply { id = msg.id; result = compute msg; });
shared-bus = st { id = "a"; dividend = 10; divisor = 2; } { id = "b"; dividend = 6; divisor = 0; } { id = "a"; dividend = 9; divisor = 3; };
server = server-c { inbox = shared-bus; };
a-replies = server.outbox.filter (r: r.id == "a");b-replies = server.outbox.filter (r: r.id == "b");Each client gets { id, result } where result is { right = n } or { left = msg }.
Alternative: isolated sessions via when-c
Section titled “Alternative: isolated sessions via when-c”If clients want full per-client state (not just reply filtering), use when-c to give each client its own actor instance:
flowchart TB
shared["shared bus\n[{to=alice,cmd=inc},{to=bob,cmd=inc},{to=alice,cmd=get}]"]
wca["when-c to==alice → cmd"]
wcb["when-c to==bob → cmd"]
a["alice\ncounter-c\nstate: 2"]
b["bob\ncounter-c\nstate: 1"]
shared --> wca --> a
shared --> wcb --> b
shared = st { to = "alice"; cmd = "inc"; } { to = "bob"; cmd = "inc"; } { to = "alice"; cmd = "get"; };
alice = counter-c { inbox = when-c (m: m.to == "alice") shared (m: m.cmd); };bob = counter-c { inbox = when-c (m: m.to == "bob") shared (m: m.cmd); };Alice and Bob each get their own counter state. Messages for one never affect the other.