Debounce Telegram messages into a single turn (Message Burst collation) #51
Labels
No labels
epic
in-review
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
weiwen/evie#51
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem Statement
When I paste a long message to evie on Telegram, the Telegram client silently splits it into several separate messages (Telegram caps a message at 4096 characters and delivers each piece as its own message, in rapid succession). Evie treats each piece as a brand-new prompt: the second piece aborts the turn started by the first, the third aborts the second, and so on. The net result is that evie only ever answers the last fragment of my paste, and the earlier fragments are stranded as aborted partials in the session history. The same thing happens when I send a photo album — each photo arrives as a separate message, so only the last photo is actually processed.
Solution
Evie collates a Message Burst — a run of Telegram messages that arrive in quick succession — into a single prompt for one turn. Incoming Telegram messages are held in a per-chat buffer; once the chat has been quiet for a short window (500ms), the buffered messages are dispatched together as one turn. Text parts are joined into one prompt and any attachments (photos, documents) are combined, so a split paste is reassembled and an album is processed as a single multi-image turn. From my perspective, pasting a long message or sending an album "just works": evie answers the whole thing once, instead of only the tail.
User Stories
/clearsent during a burst to discard the pending buffered messages, so that a stale burst does not fire a ghost turn onto my freshly-cleared session./clear,/help, and/startto continue to work immediately and never be swept into a collated prompt, so that commands behave predictably.Implementation Decisions
CONTEXT.md.DEBOUNCE_WINDOW, 500ms), alongside the existingEDIT_THROTTLE/TYPING_REFRESHconstants — not a configuration field. It tracks Telegram client timing, not a user preference.\n\n; concatenate attachments. Text fragments are joined with a blank-line separator, in arrival order. Image/attachment lists are concatenated across the burst. Accepted trade-off: a split code block or long prose paste gets a blank line injected at each 4096-character cut. (The alternative "4096-boundary heuristic" was considered and rejected — see ADR 0005.)deliver_streamingpath — which aborts any in-flight turn as its first act — is unchanged. On flush, the collated burst is handed todeliver_streamingexactly as a single message is today, so a burst arriving mid-stream still aborts the running turn (at flush time). Every incoming message now waits up to ~500ms before anything happens, including a lone message sent to interrupt a streaming turn; this latency is accepted as imperceptible against multi-second turns.Mutex-guarded map keyed by Telegram chat id, holding aPendingBurst(accumulated text parts, images, temp guards, and the current deadline), mirroring the existing per-chatTurnTrackerdependency. Threaded through the dispatcher and into the message handler.PendingBurstand spawn exactly one flush task if none exists, otherwise append content; either way set the deadline to now + 500ms. The flush task loops — read the current deadline, sleep until it, then under the lock check whether a newer message pushed the deadline out; if not, remove and take the burst (task ends) and dispatch it; if so, loop and sleep to the new deadline. At-most-one dispatch per burst; reset-on-message is the deadline bump. Concurrency-safe against the dispatcher'sdistribution_function(|_| None), which lets multiple handler tasks for the same chat run in parallel./clearadditionally removes the chat'sPendingBurstunder the map lock so its flush task finds no entry and exits without dispatching — preventing a ghost turn on the freshly-cleared session./help//start/unknown reply immediately and leave any in-progress burst intact.deliver_streamingruns at flush, exactly as today.Testing Decisions
collatefunction. The entire correctness surface of the join/concatenation rules is extracted into a pure, synchronouspub(super)function that takes the accumulated burst parts and returns the collated(text, images). This mirrors the codebase's established pure-helper pattern (markdown::paginate,view::reconcile,view::plan_plain), each of which is a pure function with a co-located#[cfg(test)] mod testsblock.collate: a single message passes through unchanged; N text parts join with\n\nin order; images concatenate across messages; a text+image mix preserves both; arrival order is preserved; empty/edge inputs behave sensibly.src/telegram/markdown.rsandsrc/telegram/view.rs(e.g.prefix_stable_seal_skips_unchanged_leading_pages,plan_plain_sends_when_view_empty) — same shape: pure function, table-ish assertions, no async or teloxide.tokio::time::pause/advanceand would largely assert tokio's own behaviour. Consistent with the existing untested async delivery loop (deliver_streaming), the timing stays unit-untested; only the purecollatehelper is tested.Out of Scope
4096-boundary heuristicfor perfectly reconstructing split pastes without an injected blank line — considered and rejected in ADR 0005; may be revisited if\n\n-join mangling of pasted code proves annoying in practice.pi's RPC steering, or the scheduled/silent delivery path.CONTEXT.md"Command" entry contradiction (unknown commands are replied to, not forwarded to pi) — surfaced during grilling, filed as a separate follow-up issue.Further Notes
ADR 0005 — Debounce Telegram messages into a single turn(docs/adr/0005-telegram-message-burst-debounce.md), which captures the 500ms trailing-debounce decision, the\n\njoin and its trade-off, the rejected 4096-boundary heuristic, and the accepted ~500ms latency on every message.CONTEXT.mdgained the Message Burst glossary term and its Session entry now references bursts.