# Agent WebSocket Receive real-time agent response streaming via a persistent WebSocket connection. ## Connection URL ``` wss://socket.wiro.ai/v1 ``` Connect to this URL after calling the [Message / Send](/docs/agent-messaging) endpoint. Use the `agenttoken` from the send response to subscribe to the agent session. This is the same WebSocket server used for model tasks — you can subscribe to both task events and agent events on the same connection. ## Connection Flow 1. **Connect** — open a WebSocket connection to `wss://socket.wiro.ai/v1` 2. **Subscribe** — send an `agent_info` message with your `agenttoken` 3. **Receive** — listen for `agent_output` events as the agent streams its response 4. **Complete** — the `agent_end` event fires when the response is finished 5. **Close** — disconnect after processing the final response Subscribe message format: ```json { "type": "agent_info", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb" } ``` ## Event Types | Event Type | Description | |---|---| | `agent_subscribed` | Subscription confirmed. The server acknowledges your `agenttoken` and returns the current status. If the agent already started before you subscribed, accumulated text is included. | | `agent_start` | The agent has started processing your message. The underlying model is now generating a response. | | `agent_output` | A streaming response chunk. Emitted **multiple times** — each chunk contains the full accumulated text so far, plus real-time performance metrics. | | `agent_end` | Response complete. Contains the final accumulated text with total metrics. This is the event to listen for. | | `agent_error` | An error occurred during processing. The `message` field may be a plain string or a structured progress object depending on the error type. | | `agent_cancel` | The message was cancelled by the user before the agent finished responding. | ## Message Format Every WebSocket message is a JSON object with this base structure: ```json { "type": "agent_output", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb", "message": { ... }, "result": true } ``` The `type` field indicates the event. The `message` field varies by type — it's an empty string for lifecycle events, a structured progress object for output events, and a string or object for errors. ### agent_subscribed Sent immediately after the server accepts your subscription. The `status` field reflects where the agent currently is in its lifecycle. If the agent already began streaming before you connected, `debugoutput` contains any accumulated text. ```json { "type": "agent_subscribed", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb", "status": "agent_queue", "debugoutput": "", "result": true } ``` Possible `status` values: | Status | Meaning | |---|---| | `agent_queue` | Message is queued, waiting for the agent to pick it up. | | `agent_start` | Agent has started processing. | | `agent_output` | Agent is actively streaming. `debugoutput` will contain accumulated text. | | `agent_end` | Agent already finished. `debugoutput` contains the complete response. | | `agent_error` | Agent encountered an error. `debugoutput` may contain partial output. | | `agent_cancel` | Message was cancelled. `debugoutput` may contain partial output. | | `unknown` | Status could not be determined. Treat as an error. | ### agent_start Signals that the agent has begun generating a response. The `message` field is an empty string: ```json { "type": "agent_start", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb", "message": "", "result": true } ``` ### agent_output (streaming) Emitted multiple times as the agent generates its response. Each event contains the **full accumulated text** up to that point (not just the delta), along with real-time performance metrics: ```json { "type": "agent_output", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb", "message": { "type": "progressGenerate", "task": "Generate", "speed": "12.5", "speedType": "words/s", "elapsedTime": "2.4s", "tokenCount": 35, "wordCount": 28, "raw": "Here is the accumulated response text so far...", "thinking": [], "answer": ["Here is the accumulated response text so far..."], "isThinking": false }, "result": true } ``` The `raw` field contains the full response as a single string. The `answer` array contains the same text split into segments. To display streaming text, replace your UI content with `raw` (or join `answer`) on each event. ### agent_end Fires when the agent finishes responding. The structure is identical to `agent_output` — the `message` contains the final complete text with total metrics: ```json { "type": "agent_end", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb", "message": { "type": "progressGenerate", "task": "Generate", "speed": "14.2", "speedType": "words/s", "elapsedTime": "8.1s", "tokenCount": 156, "wordCount": 118, "raw": "The complete agent response text...", "thinking": [], "answer": ["The complete agent response text..."], "isThinking": false }, "result": true } ``` ### agent_error An error occurred during processing. The `message` field can take two forms: **String error** — a human-readable error description: ```json { "type": "agent_error", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb", "message": "Bridge timeout", "result": false } ``` Common string errors include `"Bridge timeout"`, `"OpenClaw returned HTTP 500"`, and `"Model not available"`. **Structured error** — a progress object where the response itself indicates failure (e.g. the model returned `"..."` or an error message): ```json { "type": "agent_error", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb", "message": { "type": "progressGenerate", "task": "Generate", "speed": "0", "speedType": "words/s", "elapsedTime": "1.2s", "tokenCount": 3, "wordCount": 1, "raw": "...", "thinking": [], "answer": ["..."], "isThinking": false }, "result": false } ``` ### agent_cancel Sent when the user cancels a message before the agent completes its response: ```json { "type": "agent_cancel", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb", "message": "The operation was aborted.", "result": false } ``` ## The `result` Field Every event includes a `result` boolean: | Value | Events | |---|---| | `true` | `agent_subscribed`, `agent_start`, `agent_output`, `agent_end` | | `false` | `agent_error`, `agent_cancel` | Use `result` to quickly determine whether the event represents a successful state. When `result` is `false`, inspect `message` for error details or cancellation context. ## Streaming Metrics Each `agent_output` and `agent_end` event includes real-time performance data in the `message` object: | Field | Type | Description | |---|---|---| | `speed` | string | Current generation speed (e.g. `"12.5"`). | | `speedType` | string | Speed unit — always `"words/s"` for agent responses. | | `elapsedTime` | string | Wall-clock time since the stream started (e.g. `"2.4s"`). | | `tokenCount` | number | Total tokens generated so far. | | `wordCount` | number | Total words in the accumulated response. | These metrics update with every `agent_output` event, allowing you to display a live speed indicator or progress bar in your UI. ## Thinking Model Support When the agent is backed by a thinking-capable model (e.g. DeepSeek-R1, QwQ), the response may include thinking blocks alongside the answer: ```json { "type": "agent_output", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb", "message": { "type": "progressGenerate", "task": "Generate", "speed": "8.3", "speedType": "words/s", "elapsedTime": "4.1s", "tokenCount": 89, "wordCount": 64, "raw": "Let me work through this step by step...Based on my analysis...", "thinking": ["Let me work through this step by step..."], "answer": ["Based on my analysis..."], "isThinking": true }, "result": true } ``` | Field | Description | |---|---| | `isThinking` | `true` while the model is in a thinking phase, `false` when emitting the answer. | | `thinking` | Array of thinking block text segments. Empty if the model does not use thinking. | | `answer` | Array of answer text segments. This is the user-facing response. | | `raw` | The unprocessed output including `` tags. Use `thinking` and `answer` instead for display. | **Rendering guidance:** - While `isThinking` is `true`, show a "Thinking..." indicator or render the `thinking` text in a collapsible block. - When `isThinking` becomes `false`, the model has finished reasoning and is now producing the answer. - On `agent_end`, join the `answer` array for the final display text. ## Full Integration Example A typical integration follows this pattern: call the REST API to send a message, then subscribe via WebSocket to stream the response. **Step 1 — Send a message via REST:** ```bash curl -X POST https://api.wiro.ai/v1/UserAgent/Message/Send \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "useragentguid": "your-useragent-guid", "message": "Explain quantum computing in simple terms", "sessionkey": "user-42" }' ``` The response includes an `agenttoken`: ```json { "result": true, "errors": [], "messageguid": "c3d4e5f6-a7b8-9012-cdef-345678901234", "agenttoken": "aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb", "status": "agent_queue" } ``` **Step 2 — Subscribe via WebSocket:** ```javascript const ws = new WebSocket('wss://socket.wiro.ai/v1'); ws.onopen = () => { ws.send(JSON.stringify({ type: 'agent_info', agenttoken: 'aB3xK9mR2pLqWzVn7tYhCd5sFgJkNb' })); }; ``` **Step 3 — Handle streaming events:** ``` → agent_subscribed { status: "agent_queue" } → agent_start { message: "" } → agent_output { message: { raw: "Quantum", wordCount: 1 } } → agent_output { message: { raw: "Quantum computing uses", wordCount: 3 } } → agent_output { message: { raw: "Quantum computing uses qubits...", wordCount: 28 } } → agent_end { message: { raw: "Quantum computing uses qubits that...", wordCount: 118 } } ``` Each `agent_output` contains the full accumulated text. Replace (don't append) your display content on each event. ## Code Examples ### JavaScript ```javascript const agentToken = 'your-agent-token'; const ws = new WebSocket('wss://socket.wiro.ai/v1'); ws.onopen = () => { console.log('Connected'); ws.send(JSON.stringify({ type: 'agent_info', agenttoken: agentToken })); }; ws.onmessage = (event) => { const msg = JSON.parse(event.data); console.log('Event:', msg.type); if (msg.type === 'agent_output') { console.log('Streaming:', msg.message?.raw); } if (msg.type === 'agent_end') { console.log('Final:', msg.message?.raw); ws.close(); } if (msg.type === 'agent_error') { console.error('Error:', msg.message); ws.close(); } }; ws.onerror = (err) => console.error('WebSocket error:', err); ws.onclose = () => console.log('Disconnected'); ``` ### Python ```python import asyncio import websockets import json async def listen_agent(agent_token): uri = "wss://socket.wiro.ai/v1" async with websockets.connect(uri) as ws: await ws.send(json.dumps({ "type": "agent_info", "agenttoken": agent_token })) print("Subscribed to agent session") async for message in ws: msg = json.loads(message) print(f"Event: {msg['type']}") if msg["type"] == "agent_output": print("Streaming:", msg["message"].get("raw")) elif msg["type"] == "agent_end": print("Final:", msg["message"].get("raw")) break elif msg["type"] in ("agent_error", "agent_cancel"): print("Error:", msg.get("message")) break asyncio.run(listen_agent("your-agent-token")) ``` ### Node.js ```javascript const WebSocket = require('ws'); const ws = new WebSocket('wss://socket.wiro.ai/v1'); ws.on('open', () => { ws.send(JSON.stringify({ type: 'agent_info', agenttoken: 'your-agent-token' })); }); ws.on('message', (data) => { const msg = JSON.parse(data.toString()); console.log('Event:', msg.type); if (msg.type === 'agent_output') { console.log('Streaming:', msg.message?.raw); } if (msg.type === 'agent_end') { console.log('Final:', msg.message?.raw); ws.close(); } }); ws.on('error', console.error); ws.on('close', () => console.log('Disconnected')); ``` ### PHP ```php send(json_encode([ "type" => "agent_info", "agenttoken" => "your-agent-token" ])); while (true) { $msg = json_decode($client->receive(), true); echo "Event: " . $msg["type"] . PHP_EOL; if ($msg["type"] === "agent_output") { echo "Streaming: " . ($msg["message"]["raw"] ?? "") . PHP_EOL; } if ($msg["type"] === "agent_end") { echo "Final: " . ($msg["message"]["raw"] ?? "") . PHP_EOL; break; } } $client->close(); ``` ### C\# ```csharp using System.Net.WebSockets; using System.Text; using System.Text.Json; using var ws = new ClientWebSocket(); await ws.ConnectAsync( new Uri("wss://socket.wiro.ai/v1"), CancellationToken.None); var subscribe = JsonSerializer.Serialize(new { type = "agent_info", agenttoken = "your-agent-token" }); await ws.SendAsync( Encoding.UTF8.GetBytes(subscribe), WebSocketMessageType.Text, true, CancellationToken.None); var buffer = new byte[8192]; while (ws.State == WebSocketState.Open) { var result = await ws.ReceiveAsync( buffer, CancellationToken.None); var json = Encoding.UTF8.GetString( buffer, 0, result.Count); using var doc = JsonDocument.Parse(json); var type = doc.RootElement .GetProperty("type").GetString(); Console.WriteLine("Event: " + type); if (type == "agent_end") { Console.WriteLine("Done!"); break; } } ``` ### Go ```go package main import ( "encoding/json" "fmt" "log" "github.com/gorilla/websocket" ) func main() { conn, _, err := websocket.DefaultDialer.Dial( "wss://socket.wiro.ai/v1", nil) if err != nil { log.Fatal(err) } defer conn.Close() sub, _ := json.Marshal(map[string]string{ "type": "agent_info", "agenttoken": "your-agent-token", }) conn.WriteMessage(websocket.TextMessage, sub) for { _, message, err := conn.ReadMessage() if err != nil { break } var msg map[string]interface{} json.Unmarshal(message, &msg) fmt.Println("Event:", msg["type"]) if msg["type"] == "agent_end" { fmt.Println("Done!") break } } } ``` ### Swift ```swift import Foundation let url = URL(string: "wss://socket.wiro.ai/v1")! let task = URLSession.shared.webSocketTask(with: url) task.resume() let subData = try! JSONSerialization.data( withJSONObject: [ "type": "agent_info", "agenttoken": "your-agent-token" ]) task.send(.string( String(data: subData, encoding: .utf8)! )) { _ in } func receive() { task.receive { result in switch result { case .success(let message): switch message { case .string(let text): let msg = try! JSONSerialization .jsonObject(with: text.data( using: .utf8)!) as! [String: Any] print("Event:", msg["type"] ?? "") if msg["type"] as? String == "agent_end" { print("Done!") return } case .data(let data): print("Binary:", data.count, "bytes") @unknown default: break } receive() case .failure(let error): print("Error:", error) } } } receive() ``` ### Kotlin ```kotlin // Requires: org.java-websocket:Java-WebSocket import org.java_websocket.client.WebSocketClient import org.java_websocket.handshake.ServerHandshake import java.net.URI import org.json.JSONObject val client = object : WebSocketClient( URI("wss://socket.wiro.ai/v1")) { override fun onOpen(h: ServerHandshake) { send(JSONObject(mapOf( "type" to "agent_info", "agenttoken" to "your-agent-token" )).toString()) } override fun onMessage(message: String) { val msg = JSONObject(message) println("Event: " + msg.getString("type")) if (msg.getString("type") == "agent_end") { println("Done!") close() } } override fun onClose( code: Int, reason: String, remote: Boolean ) { println("Disconnected") } override fun onError(ex: Exception) { ex.printStackTrace() } } client.connect() ``` ### Dart ```dart import 'dart:convert'; import 'package:web_socket_channel/web_socket_channel.dart'; final channel = WebSocketChannel.connect( Uri.parse('wss://socket.wiro.ai/v1'), ); channel.sink.add(jsonEncode({ 'type': 'agent_info', 'agenttoken': 'your-agent-token', })); channel.stream.listen((message) { final msg = jsonDecode(message); print('Event: ' + msg['type'].toString()); if (msg['type'] == 'agent_output') { print('Streaming: ' + (msg['message']?['raw'] ?? '')); } if (msg['type'] == 'agent_end') { print('Done!'); channel.sink.close(); } }); ``` ## Quick Reference ```json // Subscribe {"type": "agent_info", "agenttoken": "aB3xK9..."} // agent_subscribed {"type": "agent_subscribed", "agenttoken": "aB3xK9...", "status": "agent_queue", "result": true} // agent_start {"type": "agent_start", "agenttoken": "aB3xK9...", "message": "", "result": true} // agent_output (streaming — emitted multiple times) {"type": "agent_output", "agenttoken": "aB3xK9...", "message": {"raw": "Accumulated text...", "speed": "12.5", "wordCount": 28}, "result": true} // agent_end (final response) {"type": "agent_end", "agenttoken": "aB3xK9...", "message": {"raw": "Complete response...", "speed": "14.2", "wordCount": 118}, "result": true} // agent_error {"type": "agent_error", "agenttoken": "aB3xK9...", "message": "Bridge timeout", "result": false} // agent_cancel {"type": "agent_cancel", "agenttoken": "aB3xK9...", "message": "The operation was aborted.", "result": false} ```