Connecting to Semurg over a WebSocket: live pushes and the same operations
12 October 2026 · Semurg · 4 min read · Markdown
Verified: every command below ran on a fresh node of r11.78.20261012.bc0d94f (how)
connectionswebsocketrealtimehow-to
REST answers when you ask. A WebSocket also tells you when something changes. Semurg's socket does both: it pushes the node's state every second, and it accepts the same operations as the REST routes, answered by the same code, so a dashboard and an application can share one connection.
The socket and its topics
The socket lives at /socket on your node and speaks the Phoenix channels protocol: every message is a JSON array of a join reference, a message reference, a topic, an event and a payload. You join a topic, and then send events on it.
The topics push the node's state every second, the same maps the REST cluster routes return:
cluster:util: the utilisation snapshot (disks, cores, memory bandwidth, per node and in total), with the memory-tier counters beside it.cluster:conveyor: the memory tier, prefetch and janitor counters alone.cluster:nodes: the nodes this node knows.cluster:licence: the licence state.
The token, safely
From the machine the node runs on, the token can ride in the connect address. From anywhere else it must not: an address lands in every proxy and access log on its way in, so off the local machine a token in the address is ignored, and the socket connects unproven. Send the token in the join message instead, inside the payload, where no log sees it. A socket without a valid token is treated exactly as a REST call without one: it sees only the coarse public view, and on a node exposed without a token, the public sandbox.
If the token is revoked while the socket is open, the channel re-checks it on its next message or push and closes with a revoked event, so a revoked key does not keep a live connection.
The same operations, as events
A joined socket can send any data operation as an event, answered as a reply on the same topic with the same body the REST route would return. Among them: memory:recall and the other memory calls, object:put, object:get, stream:produce, stream:read, search:term_postings, geo:within, time_series:latest, vector:hybrid, neighbours:read, rows:read and analytics:run. Two events push to you on their own: stream:wake when a stream you follow has new messages, and jobs:done when a job you subscribed to finishes.
A first connection
This example runs with Node.js and no packages, and we run it as written on a fresh node. It joins cluster:util with the token in the join message, stores that Dana works at Acme, asks memory what it holds about Dana, and prints one line per step:
const token = process.env.SEMURG_API_TOKEN;
const ws = new WebSocket("ws://localhost:4000/socket/websocket?vsn=2.0.0");
let ref = 0;
const pending = new Map();
const send = (topic, event, payload, step) => {
const r = String(++ref);
pending.set(r, step);
ws.send(JSON.stringify(["1", r, topic, event, payload]));
};
ws.onopen = () => {
send("cluster:util", "phx_join", { token }, "join");
send("cluster:util", "memory:remember", {
scope: "demo",
facts: {
entities: [{ name: "Dana", type: "Person" }, { name: "Acme", type: "Company" }],
relations: [{ source: "Dana", target: "Acme", type: "WORKS_AT" }],
},
}, "remember");
send("cluster:util", "memory:recall", { scope: "demo", subject: "Dana" }, "recall");
};
let snapshotSeen = false;
ws.onmessage = (message) => {
const [, r, , event, payload] = JSON.parse(message.data);
if (event === "phx_reply" && pending.has(r)) {
const step = pending.get(r);
pending.delete(r);
if (step === "recall") {
const found = JSON.stringify(payload.response).includes("Acme");
console.log(found ? "recall found Acme" : "recall found nothing");
} else {
console.log(`${step} ${payload.status}`);
}
} else if (event === "snapshot" && !snapshotSeen) {
snapshotSeen = true;
console.log("snapshot seen");
}
};
// stop after a few seconds; a long-lived client keeps the socket open instead
setTimeout(() => ws.close(), 5000);Expected: join ok, remember ok, recall found Acme, snapshot seen
Each reply comes back on the same topic as a phx_reply carrying the reference of the message it answers, with status ok or error; the recall's reply carries the same state and items the /v1/memory/recall route returns. Between replies, a snapshot event arrives every second; this example prints the first one and closes the socket after a few seconds.
When something goes wrong
A refused operation replies with status error and the same error and detail the REST route gives for the same mistake. A join with no valid token, on a node that requires one, sees only the public view, never your data. A push the node cannot fill sends null for that field rather than dropping the connection.
Next
- The REST post: the same operations as plain HTTP.
- Look after your node: the cluster routes behind each topic.
How these commands were checked
Our test farm ran each of the 1 command block marked runnable, exactly as written, with the answer checked against the expectation printed under it, against a fresh node of the published release r11.78.20261012.bc0d94f. A post is published only when every one of them passed there.