WebSocket
Cortex provides a WebSocket endpoint for real-time event delivery. Events include agent status changes, task updates, swarm activity, and chat messages.
Connection
wss://api.cortex.acrobi.com/api/ws?token=<JWT>The connection requires a valid JWT access token passed as a query parameter. The server validates the token and extracts the user ID for channel scoping.
Connection Example (JavaScript)
const ws = new WebSocket(`wss://api.cortex.acrobi.com/api/ws?token=${accessToken}`);
ws.onopen = () => {
console.log('Connected to Cortex WebSocket');
// Subscribe to channels
ws.send(JSON.stringify({ type: 'subscribe', channelId: 'agent:my-agent' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Event:', data.type, data.payload);
};
ws.onclose = (event) => {
console.log('Disconnected:', event.code, event.reason);
// Reconnect with exponential backoff
};Channel Subscriptions
After connecting, subscribe to specific channels to receive relevant events:
{ "type": "subscribe", "channelId": "agent:my-coder-agent" }The server confirms with:
{ "type": "subscribed", "channelId": "agent:my-coder-agent" }Channel Types
| Channel Pattern | Events Received |
|---|---|
agent:<name> | Status changes, execution updates for a specific agent |
swarm:<id> | Swarm phase changes, agent assignments, task progress |
project:<id> | All activity within a project |
org:<id> | Organization-wide events |
task:<id> | Updates to a specific task |
Event Types
Agent Events
| Event | Trigger | Payload |
|---|---|---|
agent.spawned | Agent execution started | { agentId, agentName, taskId, timestamp } |
agent.completed | Agent execution finished | { agentId, agentName, taskId, result, duration, timestamp } |
agent.failed | Agent execution errored | { agentId, agentName, taskId, error, timestamp } |
agent.status | Agent status changed (error/blocked only) | { agentId, agentName, status, message, timestamp } |
Task Events
| Event | Trigger | Payload |
|---|---|---|
task.created | New task created | { taskId, title, projectId, timestamp } |
task.status_changed | Task status updated | { taskId, oldStatus, newStatus, timestamp } |
task.updated | Task details modified | { taskId, changes, timestamp } |
task.deleted | Task removed | { taskId, timestamp } |
Swarm/Chief Events
| Event | Trigger | Payload |
|---|---|---|
chief.plan.created | Chief created execution plan | { planId, tasks, timestamp } |
chief.task.assigned | Chief assigned task to agent | { taskId, agentName, timestamp } |
chief.task.progress | Task progress update | { taskId, progress, message, timestamp } |
swarm.phase.started | Swarm entered new phase | { swarmId, phase, timestamp } |
swarm.phase.completed | Swarm phase finished | { swarmId, phase, results, timestamp } |
Quality Gate Events
| Event | Trigger | Payload |
|---|---|---|
gate.passed | Quality gate passed | { taskId, gate, timestamp } |
gate.failed | Quality gate failed | { taskId, gate, reason, timestamp } |
Chat Events
| Event | Trigger | Payload |
|---|---|---|
chat.message | New chat message | { messageId, agentId, content, timestamp } |
Architecture
The WebSocket system runs on a single Cloudflare Durable Object (WebSocketRoom):
- Zero persistent storage — all connection state is in-memory (
Map<WebSocket, Session>) - Channel-scoped subscriptions — clients only receive events they subscribed to
- Fire-and-forget broadcasts — events are pushed without delivery confirmation
- Cost: ~$1.50/month at 1,000 concurrent connections
When a connection closes (client disconnect, error, or DO hibernation), all session state is lost. Clients should implement reconnection with exponential backoff.
For Desktop Users
The Electron desktop app includes an embedded local WebSocket server. Desktop users get real-time events locally without cloud WebSocket connections.
Reconnection Strategy
let reconnectDelay = 1000; // Start at 1s
const maxDelay = 30000; // Cap at 30s
function connect() {
const ws = new WebSocket(url);
ws.onopen = () => {
reconnectDelay = 1000; // Reset on success
// Re-subscribe to channels
};
ws.onclose = () => {
setTimeout(() => {
reconnectDelay = Math.min(reconnectDelay * 2, maxDelay);
connect();
}, reconnectDelay);
};
}Scaling
See Scaling Architecture for how Cortex handles 50,000+ concurrent connections through per-org DO sharding and the desktop local-first approach.