Telegram Is Two Things at Once
For most of Pippa's life Telegram was simply one more client with its own conversation. Under Master Chief it became two things that pull in opposite directions. It is a doorway: Dad types into it and the turn enters the one shared room. And it is a phone notification channel: every message the bot sends makes his phone buzz, wherever he is. A doorway wants to show the room. A notification channel should only speak when it has something to say.
The doorway half is plain. Telegram defaults to the Chief. /new (or the New button) opens a separate Telegram conversation outside it, /switch to any other row leaves it, /switch to the Chief's row or /chief brings it back, /list marks the Chief's row by name among the recent conversations, and /status says which mode the chat is in. The bot does not trust a cached id for the room: on each sync pass it asks the engine to resolve the Chief and repoints every chat bound to it, so those chats follow the Chief whatever row it is.
The Phone That Rang Twice
The bot learns about new messages by polling, and its rule from before Master Chief was sensible for a conversation Telegram owned: mirror every message another client added. If you had said something in the WebUI, you wanted to see it in Telegram too. In a room every device shares, that rule turns every exchange into a double notification. Dad asks something on the laptop, Pippa answers on the laptop, and the phone in his pocket buzzes twice for a conversation he is already reading. He ruled on it within the first two days of the Chief (coop #347; the code records it as 2026-10-08): in Master Chief, Telegram mirrors only Telegram's own turns. (Coop notices and follow-up blocker alerts still reach the phone, by their own path; the mirror is not how they travel.)
Whose Turn Is It?
That ruling needs every message to know which door it came through, and Track 2 showed where that comes from: the Chief route stamps origin_surface on every user row it admits. An assistant reply carries no doorway of its own, so _turn_origin walks up the parent chain (at most eight steps) to the nearest user row that names one, stepping past steer rows, which belong to the turn they interrupted. _mirrors_to_telegram then decides, in this order:
| The message | Mirrored to Telegram? |
|---|---|
Carries a turn_kind (a Coop notice, a follow-up line, a task outcome, a compaction summary) | Never. Notices already ride their own channel; a summary is bookkeeping. |
Has origin_surface: scheduled-task in its host context | Never. A scheduled report owns its own notification policy. |
| Lives in an ordinary conversation, not the Chief | Yes, the old rule: every message another client added. |
| Lives in the Chief | Only if its turn's origin is telegram. |
A message that is not mirrored is not ignored. It is settled: written into the bot's delivery ledger and its known set exactly as if it had been sent. That matters after a restart, when the bot rebuilds what it has already handled. Without settling, every WebUI turn would look undelivered forever and be read again on every poll.
Read the Version First
Polling an endless conversation has its own trap. On the Chief's second day the poll was reading the whole transcript every five seconds, about 2.9 MB each time, and growing. Now the poll asks a cheap question first: GET /api/conversations/{id}/version, which answers an in-memory counter and a boot epoch, a random value chosen at each engine start. The pair (epoch, version) is what the bot compares, because a restarted engine starts counting from zero and could otherwise repeat an old number for new contents. Only when the pair moved does the bot read a page of the newest 100 messages. And it records the new pair only once every message on that page is settled, so a batch cut short or a failed image send is read again on the next pass. The cold review checked this gate and listed it as sound.
A Cut Stream Is a Question, Not a Resend
Phones lose connections. When the bot's stream to the engine breaks, it never just sends the message again, because a second send would append Dad's words twice and run a duplicate turn. Every Telegram turn carries an external request id built from the chat and the message, in the format tg-<chat>-<message>. If the cut came before the engine said it had started the turn, the bot asks the engine's turn status for that id, every three seconds for up to two minutes. Missing means the engine never took it, so the bot sends it once more under the same id, which the engine would refuse to run twice. Pending or completed means the turn is in the room, and the reply arrives through the sync poll, which mirrors it because its origin is Telegram. A cut after the turn started is never resent at all.