# Unread Runtime Follow-up Directions

> Canonical path: `specs/message/unread-runtime-follow-up.md`
> Status: follow-up design
> Scope: V1 unread runtime 之后的架构收敛、Task attention、capacity / eviction 和 future runtime substrate。
> Related specs:
> - [`specs/message/unread-runtime-memory.md`](./unread-runtime-memory.md)
> - [`specs/message/read-path-performance-boundaries.md`](./read-path-performance-boundaries.md)
> - [`specs/message/delivery-runtime.md`](./delivery-runtime.md)
> - [`specs/task/task-notifications-and-related-task-ux.md`](../task/task-notifications-and-related-task-ux.md)

本文承接 [`unread-runtime-memory.md`](./unread-runtime-memory.md) 的第 17 节。主 spec 描述 V1 unread runtime memory model 和验收边界；本 spec 记录后续架构方向。这里的方向不能作为保留 warm-path TiDB unread query、heartbeat polling、request-time aggregation 或 legacy Task projection 的理由。

## 后续方向

1. **External runtime substrate**
   - 当 split deployment 需要 central workspace list red dots，或未来出现 same-workspace multi-owner runtime 时，引入 Redis / Valkey 或等价 external runtime substrate 承载 workspace `hasUnread` aggregate、pub/sub 和 short-lived serving state。
   - 该 substrate 应替代 owner-local heap 的跨进程读取需求，而不是让 agentteam server 进程互相同步私有内存，也不是让 central fallback 到 TiDB unread aggregation。

2. **Durable unread event / outbox contract**
   - V1 trusts snapshot after restart and does not automatically replay crash-window unread. Later versions can add a clearer transactional outbox or durable unread event log if the product needs stronger crash recovery。
   - Any replay / catch-up design must remain outside request-time refresh / heartbeat / open-page serving paths unless backed by an external runtime substrate or explicit operator job。
   - Task Message 与 pure typed Activity 已复用 `scope_fanout_outbox` 保存 stable recipient snapshot；未来 durable unread replay 应消费这个事实，不得按 replay 时的最新 assignee / Watch 重新选择 recipients。

3. **Retired taskattention schema cleanup boundary**
   - `internal/store/taskattention` 不再拥有 unread counters、Task Inbox candidate projection、recipient policy 或 compatibility cleanup。
   - `conversation_member_states` 不再有 `task_message_unread_count`、`task_activity_unread_count`、`task_inbox_active`、`task_inbox_sort_at` 或只服务这些字段的 indexes。
   - Task attention 使用 canonical message/activity facts、read cursors、UnreadRuntime 与 member-local unified conversation index；future changes must not reintroduce a durable Task unread serving projection。

4. **Generation / frontier matrix**
   - 每类 unread-affecting mutation 必须提供 monotonic generation、version 或 equivalent frontier。后续应把 message、activity、read cursor、membership、notification、manual unread、rewind 和 visibility mutation 的 durable anchor 整理成实现级矩阵。
   - 如果 owning row 只有低精度 `updated_at` 或无法检测值往返，应在 owning row 上补 generation，而不是新增 unread count projection。

5. **Memory capacity and eviction policy**
   - V1 要求 inactive member / scope entries 有内存预算，但不强制具体 eviction 算法。
   - 后续应定义明确的 eviction policy：dirty entries 在 flush 或 durable repair marker 落地前不能无声逐出；evicted ready entries 重新访问时走 lazy snapshot hydrate；eviction 暴露 bounded metrics。

6. **Task attention product model**
   - 一个 Task 对每个 Human 最多有一个聚合 Inbox conversation row；Inbox `Tasks` 只是 `kind=task` filter，不是第二套 Inbox。
   - 一条 committed Task event 的 recipients 固定为 `explicit watchers ∪ current assignee ∪ current Message direct mentions − actor`。
   - Human recipient 获得 targeted message / typed-Activity unread；Agent recipient 获得 delivery / wake。普通 participant、creator、commenter、activity actor 与 source-scope visibility 不产生 attention。
   - Task Message 由 message outbox 投递；paired `discussion_added` Activity 只进 timeline。Pure typed Activity 由 `activity_event` outbox 投递。两者不能 double fanout，也不能合成 hidden message。
   - Explicit Watch 复用 Scope notification setting；assignee attention 从当前 Task fact 派生，不持久化为 Watch，Unwatch 不能逃避仍在承担的 assignee responsibility。
   - Mark read advances both Task message and activity cursors, clears manual unread, and updates the same UnreadRuntime / conversation-index entry。
   - Rollout 不回灌历史 unread。Cutover 后的新 targeted events 才进入 Task attention。

7. **Task attention cost boundary**
   - 不允许 O(all visible task members) fanout。Planner 只读取 bounded explicit watchers、一个 current assignee 与本条 Message mentions。
   - Recipient snapshot 必须在 owning transaction / outbox boundary 固化；worker replay 不做 membership-wide recipient scan。
   - Global Inbox query 读取 member-local `im_conversation_index`，不 union 所有 visible Task，也不 request-time aggregate Task tails。
   - Runtime snapshot 仍是 coalesced serving hint，不变成 synchronous per-event durable projection。
   - Pin / hide / manual unread 可以继续使用 generic conversation preference facts，但不参与 recipient selection。

8. **Validation**
   - tests / guardrails prove watcher、assignee、mention union；actor exclusion；recipient dedupe；assignee-over-Unwatch；mention-over-no-wake；
   - typed Activity creates Human Task unread and Agent wake without a message；
   - Task Message plus `discussion_added` does not double unread / wake；
   - unrelated Task members receive neither Inbox row nor delivery；
   - one Task creates at most one member-local Inbox row and mark-read clears both cursor domains；
   - warm Inbox / refresh / heartbeat paths do not introduce broad TiDB unread queries；
   - removed Task counters, indexes and `internal/store/taskattention` remain absent。
