How do I stream responses?
Daemion streams responses over WebSocket. Connect to /stream, then send a chat turn via POST /chat — events flow back in real time as the agent thinks, speaks, and calls tools.
Connect the WebSocket
The WebSocket endpoint is ws://localhost:3001/stream. Pass the bearer token as a query parameter — the gateway validates it with a timing-safe comparison before accepting the connection.
Optionally pass threadId to subscribe to events for a specific thread only. Without it, you receive global events (extension changes, thread updates) but not thread-scoped streaming events.
// npm install ws const WebSocket = require(‘ws’);
const token = process.env.DAEMION_TOKEN; const threadId = ‘thr_01abc123’;
const ws = new WebSocket(
ws://localhost:3001/stream?token=${token}&threadId=${threadId}
);
ws.on(‘open’, () => { console.log(‘connected’); });
ws.on(‘message’, (data) => { const event = JSON.parse(data.toString()); handleEvent(event); });
ws.on(‘close’, () => console.log(‘disconnected’)); ws.on(‘error’, (err) => console.error(‘ws error’, err.message));
Send a chat turn
Once connected, trigger streaming by posting a turn:
curl -X POST http://localhost:3001/chat
-H “Authorization: Bearer $DAEMION_TOKEN”
-H “Content-Type: application/json”
-d ’{“thread_id”: “thr_01abc123”, “content”: “Summarise recent activity”}’
Events begin arriving on the WebSocket immediately.
Handle the 12 event types
Every event has a type field. Here is a complete handler covering all 12:
function handleEvent(event) { switch (event.type) { // Fired once on WebSocket connection — confirms the thread subscription case ‘connected’: console.log(‘subscribed to thread’, event.threadId); break;
// A completed turn was saved (user or assistant) case ‘message’: console.log(‘new turn saved’, event.message.id); break;
// Agent started generating — marks the beginning of a response case ‘start’: console.log(‘agent started’, event.messageId, ‘model:’, event.model); break;
// Incremental text — append to your buffer case ‘text-delta’: process.stdout.write(event.delta); break;
// Agent is calling a tool case ‘tool-start’: console.log(‘tool call:’, event.tool, ‘input:’, event.input); break;
// Tool call finished case ‘tool-end’: console.log(‘tool result:’, event.tool, ‘output:’, event.output); break;
// Turn complete — includes cost and token counts case ‘finish’: console.log( ‘done. cost: $’ + event.costUsd.toFixed(4), ‘tokens in/out:’, event.inputTokens, ’/’, event.outputTokens ); break;
// Agent encountered an error mid-stream case ‘error’: console.error(‘agent error:’, event.error); break;
// Agent was stopped early (e.g. user cancelled) case ‘stopped’: console.log(‘agent stopped’, event.messageId); break;
// Non-fatal warning from the gateway case ‘warning’: console.warn(‘gateway warning:’, event.text); break;
// An extension was created, updated, deleted, or toggled case ‘extension-changed’: console.log(‘extension’, event.action, event.extension); break;
// A thread’s title was updated case ‘thread-updated’: console.log(‘thread title updated:’, event.threadId, event.title); break; } }
Late-join replay
If you connect to a thread while a response is already in progress, the gateway replays recent streaming events (up to the last 50, within a 5-minute window) so you catch up without missing anything. The replay delivers start, text-delta, tool-start, tool-end, finish, and error events in order.
Thread scope vs. global scope
Connect with threadId to receive thread-scoped events: message, start, text-delta, tool-start, tool-end, finish, error, stopped, warning.
Connect without threadId to receive global events only: connected, extension-changed, thread-updated. These are broadcast to all connected clients regardless of thread.
Common questions
threadId. The replay buffer will catch you up on any events you missed (within 5 minutes of the original stream).ping events and call ws.pong().finish, error, or stopped. Any of those three signals the end of a response. The finish event also carries cost and token counts.What can go wrong
401 Unauthorized (connection refused) — The token query parameter is missing, wrong, or a different length than the stored token. The gateway uses a timing-safe comparison — even an extra character causes a rejection. Read the token from ~/.daemion/.gateway-token on the gateway machine and URL-encode it.
No events after POST /chat — Confirm threadId in the WebSocket URL matches the thread_id in the POST body exactly. A mismatch means events are broadcast to the thread’s subscribers, not to your global-scope connection.
Connection closes immediately — The gateway is not running, or it was restarted since you connected. Reconnect and re-pair if necessary.
What’s next?
- WebSocket API reference — full event type definitions and connection options