WebSocket Messages Reference
This is a comprehensive reference of every message type that flows through Relay's WebSocket connections.
Message Direction Legend
- App → Relay: Apps send these messages to Relay
- Relay → App: Relay sends these to apps
- Agent → Relay: Agents send these to Relay
- Relay → Agent: Relay sends these to agents
- Bidirectional: Either direction
App-to-Relay Messages
Event
Send an event to an agent.
Direction: App → Relay
Schema:
{
"type": "event",
"agent_id": "string (required)",
"thread_id": "string (required)",
"payload": "object (required, max 64KB)"
}
Fields:
type: Always"event"agent_id: Target agent (e.g., "athena", "klyve")thread_id: Thread/conversation ID (e.g., "task-123", "lesson-101")payload: Application-specific data, forwarded untouched to agent
Example:
{
"type": "event",
"agent_id": "athena",
"thread_id": "task-123",
"payload": {
"event": "comment.mention",
"message": "@athena summarize this",
"task_id": "task-123",
"task_title": "Q2 Roadmap"
}
}
Discover
Request list of available agents.
Direction: App → Relay
Schema:
{
"type": "discover"
}
Response: Agents message
Example:
{ "type": "discover" }
Ping
Heartbeat request.
Direction: Bidirectional
Schema:
{
"type": "ping"
}
Response: Pong message
Example:
{ "type": "ping" }
Relay-to-App Messages
Accepted
Event has been accepted for delivery.
Direction: Relay → App
Schema:
{
"type": "accepted",
"event_id": "string",
"agent_id": "string",
"session_key": "string",
"status": "accepted"
}
Fields:
event_id: Relay-generated unique ID for this event. Your app does NOT generate this — Relay creates it and returns it here. Use it to correlate all futuretoken,reply, anderrormessages for this event.agent_id: Which agent received itsession_key: Internal session identifier (for reference)status: Always"accepted"
Example:
{
"type": "accepted",
"event_id": "evt_k9p2m",
"agent_id": "athena",
"session_key": "relay:athena:portal:task-123",
"status": "accepted"
}
Token
Streaming token from agent.
Direction: Relay → App
Schema:
{
"type": "token",
"event_id": "string",
"agent_id": "string",
"token": "string"
}
Fields:
event_id: Matches the original eventagent_id: Which agent generated ittoken: Text fragment (typically 1-5 words)
Multiple tokens will arrive as separate messages:
{ "type": "token", "event_id": "evt_k9p2m", "agent_id": "athena", "token": "Here" }
{ "type": "token", "event_id": "evt_k9p2m", "agent_id": "athena", "token": "'s the" }
{ "type": "token", "event_id": "evt_k9p2m", "agent_id": "athena", "token": " summary" }
Reply
Final response from agent.
Direction: Relay → App
Schema:
{
"type": "reply",
"event_id": "string",
"agent_id": "string",
"thread_id": "string",
"reply": "string",
"payload": "object",
"metadata": {
"tokens_used": "number",
"model": "string",
"latency_ms": "number",
"session_key": "string"
}
}
Fields:
event_id: Matches the original eventagent_id: Which agent repliedthread_id: Original thread_id from eventreply: Complete response textpayload: Echo of original payloadmetadata.tokens_used: Token countmetadata.model: Model that generated responsemetadata.latency_ms: End-to-end latencymetadata.session_key: Session identifier
Example:
{
"type": "reply",
"event_id": "evt_k9p2m",
"agent_id": "athena",
"thread_id": "task-123",
"reply": "Here's the summary of the Q2 Roadmap Review...",
"payload": { ... },
"metadata": {
"tokens_used": 1500,
"model": "claude-sonnet",
"latency_ms": 2300,
"session_key": "relay:athena:portal:task-123"
}
}
Error
Error response for an event.
Direction: Relay → App
Schema:
{
"type": "error",
"event_id": "string | null",
"agent_id": "string",
"error": "string",
"code": "string"
}
Fields:
event_id: Matches the original event (may be null for handshake errors)agent_id: Which agent had the errorerror: Human-readable descriptioncode: Error code (see Error Codes)
Example:
{
"type": "error",
"event_id": "evt_k9p2m",
"agent_id": "athena",
"error": "Agent not connected",
"code": "AGENT_OFFLINE"
}
Agents
List of available agents.
Direction: Relay → App
Schema:
{
"type": "agents",
"agents": [
{
"agent_id": "string",
"name": "string",
"description": "string"
}
]
}
Fields:
agents[].agent_id: Agent identifieragents[].name: Display nameagents[].description: What the agent does
Example:
{
"type": "agents",
"agents": [
{
"agent_id": "athena_k9p2m3",
"name": "Athena",
"description": "Personal EA, general tasks"
},
{
"agent_id": "klyve_x8f4n2",
"name": "Klyve",
"description": "Technical workflows"
}
]
}
Pong
Heartbeat response.
Direction: Relay → App
Schema:
{
"type": "pong"
}
Example:
{ "type": "pong" }
Agent-to-Relay Messages
Token (Agent)
Stream token back to Relay.
Direction: Agent → Relay
Schema:
{
"type": "token",
"event_id": "string",
"token": "string"
}
Fields:
event_id: Echo from incoming eventtoken: Text fragment
Note: Agent version doesn't include agent_id (Relay knows from auth token)
Example:
{ "type": "token", "event_id": "evt_k9p2m", "token": "Here" }
Reply (Agent)
Final response from agent.
Direction: Agent → Relay
Schema:
{
"type": "reply",
"event_id": "string",
"content": "string",
"done": true,
"metadata": {
"tokens_used": "number",
"model": "string",
"latency_ms": "number"
}
}
Fields:
event_id: Echo from incoming eventcontent: Complete response textdone: Alwaystruemetadata: Optional performance metrics
Example:
{
"type": "reply",
"event_id": "evt_k9p2m",
"content": "Here's the summary...",
"done": true,
"metadata": {
"tokens_used": 1500,
"model": "claude-sonnet",
"latency_ms": 2300
}
}
Error (Agent)
Error from agent.
Direction: Agent → Relay
Schema:
{
"type": "error",
"event_id": "string",
"error": "string",
"code": "string"
}
Fields:
event_id: Echo from incoming eventerror: Human-readable descriptioncode: Error code
Example:
{
"type": "error",
"event_id": "evt_k9p2m",
"error": "Session timeout",
"code": "AGENT_TIMEOUT"
}
Ping (Agent)
Heartbeat from agent.
Direction: Agent → Relay
Schema:
{
"type": "ping"
}
Relay-to-Agent Messages
Event (Relay to Agent)
Incoming event for agent.
Direction: Relay → Agent
Schema:
{
"type": "event",
"event_id": "string",
"app_id": "string",
"thread_id": "string",
"session_key": "string",
"payload": "object"
}
Fields:
event_id: Unique event ID (echo in replies)app_id: Which app sent it (e.g., "portal")thread_id: Thread from the eventsession_key:relay:{app_id}:{thread_id}(for session continuity)payload: Application payload (untouched by Relay)
Note: No agent_id — agent already knows who it is from auth token
Example:
{
"type": "event",
"event_id": "evt_k9p2m",
"app_id": "portal",
"thread_id": "task-123",
"session_key": "relay:portal:task-123",
"payload": {
"event": "comment.mention",
"message": "@athena summarize",
"task_id": "task-123"
}
}
Pong (Relay to Agent)
Heartbeat response.
Direction: Relay → Agent
Schema:
{
"type": "pong"
}
Error Codes
| Code | Meaning | Retryable |
|---|---|---|
AGENT_OFFLINE | Agent not connected | Yes |
AGENT_NOT_ALLOWED | App not allowlisted | No |
AGENT_TIMEOUT | Agent response timeout | Maybe |
PAYLOAD_TOO_LARGE | Event exceeds 64KB | No |
RATE_LIMITED | Rate limit exceeded | Yes |
INVALID_EVENT | Malformed event | No |
RELAY_INTERNAL_ERROR | Server error | Yes |
See Error Codes for detailed information on each.
Message Flow Examples
Happy Path (App → Agent → App)
1. App: { "type": "event", "agent_id": "athena", ... }
2. Relay → App: { "type": "accepted", "event_id": "evt_k9p2m" }
3. Relay → Agent: { "type": "event", "event_id": "evt_k9p2m", ... }
4. Agent → Relay: { "type": "token", "event_id": "evt_k9p2m", "token": "Here" }
5. Relay → App: { "type": "token", "event_id": "evt_k9p2m", "token": "Here" }
6. Agent → Relay: { "type": "token", "event_id": "evt_k9p2m", "token": "'s the" }
7. Relay → App: { "type": "token", "event_id": "evt_k9p2m", "token": "'s the" }
8. Agent → Relay: { "type": "reply", "event_id": "evt_k9p2m", "content": "..." }
9. Relay → App: { "type": "reply", "event_id": "evt_k9p2m", "reply": "..." }
Agent Offline
1. App: { "type": "event", "agent_id": "athena", ... }
2. Relay → App: { "type": "error", "code": "AGENT_OFFLINE", ... }
Agent Discovery
1. App: { "type": "discover" }
2. Relay → App: { "type": "agents", "agents": [...] }
Heartbeat
1. App: { "type": "ping" }
2. Relay → App: { "type": "pong" }
Best Practices
For Apps
- Always store event_id from "accepted"
- Use event_id to match tokens/replies
- Handle all message types
- Send heartbeats every 30-60s
- Validate payload size < 64KB
For Agents
- Always echo event_id in responses
- Send tokens frequently (not buffered)
- Include metadata in final reply
- Handle connection errors gracefully
- Send heartbeats every 30-60s