Part 1
One turn for every channel
Study first
- The adapter pattern: each channel turns what arrives into one shape,
{ channel, address, text, thread }, and renders what goes back. The agent in the middle does not know which channel it is on, except for a line about how to write. - Where a conversation lives decides where it can continue. A history kept in the browser cannot be continued from a phone.
- Latency: the web streams, SMS waits for the whole reply. The same turn serves both; only the call that runs it differs.
Build
prepareinsrc/lib/assistant.ts: the web chat's turn taken out of its route, the versioned prompt first so the provider can cache it, then what is known about the person and the conversation trimmed to the context budget. The web streams it; the other channels wait for it.- A
conversationstable andsrc/lib/history.ts: each conversation with its person and its messages, each message marked with the channel it came by. - The web route starts from the server's copy when there is one, so turns taken on another channel are part of the next answer, and updates the copy after each reply. The chat page fetches the server's copy when it opens a conversation and labels each message that came by another channel.
Done when
- A conversation that includes WhatsApp turns reaches the model whole, even when the browser sends only its own older messages and the new question; a turn whose save has not landed yet is kept from the browser's copy.
Technical notes
- The save is guarded in SQL: an update applies only where the stored person matches, so a client that sends someone else's conversation id cannot overwrite it.
- Taking the server's copy as the conversation is safe because the web chat retries only after a failed turn, and a failed turn saves nothing.





