Documentation
Conversational Triage API
Reference
Streaming events

Streaming events

The same set of events is delivered as NDJSON lines on POST /conversations/{conversation_id}/turns/stream and as JSON frames on the realtime WebSocket endpoint. Every event carries a type discriminator.

The WebSocket transport adds four transport-only events — conversation.created, conversation.resumed, conversation.settings.updated and ping — that are not part of the HTTP NDJSON stream. Those are documented under Realtime (WebSocket).

Message events

conversation.user_message.created

Confirms that the user message was accepted and a turn is in progress. user_message.content is the post-redaction text.

FieldTypeDescription
user_messageUserMessageThe accepted user message.

conversation.assistant_message.delta

Incremental text for an in-progress assistant message.

FieldTypeDescription
assistant_messageAssistantMessageDeltaIn-progress delta chunk.

conversation.assistant_message.ui_widget

Transient structured widget for an in-progress assistant message. Emitted only when ui_widgets_enabled is true.

FieldTypeDescription
assistant_messageAssistantMessageUiWidgetEventPayloadWidget scoped to one assistant message segment.

AssistantMessageUiWidgetEventPayload:

FieldTypeDescription
idUUIDAssistant message this widget belongs to.
turn_idUUIDTurn producing the message.
widgetUIWidgetWidget payload to append to the message UI.

conversation.assistant_message.completed

Canonical full representation of a finished assistant message.

FieldTypeDescription
assistant_messageAssistantMessageFinal assistant message for this segment.

Conversation-level events

conversation.assessment.updated

Replace the client's local assessment state with this payload.

FieldTypeDescription
assessmentAssessmentCurrent assessment state.

conversation.handoff.requested

Emitted when the turn produced a handoff record. Only emitted while at least one detector listed in ConversationSettings.handoff_policies is enabled.

FieldTypeDescription
handoffHandoffHandoff record produced by the turn.

conversation.completed

Lifecycle marker — the conversation reached a terminal recommendation. The recommendation itself is already present in the preceding conversation.assessment.updated payload.

FieldTypeDescription
conversation_idUUIDConversation that has completed.

conversation.terminated

Emitted when the turn left the conversation permanently unable to accept further messages, whatever the cause — for example an underage patient or a confirmed age or sex change. Further turn calls fail with conversation_terminated; the client must start a new conversation.

FieldTypeDescription
conversation_idUUIDConversation that has terminated.
ℹ️

The event is a lifecycle marker only. Why the conversation ended, and what the user should do about it, arrive in that turn's assistant messages — render those, not a client-side reason mapping.

Terminal turn events

Exactly one of these ends each turn; they are mutually exclusive.

conversation.turn.completed

FieldTypeDescription
turn_idUUIDThe turn that has completed.

conversation.turn.interrupted

Emitted when the turn was interrupted by a newer accepted input.

FieldTypeDescription
turn_idUUIDThe interrupted turn.

conversation.turn.failed

FieldTypeDescription
turn_idUUIDThe failed turn.

error

Recoverable protocol or processing error. Both transports use the same payload shape. A WebSocket connection stays open after error unless the server also closes the socket; an HTTP NDJSON stream closes after a terminal turn event.

FieldTypeDescription
errorErrorError code and message; see Error codes.

Successful turn order

A successful non-terminal turn produces:

  1. conversation.user_message.created
  2. Then, for each assistant message the turn produces, in order:
    • conversation.assistant_message.delta — one or more
    • conversation.assistant_message.ui_widget — zero or more, only when ui_widgets_enabled is true; may appear before, between, or after deltas for the same assistant_message.id
    • conversation.assistant_message.completed
  3. conversation.assessment.updated
  4. conversation.handoff.requested — only when a handoff was recorded this turn
  5. conversation.turn.completed

A single turn commonly produces several assistant messages of different kinds — for example a short acknowledgement followed by the intro that carries the actual question. Each one runs its own delta/completed cycle, distinguished by assistant_message.id. Treat the terminal turn event, not the first conversation.assistant_message.completed, as the end of assistant output for the turn.

Variations:

  • A turn that reaches a recommendation inserts conversation.completed between the handoff step, if any, and the final conversation.turn.completed.
  • A turn that leaves the conversation unable to continue emits conversation.terminated between the optional handoff event and the terminal turn event. The explanation for the user arrives earlier in the turn, on conversation.assistant_message.completed, like any other assistant message.
  • A terminating turn never emits conversation.handoff.requested: the two are mutually exclusive, and terminating clears any handoff recorded earlier in the conversation.

Transient widget events

conversation.assistant_message.ui_widget is transient: it is delivered live to connected clients but is not individually persisted or replayed on reconnect. When ui_widgets_enabled is true, the server may emit one or more widget events while a message is still streaming, so clients can render structured UI before conversation.assistant_message.completed.

On turn completion, the same widgets appear in assistant_message.content_parts on the completed event, and in blocking Turn.assistant_messages. Treat content_parts as the durable source of truth and transient widget events purely as progressive rendering.

After a reconnect, transient events in the gap between snapshot creation and live subscription may be missed. Reconcile from the next durable boundary — conversation.turn.completed, conversation.turn.interrupted, or conversation.turn.failed — and from refreshed conversation snapshots. If the first post-reconnect transient is not at the start of a new turn, ignore further transients for that in-flight turn until a terminal turn event.

Was this page helpful?