fix(byoa): don't ack unread the wake digest never showed (#29)
snapshotUnread builds `seen` from EVERY inbox row, but caps the digest handed to
the engine with one GLOBAL tail (`lines.slice(-40)`). On a clean turn the daemon
then calls `ackSeen(token, seen)` — "ack everything this turn saw" — over the
full map.
So anything the tail evicted is marked read having never been shown. And because
/conversation/mark-read pins each conversation's cursor at its NEWEST message id
(the unread filter is `ROW(mm.created_at, mm.id) > ROW(lr_at, lr_id)`), those
messages can never reappear in a later wake digest. The contract comment right
above snapshotUnread says the opposite is intended: "Captured BEFORE the engine
runs so `ackSeen` advances exactly what THIS turn saw".
The wake prompt makes it worse by telling the model the truncated set is
complete — "Your unread messages (ALREADY FETCHED — no need to re-run `cumora
inbox`)" — so it has no cue to re-fetch and no count to notice a gap.
Measured against the old renderer:
- a human DM that arrived BEFORE a 200-message group burst is evicted whole:
the conversation does not appear in the digest at all, and is then acked.
- 25 conversations x 2 unread (the offline-catch-up shape): 12 conversations
vanish entirely from the digest while all 25 read cursors jump forward. The
agent cannot even name what it lost.
- 60 unread in one room: 20 dropped with no marker of any kind.
Spend the same 40-line budget PER CONVERSATION instead of globally, water-filling
quietest-first so a small conversation keeps all of its messages and the busy one
absorbs the slack. This mirrors what the cloud path already does — loadContext
gives every conversation with unread its own window, and turn.ts prints an
explicit unread count — so this brings BYOA in line rather than inventing a
policy.
Whatever still cannot fit is announced in place with its exact count and the
command that reads it, because this turn is about to mark it read: dropping
unread in silence is the part that makes the loss unrecoverable.
When everything fits, the digest is byte-for-byte what it was. X
Xialie Zhuang committed
6369874e817403be6782d402a8eec7959105668a
Parent: ceedc0c
Committed by GitHub <noreply@github.com>
on 8/19/2026, 8:02:34 PM