WebSocket Protocol¶
The chat interface communicates with the server over a WebSocket connection at /ws/{session_id}.
Connection¶
Authentication is enforced on the HTTP upgrade request using the aide_user session cookie. Connections without a valid session are rejected.
On connect, the server immediately sends a models event.
Events: server → client¶
models¶
Sent immediately on connect. Contains all available models and the default selection.
{
"type": "models",
"models": [
{
"id": "anthropic:claude-sonnet-4-6",
"name": "Claude Sonnet 4.6",
"provider": "anthropic",
"context_length": 200000,
"pricing": {"prompt": 3.0, "completion": 15.0},
"capabilities": {"vision": true, "tools": true, "online": false}
}
],
"default": "anthropic:claude-sonnet-4-6"
}
text¶
Streaming text from the model. Multiple text events may arrive for a single response.
tool_start¶
The agent is about to execute a tool.
{
"type": "tool_start",
"call_id": "tc_abc123",
"tool_name": "web",
"arguments": {"operation": "search", "query": "Oslo weather"}
}
tool_done¶
A tool has finished executing.
{
"type": "tool_done",
"call_id": "tc_abc123",
"tool_name": "web",
"success": true,
"result_summary": "{'query': 'Oslo weather', 'results': [...]...}",
"confirmed": false
}
confirmation_required¶
The agent needs user approval before proceeding. The agent loop is paused until the client sends a confirm message.
{
"type": "confirmation_required",
"call_id": "tc_def456",
"tool_name": "email",
"arguments": {"operation": "send_email", "to": "alice@example.com", "subject": "..."},
"description": "Send email to alice@example.com: 'Meeting tomorrow'"
}
images¶
Image(s) generated by an image-generation model.
done¶
The agent loop has finished. No more events will be sent for this turn.
{
"type": "done",
"text": "The weather in Oslo today is 12°C and cloudy.",
"tool_calls_made": 1,
"usage": {"input_tokens": 1234, "output_tokens": 56}
}
error¶
A fatal error occurred. The run has been aborted.
Messages: client → server¶
message¶
Send a user message to start an agent run.
{
"type": "message",
"text": "What's the weather in Oslo?",
"model": "anthropic:claude-sonnet-4-6",
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"attachments": [
{
"media_type": "image/jpeg",
"data": "<base64-encoded image>"
}
]
}
model— optional; overrides the default for this turnsession_id— optional; if omitted, a new UUID is generatedattachments— optional; list of image or PDF attachments
confirm¶
Approve or deny a tool call that requires confirmation.
response: true— approved, proceed with the tool callresponse: false— denied, tool call is cancelled
Server architecture¶
The WebSocket handler in main.py runs two concurrent asyncio tasks per connection:
receiver(): Reads incoming messages from the client and handles them:type: confirm→ immediately resolves the pendingconfirmation_managerrequest (unblocks the agent loop without waiting for the sender)-
type: message→ puts the message in a queue -
sender(): Processes the message queue through the agent loop, serialises events to JSON, and sends them to the client
The receiver() and sender() run as asyncio.gather(receiver(), sender()) — fully concurrent.
Why the split?
The confirmation modal pattern requires the browser to send a confirm message while the agent loop is paused. If receiver and sender were sequential, the confirmation message would never be read until after the timeout. By running them concurrently, the receiver can unblock the agent loop while the sender is awaiting the confirmation.