Reliability requirements¶
Clinical audio must never be lost silently. A production client must follow every rule on this page. Sahl AI's own clients follow the same rules.
1. Buffer until acknowledged¶
Keep each chunk in a map of seq_id → { payload, sent } until its ack arrives, then delete it. On reconnect, mark all buffered entries unsent and resend them in ascending seq_id order, about 100 ms apart.
2. Never reset seq_id¶
seq_id counts from 1 across the whole consultation, including reconnects and pauses. The server uses it to remove duplicate chunks you resend and to keep audio in order.
3. Detect dead connections¶
Record the time of the last message received, of any type. Check it every 5 seconds. If nothing has arrived for 25 seconds, treat the socket as dead: close it and reconnect. A half-open TCP connection can look healthy while audio is being lost.
4. Keep the socket alive¶
Send ping every 30 seconds from open to close: while recording, while paused and while waiting for task_status.
5. Reconnect correctly¶
On an unexpected close, if the consultation has not finished:
- Wait about one second, then reopen the socket with the same token. Back off further if it keeps failing.
- Send
startagain withencounter_idset to the value fromencounter_created. - Resend every unacknowledged chunk (rule 1) before sending new audio.
6. Handle network changes¶
Listen for the browser or operating system going offline. When it does, close the socket and stop sending. Chunks keep collecting in the buffer. When the network comes back, reconnect as in rule 5. The buffered audio is then sent in order, so nothing said during the outage is lost.
7. Queue the end of the consultation¶
Put end_transcript in the same buffer as the audio, so it is sent only after every earlier chunk has gone. The number of buffered chunks is a reliable progress figure for an "Uploading…" indicator after the clinician presses End.
Checklist¶
- Token passed as the second subprotocol after
listen-protocol - Audio is PCM-16, 16 kHz, mono, base64, 100–200 ms per chunk
-
startsent before the first chunk, with yourprompt_id -
encounter_idstored whenencounter_createdarrives -
seq_idstarts at 1, increases by one per chunk and never resets - Unacknowledged chunks buffered and resent after reconnect
-
pingevery 30 seconds for the life of the socket - 25-second inbound silence triggers a reconnect
- Reconnect sends
startwith the existingencounter_id - Socket kept open after
end_transcriptuntiltask_statusarrives -
task_statuswithstatus: "FAILED"handled as a failure