Version: v1
Endpoint: ws://host/ws/v1/sessions/{session_id}
Log stream: ws://host/ws/v1/logs
Related documents:
docs/api/overview.md— API orientation, quick start, authentication modeldocs/api/client-contract.md— TypeScript types and full WebSocket usage example (SessionSocketclass)docs/api/webui-development-guide.md— React integration patterns for streaming
WebSockets are used exclusively for real-time streaming surfaces:
- Token-by-token agent output streaming (session WebSocket)
- Tool execution progress (session WebSocket)
- Tool confirmation dialogs (session WebSocket)
- Live log streaming (log WebSocket, admin only)
All other operations use the REST API.
The JWT bearer token must be provided on connection. Two methods are supported:
Method 1 — Authorization header (preferred):
Authorization: Bearer <jwt>
Method 2 — Query parameter (browser WebSocket API fallback):
ws://host/ws/v1/sessions/{id}?token=<jwt>
If the token is missing, malformed, or invalid the server closes the connection with:
- Close code
4001— unauthorized - Close code
4003— forbidden (valid token but wrong role/ownership)
All messages in both directions use this JSON envelope:
{
"type": "<message_type>",
"session_id": "<uuid>",
"payload": { ... },
"seq": 42,
"ts": "2026-03-04T12:34:56.789Z"
}| Field | Type | Description |
|---|---|---|
| type | string | Message type discriminator (see Section 4) |
| session_id | string | UUID v4 of the session this message belongs to |
| payload | object | Type-specific payload (see Section 4) |
| seq | int | Monotonically increasing per-connection sequence number |
| ts | string | ISO 8601 UTC server timestamp |
{
"type": "<message_type>",
"payload": { ... }
}Emitted once per output token during agent generation.
The frontend appends text to the response buffer.
{
"type": "token",
"session_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"payload": {
"text": " Paris",
"final": true
},
"seq": 42,
"ts": "2026-03-04T12:34:56.789Z"
}| Payload field | Type | Description |
|---|---|---|
| text | string | Incremental token text |
| final | bool | true when this token is part of the final response (after all tool calls complete). false during preamble text before tool calls. Use this to distinguish intermediate reasoning from the actual answer. Only meaningful when tool_call_count > 0; false until the first tool call is seen. |
Emitted when the agent invokes a tool.
{
"type": "tool_start",
"session_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"payload": {
"tool": "web_search",
"tool_call_id": "call_abc123",
"input": {
"query": "climate policy 2025"
}
},
"seq": 43,
"ts": "2026-03-04T12:34:57.001Z"
}| Payload field | Type | Description |
|---|---|---|
| tool | string | Tool name |
| tool_call_id | string | Unique invocation ID (links to tool_end) |
| input | object | Arguments passed to the tool |
Emitted when a tool returns (success or error).
{
"type": "tool_end",
"session_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"payload": {
"tool": "web_search",
"tool_call_id": "call_abc123",
"duration_ms": 340,
"error": null
},
"seq": 44,
"ts": "2026-03-04T12:34:57.341Z"
}| Payload field | Type | Description |
|---|---|---|
| tool | string | Tool name |
| tool_call_id | string | Unique invocation ID (matches tool_start) |
| duration_ms | int | Execution time in milliseconds |
| error | string/null | Error description on failure; null on success |
Emitted when a safety-wrapped tool requires human approval before execution.
The agent is blocked until the client sends a tool_confirm response.
The frontend must display a confirmation dialog immediately.
{
"type": "tool_confirm_request",
"session_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"payload": {
"confirmation_id": "conf_3f2504e0",
"tool": "write_file",
"parameters": {
"path": "/home/user/report.md",
"content": "# Climate Report\n..."
},
"message": "Write 2 KB to /home/user/report.md"
},
"seq": 45,
"ts": "2026-03-04T12:34:58.001Z"
}| Payload field | Type | Description |
|---|---|---|
| confirmation_id | string | Opaque ID to echo in the tool_confirm response |
| tool | string | Tool requiring confirmation |
| parameters | object | Tool call parameters (large values sorted last) |
| message | string | Human-readable description of the action |
Emitted when the agent transitions between execution phases.
{
"type": "agent_state",
"session_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"payload": {
"state": "thinking"
},
"seq": 46,
"ts": "2026-03-04T12:34:58.050Z"
}| Payload field | Type | Description |
|---|---|---|
| state | string | One of: idle, thinking, analyzing, researching, deep_thinking, writing, delegating, done, error |
State transitions by mode:
Normal mode:
idle → thinking → done
Think mode (forced deep reasoning):
idle → thinking → analyzing → researching (if web tools used) → deep_thinking → done
Delegate mode (forced task delegation):
idle → thinking → delegating → done
Emitted when the background memory subsystem runs a summarization or compression pass.
{
"type": "memory_update",
"session_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"payload": {
"mode": "conversation",
"tokens_used": 1200,
"summarized": true
},
"seq": 47,
"ts": "2026-03-04T12:34:58.200Z"
}| Payload field | Type | Description |
|---|---|---|
| mode | string | Active memory mode |
| tokens_used | int | Estimated context token count after update |
| summarized | boolean | True when a LLM summarization pass ran |
Emitted when the agent encounters an error during the turn (not a WebSocket protocol error). The connection stays open; the frontend should display the error in the chat UI.
{
"type": "error",
"session_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"payload": {
"code": "TOOL_EXPANSION_FAILED",
"message": "web_search could not be loaded: API key not configured."
},
"seq": 48,
"ts": "2026-03-04T12:34:58.300Z"
}| Payload field | Type | Description |
|---|---|---|
| code | string | Machine-readable error code |
| message | string | Human-readable description safe to display |
Emitted when the agent turn finishes (successfully or after an error recovery). Always the last message for a turn.
{
"type": "done",
"session_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"payload": {
"message_id": "7a3c1b2e-5d4f-11ee-be56-0242ac120002",
"total_tokens": 1800,
"input_tokens": 1420,
"output_tokens": 380,
"duration_ms": 4200,
"tool_calls": 3
},
"seq": 49,
"ts": "2026-03-04T12:34:59.200Z"
}| Payload field | Type | Description |
|---|---|---|
| message_id | string | UUID of the AI message created |
| total_tokens | int | Total tokens for this turn |
| input_tokens | int | Input tokens |
| output_tokens | int | Output tokens |
| duration_ms | int | Wall-clock turn duration in milliseconds |
| tool_calls | int | Number of tool invocations |
Response to a client ping message.
{
"type": "pong",
"session_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"payload": {},
"seq": 50,
"ts": "2026-03-04T12:35:00.001Z"
}Emitted on the /ws/v1/logs endpoint only. Note: log stream messages are plain JSON dicts — they do NOT use the common ServerMessage envelope (no session_id, seq, or ts fields).
{
"type": "log_line",
"level": "INFO",
"logger": "cogtrix.orchestration.runner",
"message": "Agent turn completed in 4.2s",
"timestamp": "2026-03-04T12:34:59.200Z"
}Alternative to the REST POST /api/v1/sessions/{id}/messages.
Useful for low-latency chat UIs that want to avoid an extra HTTP round-trip.
{
"type": "user_message",
"payload": {
"text": "What is the capital of France?",
"mode": "normal"
}
}| Payload field | Type | Description |
|---|---|---|
| text | string | User message text (1–65536 chars) |
| mode | string | normal, think, or delegate |
Must be sent in response to a tool_confirm_request message.
{
"type": "tool_confirm",
"payload": {
"confirmation_id": "conf_3f2504e0",
"action": "allow"
}
}| Payload field | Type | Description |
|---|---|---|
| confirmation_id | string | The confirmation_id from the tool_confirm_request |
| action | string | allow, deny, allow_all, disable, forbid_all, or cancel |
Action semantics (mirrors CLI options):
| Action | CLI key | Description |
|---|---|---|
| allow | y | Allow this invocation once |
| deny | n | Deny this invocation; agent may retry |
| allow_all | a | Auto-approve this tool for the entire session |
| disable | d | Disable this tool for the entire session |
| forbid_all | f | Block all further tool requests this turn |
| cancel | c | Cancel the current agent workflow entirely |
Must be sent every 30 seconds. Connections silent for 90 seconds are dropped.
{
"type": "ping",
"payload": {}
}Signals the server to abort the in-progress agent turn.
The server transitions to agent_state: idle first, then sends an error message with code CANCELLED.
A done message is not sent. The connection remains open for the next turn.
{
"type": "cancel",
"payload": {}
}CLIENT SERVER
| |
| ---- WS connect + JWT -----------> |
| | validate token & session ownership
| <--- agent_state (idle) ---------- |
| |
| ---- user_message / POST REST ---> | [or message sent via REST]
| |
| <--- agent_state (thinking) ------ |
| <--- tool_start (web_search) ----- |
| <--- tool_confirm_request -------- | [if tool needs confirmation]
| ---- tool_confirm (allow) -------> |
| <--- tool_end (web_search) ------- |
| <--- agent_state (analyzing) ----- | [think mode: classifying task]
| <--- agent_state (researching) --- | [think mode: research delegate]
| <--- agent_state (deep_thinking) - | [think mode: deep reasoning]
| <--- agent_state (delegating) ---- | [delegate mode: parallel delegation]
| <--- agent_state (writing) ------- |
| <--- token ("The capital") ------- |
| <--- token (" is Paris") --------- |
| <--- memory_update --------------- | [if summarization ran]
| <--- agent_state (done) ---------- |
| <--- done ------------------------- |
| |
| ---- ping ------------------------> | [every 30s]
| <--- pong ------------------------- |
| |
| [... next turn ...] |
If a user_message arrives while an agent turn is already running, the server sends
an error payload with code TURN_IN_PROGRESS. The connection remains open and the
in-progress turn is unaffected. Wait for the done message before sending another message.
If the agent raises an unrecoverable exception during a turn:
- Server sends
type: errorwith a descriptive error code and message. - Server transitions to
agent_state: error, thenagent_state: idle. - The connection remains open for the next turn.
| Close code | Meaning |
|---|---|
| 4000 | Session registry unavailable — server is still starting up |
| 4001 | Unauthorized — no token, invalid signature |
| 4003 | Forbidden — valid token, wrong role or session ownership |
| 4004 | Session not found — session does not exist or was archived |
| 1000 | Normal closure |
| 1001 | Server going away (shutdown); also used when a second connection replaces the first |
| 1011 | Internal server error |
The seq field enables the frontend to detect dropped messages and recover:
- Store
last_seen_seqin memory (reset to -1 on new page load). - On reconnect, send
?last_seq=<last_seen_seq>as a query parameter. - The server replays buffered messages with
seq > last_seq(buffer kept 30 s post-disconnect). - If
last_seqis too old (buffer expired), the server sends the current state only.
Recommended reconnect strategy:
- Immediate reconnect on first disconnect.
- Exponential backoff: 1s → 2s → 4s → 8s → 16s → cap at 30s.
- Stop retrying after 10 consecutive failures; show the user an error.
- On successful reconnect, fetch message history via REST to fill any gap.
Admin-only endpoint for live log streaming.
ws://host/ws/v1/logs?token=<jwt>&level=INFO
Query parameters:
token— JWT bearer token (admin required).level— minimum log level to stream: DEBUG, INFO, WARNING, ERROR (default INFO).
Streams log_line messages as they are emitted.
Same keepalive timing applies (drop after 90 s of silence). Note: the log stream uses a plain text "ping" string for keepalive (not the ClientMessage JSON envelope used by session WebSockets). The server responds with {"type": "pong"}.