# 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}
```