The protocol package
Every message shape on this page is published as TypeScript definitions in@fishaudio/agent-protocol — zero runtime dependencies. This page documents protocol revision 0.3.0; the npm package is versioned independently. The package is the source of truth for shapes; this page fixes the semantics.
npm
Connect to a session
Create a session server-side (authentication), then connect to the transport named in the response. Public agents can create sessions without theAuthorization header.
Create a session
Response (201)
transport. The livekit arm carries livekit_url and token: connect a LiveKit-compatible WebRTC client with them before expires_at (the join deadline), publish your microphone track, and subscribe to the agent’s audio track.
Channels
Once connected, a session uses these channels. The two*-event topics carry reliable data packets — one complete JSON object per packet.
Data-channel message fields are camelCase; REST bodies are snake_case.
Agent events — agent-event
client_tool.call
The agent wants to run a client tool in your app.
client_tool.call
client_tool.result with the same callId. While expectsResponse is true, the agent suspends the model’s tool call until your result arrives or the tool’s configured timeout elapses (default 30 seconds, configurable from 1 to 120). When expectsResponse is false, the call is fire-and-forget: the agent continues immediately and any result you send is ignored.
Tool lifecycle — tool.started, tool.completed, tool.failed
Each tool the agent invokes emits one tool.started, resolved by exactly one terminal message (tool.completed or tool.failed) with the same callId. Terminal messages repeat toolName and toolSource, so a client that missed the start can still render a complete entry.
These events are on by default and their payloads travel to the end user’s client. Pass
tool_events: false when creating the session to keep tool data off the wire — the session then receives none of the three.
error
error
code is a coarse category only: provider_error (an upstream model or voice provider failed) or internal_error (the runtime failed). The message deliberately carries no raw error detail.
Client events — client-event
Publish these on the client-event topic. The agent ignores malformed JSON and unknown types.
user.message
client_tool.result
user.messagegets no server echo — you already hold the text, so render the bubble locally. Add"audio": false(protocol 0.3.0) to have the agent answer that turn in text only: no speech is synthesized and the reply arrives over transcription.client_tool.resultmay carryresult(any JSON value) or"isError": trueto report the tool as failed to the model. Results for unknown or already-settledcallIds are ignored.
Transcription and agent state
These ride the transport’s built-in mechanisms rather than custom messages. Transcription arrives as text streams on thelk.transcription topic. Segments are identified by the lk.segment_id stream attribute; lk.transcription_final: "true" marks a segment as final. Roles are distinguished by sender identity: the user’s segments are sent under the user’s own participant identity, the agent’s under the agent participant.
- Agent segments stream incrementally, paced to audio playback. An interrupted segment closes containing only the words actually spoken — there is no residual text.
- User segments are interim until final; each interim update replaces the entire segment text under the same segment id.
lk.agent.state participant attribute with values initializing, idle, listening, thinking, and speaking. Sticky means a client that connects late or reconnects reads the current value immediately. The SDKs derive their three public modes from this attribute plus transcript segment open/close.
Compatibility rules
- Ignore unknown
typevalues and unknown fields. This is required consumer behavior and the foundation of forward compatibility. - Evolution is additive-only. Published fields never change name or meaning and are never removed; new fields are always optional. A semantic change ships as a new
type. - No replay. Data-channel delivery is reliable and ordered within a connection, but after a reconnect or late join, missed messages are gone — never wait for history. Tool terminal messages repeat their identifying fields, and the state attribute is sticky, precisely to soften this.
Protocol revision history
Going further
Web SDK
The supported implementation of this protocol for browsers.
Client tools
Declare the tools your
client_tool.call handlers implement.Authentication
Session creation, API keys, and token handling.
Public agents
Let clients create sessions without a backend.

