The daemon streams real-time events over a WebSocket connection. This is the recommended way to build interactive clients — it provides lower latency than HTTP polling and delivers every event as it happens.
Connecting
WS/v3/ws?token=T
Opens a persistent WebSocket connection. The daemon pushes all session events for the authenticated client. The token is passed as a query parameter.
Connect using the daemon token from ~/.config/opta/daemon/state.json:
One WebSocket connection receives events for all sessions. You do not need a separate connection per session. Filter events client-side using the data.sessionId field.
Envelope Format
Every WebSocket message is a JSON object called a V3Envelope. It always contains three fields:
The seq field is globally ordered across all sessions. It is the key to reliable reconnection — clients track the last received seq and pass it as afterSeq when reconnecting.
Event Types
The daemon emits the following event types. They are grouped by category.
Session Events
Sent immediately after WebSocket connection or session creation. Contains the full session state.
If the WebSocket connection drops, clients should reconnect using the last received sequence number to avoid re-processing events. The daemon supports cursor-based reconnection via the afterSeq query parameter.
When you reconnect with afterSeq, the daemon replays only events with a sequence number greater than the value you provide. This guarantees no duplicates and no gaps.
Client Example
Here is a complete example of a minimal TypeScript client that connects to the daemon, submits a turn, and streams the response:
daemon-client.ts
import { readFileSync } from "fs";
import { join } from "path";
import { homedir } from "os";
// Read token from state file
const statePath = join(homedir(), ".config/opta/daemon/state.json");
const state = JSON.parse(readFileSync(statePath, "utf-8"));
const { token, port } = state;
// Connect WebSocket
const ws = new WebSocket(`ws://127.0.0.1:${port}/v3/ws?token=${token}`);
ws.onopen = async () => {
// Create a session
const res = await fetch(`http://127.0.0.1:${port}/v3/sessions`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ mode: "chat" }),
});
const { sessionId } = await res.json();
// Submit a turn
await fetch(`http://127.0.0.1:${port}/v3/sessions/${sessionId}/turns`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ content: "What is Opta?" }),
});
};
ws.onmessage = (e) => {
const { event, data } = JSON.parse(e.data);
switch (event) {
case "turn.token":
process.stdout.write(data.token);
break;
case "turn.done":
console.log("\n--- Done ---");
console.log(`${data.stats.tokens} tokens at ${data.stats.speed} tok/s`);
ws.close();
break;
case "turn.error":
console.error("Error:", data.error.message);
ws.close();
break;
}
};