Wire Protocol¶
This page specifies the byte-level contract a third-party client must implement to talk to a Tryll server without using the shipped C++, Python, or Unreal client libraries. If you are using one of those libraries, you do not need any of this — skip to the how-to guides.
The authoritative source of truth for every structure named below is
the FlatBuffers schema file server/schema/messages.fbs.
This reference describes the transport around that schema.
Transport¶
- TCP only. No TLS; the server is expected to run on
localhostor inside a trusted network. - Bi-directional. Both client-to-server requests and server-to-client responses use the same framing over the same socket.
- One connection = one session. Closing the socket destroys every agent owned by the session.
Framing¶
Every frame is a length-prefixed FlatBuffers payload:
+-------------------+----------------------------------+
| length (4 bytes) | FlatBuffers root table (N bytes)|
| little-endian | |
| uint32 | |
+-------------------+----------------------------------+
lengthis the byte count of the FlatBuffers payload that follows, little-endian.- The payload's root type is
Message(seemessages.fbs);Message.bodyis a union whose variant tag selects the message kind. - Maximum frame size: 1 MiB (1 048 576 bytes). Any frame exceeding
the cap is rejected with error
5003FrameTooLarge(see error codes).
No compression, no chunking below the FlatBuffers level, no keep-alive
heartbeats. The client is expected to read frames in a loop and
dispatch them by the body union tag.
Message types¶
All messages are defined in messages.fbs under the
MessageBody union. The full catalog (table names as they appear in
the schema):
| Direction | Request | Corresponding response(s) |
|---|---|---|
| S → C | — | SessionReady (unsolicited, sent on accept) |
| C → S | ConfigureSessionRequest |
ConfigureSessionResponse |
| C → S | CreateAgentRequest |
CreateAgentResponse |
| C → S | SendMessageRequest |
AnswerText × N, then TurnComplete |
| C → S | DestroyAgentRequest |
Ack |
| C → S | ListModelsRequest |
ListModelsResponse |
| C → S | DownloadModelRequest |
DownloadProgress × N, then DownloadComplete |
| C → S | LoadModelRequest |
LoadModelResponse |
| C → S | UnloadModelRequest |
Ack |
| C → S | CreateStringStorageRequest |
CreateStringStorageResponse |
| C → S | DestroyStringStorageRequest |
Ack |
| C → S | CreateEmbeddedStringStorageRequest |
CreateEmbeddedStringStorageResponse |
| C → S | DestroyEmbeddedStringStorageRequest |
Ack |
| S → C | ErrorResponse |
(replaces any expected response on failure) |
| S → C | NodeEvent |
(unsolicited, fire-and-forget) |
Session lifecycle¶
A connection follows this sequence:
sequenceDiagram
participant C as Client
participant S as Server
C->>S: TCP connect
S-->>C: SessionReady(protocol_version, session_id)
C->>S: ConfigureSessionRequest(inference_engine, stt_engine?, tts_engine?, embedding_engine?, allow_auto_model_downloading?)
S-->>C: ConfigureSessionResponse
C->>S: CreateAgentRequest(graph, ...)
note over S: if allow_auto_model_downloading=true and models absent:
S-->>C: DownloadProgress(request_id=CreateAgent rid) × N
S-->>C: CreateAgentResponse(agent_id)
loop per turn
C->>S: SendMessageRequest(agent_id, text)
S-->>C: AnswerText(chunk, is_final=false)
S-->>C: AnswerText(chunk, is_final=true)
S-->>C: TurnComplete(status, debug_info?)
end
C->>S: DestroyAgentRequest(agent_id)
S-->>C: Ack
C->>S: TCP close
The server emits SessionReady before any request has been sent;
a client that issues ConfigureSessionRequest without first reading
SessionReady races against the schema-version check carried in that
frame.
ConfigureSessionRequest carries one engine per model kind:
inference_engine for language models, plus stt_engine, tts_engine,
and embedding_engine. Each defaults to Mock; set only the ones you
actually use. The server dispatches model lookups by the catalog
ModelType of the model name in question, so ListModels and
UnloadModelRequest work uniformly across all four kinds.
Important properties:
- Request IDs are optional. Every request may carry a
client-assigned
request_id; the server echoes it back on the matching response and on anyErrorResponsethat replaces that response. Use it to correlate when multiple requests are in flight. - Only one turn per agent at a time. Sending a second
SendMessageRequestbeforeTurnCompleteResponsearrives yields error3004AgentBusy. - Multiple agents per session. A session may own many agents; turns on different agents run in parallel.
- Unsolicited frames. The server may emit
NodeEventor other unsolicited frames at any time. Clients must tolerate frames arriving between an in-flight request and its response. DownloadProgressunder aCreateAgentrequest_id. Whenallow_auto_model_downloading=trueand models need downloading, the server emitsDownloadProgressframes using the CreateAgentrequest_id(informational only). The terminal frame is alwaysCreateAgentResponseorErrorResponse— neverDownloadComplete.
Streaming answers¶
For every turn, the server emits one or more
AnswerText frames followed by exactly one TurnComplete:
| Field | Meaning |
|---|---|
agent_id |
Echoes the target agent. |
text |
The text chunk. |
is_delta |
true for delta chunks, false when text holds the accumulated response so far. |
is_final |
true on the last chunk before TurnComplete. Exactly one AnswerText per turn has is_final = true; a turn that produces no text (e.g. routed to a canned response with empty output) still emits one final chunk with empty text. |
TurnComplete carries:
| Field | Meaning |
|---|---|
agent_id |
Echoes the target agent. |
status |
TurnStatus::Success, Error, or Cancelled. |
debug_info |
JSON string with per-node execution data; populated only when the agent was created with enable_diagnostics = true. Empty string otherwise. |
tokens_generated |
Total tokens sampled across all generation nodes in this turn (prompt tokens excluded). |
Voice input and transcripts¶
When a VoiceInput utterance is active the server streams WireTranscriptUpdate frames to the client. Each frame carries a kind field that classifies the event:
kind |
Value | Meaning |
|---|---|---|
SpeechStart |
0 | VAD rising edge; text is empty. |
Partial |
1 | Revisable in-progress hypothesis. Overwrite any previous partial. |
SegmentFinal |
2 | Engine-committed chunk; utterance still open. |
UtteranceFinal |
3 | Last frame for this utterance. text holds the complete concatenated transcript. Auto-send (if configured) fires here. |
Clients should update their display on Partial updates and commit on UtteranceFinal.
Error responses¶
When the server cannot satisfy a request, it emits an
ErrorResponse instead of the expected response frame:
| Field | Meaning |
|---|---|
request_id |
Echoes the request_id of the failed request, if any. |
code |
Numeric error code. |
message |
Human-readable description; safe for display. |
Inference errors that occur during a turn are not delivered as
standalone ErrorResponse frames — they surface via
TurnComplete.status = Error, with detail in TurnComplete.debug_info
when diagnostics are on. Session-level and protocol-level errors still
come through ErrorResponse.
Refer to error codes for the full catalog and per-range recovery guidance.
String storage¶
CreateStringStorageRequest creates a named string storage scoped to the
current session. It carries four content fields:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Unique session-scoped name. |
kind |
StringStorageKind uint8 enum |
no | 0 = List (default), 1 = Map, 2 = Multimap. |
strings |
[string] |
one of strings/file_path |
Inline values. For Map/Multimap, parallel with keys. |
keys |
[string] |
only for Map/Multimap inline | Inline keys. Must have the same length as strings. |
file_path |
string | one of strings/file_path |
Server-side file. .txt for List; .json array of {id, text} for Map/Multimap. |
Backward compatibility: old clients that omit kind receive List (0) as the
default. Old clients that omit keys receive null (none), consistent with List
creation. New fields are safely ignored by old server versions (FlatBuffers
forwards-compatible table extension).
Validation rules:
kind = List:keysmust be absent or empty.stringsorfile_pathrequired.kind = Map:strings+keysof equal non-zero length required (inline), orfile_path. Duplicate keys are rejected (7003).kind = Multimap: same as Map but duplicate keys are accepted.
The server responds with CreateStringStorageResponse on success, or
ErrorResponse on failure (see error codes 7xxx).
GraphDescription shape (protocol v2)¶
CreateAgentRequest carries a GraphDescription table with two fields:
| Field | Type | Description |
|---|---|---|
nodes |
[NodeDescription] |
Ordered list of nodes. Each NodeDescription holds a name, a params_type union tag, and a typed params table. |
start_node |
string |
Name of the first node to execute each turn. |
Wiring is encoded in the typed params table of each source node, not in a
separate list. Every declared exit has a corresponding <exit_name>_exit string
field (e.g. default_exit, triggered_exit). An empty string means route to
END; any non-empty value must name another node in nodes.
The server validates all exit targets when processing CreateAgentRequest. A
non-empty exit field that names a missing node is rejected with error
3008 InvalidExitTarget.
Versioning¶
The current wire-protocol version is 2. Version 2 removed the
routes: [ExitRoute] field from GraphDescription and moved wiring onto each
node's typed params (see GraphDescription shape
above).
When a breaking change lands, the server rejects incompatible clients at
SessionReady time with error 5004 ProtocolVersionMismatch. Clients should
surface the error message verbatim and stop reconnecting.
Writing a new client: checklist¶
- Open a TCP socket to the configured host/port.
- Read exactly 4 bytes; interpret as little-endian uint32 →
length. - Read exactly
lengthbytes; decode asMessagepermessages.fbs. - Dispatch on the
bodyunion tag. - Read
SessionReadyfirst (unsolicited), then sendConfigureSessionRequest; block untilConfigureSessionResponsearrives. - Serialise per-agent
SendMessageRequests client-side; do not issue a second turn beforeTurnComplete. - Treat any frame > 1 MiB as a fatal protocol error.
- Tolerate unsolicited frames (e.g.
NodeEvent) interleaved with expected responses.
Related¶
- Error codes — numeric catalog.
- Concept: Agents and sessions — state-machine view of the lifecycle above.
server/schema/messages.fbs— canonical schema.