---
status-class: historical
design-state: superseded-unshipped-v1
historical-refs:
  - specs/agent-space/shared-spaces-and-agent-spaces.md
  - specs/message/delivery-runtime.md
  - specs/agent-space/owner-private-agent-delivery.md
---
# Agent Space 02：Delivery / Wake Design

日期：2026-04-24
状态：superseded historical record。本文只记录未上线 Agent Space transcript /
publish / auto-publish delivery 设计线，不能作为当前产品、schema、API、runtime
或用户可见 surface 的合同。当前实现已经删除 Agent Space API/realtime/history
unread surface、manual/auto-publish fan-out、Agent Space transcript delivery、server agent `/receive` 与
agent-facing `scope-execution:<id>` target；private-agent execution 使用 parent
target + hidden owner-private context，unread/wake 由 parent timeline、sparse
targeted events、scope cursors 和 task/context facts 承载。需要实现当前语义时请参考
`runtime-api-design.md`、`../message/delivery-runtime.md`、Task 收敛 specs、storage spec 与代码。
范围：统一 `channel` / `thread` / `dm` / `agent_space` 在 human / agent 两侧的 delivery、pending、unread、wake 语义；把 `01-space-core` 的统一 Space 底座继续收敛成可实现的 delivery / wake 合同。
非目标：share / publish pipeline 细节、inline / panel UI 呈现、group / role mention 机制、runtime adapter 具体队列实现、generic unread backlog 的自动启动策略。

## 0. 与当前 canonical specs 的关系

本篇定位为 **superseded historical record**。

这意味着：

- unified Space 底座与 Agent Space delivery / wake 主路径曾在未上线 implementation line 中落地；
- 本篇记录的是未上线实现曾采用的 delivery / wake 语义；
- 若本文后续保留早期 draft wording，它只说明已删除的未上线实现，不覆盖当前 Task / Scope Execution / storage / runtime 语义。

该未上线 Agent Space delivery / wake 设计线当时与以下文档形成 supersede / 收敛关系：

- `specs/agent-space/shared-spaces-and-agent-spaces.md`（尤其是其中关于 quiet / active 默认强度、private/public agent 默认 wake contract 的段落）
- `specs/message/delivery-runtime.md`（其 runtime reliability 仍是 canonical，但 wake / unread / pending 的产品语义将由本篇提供上位合同）
- `specs/agent-space/owner-private-agent-delivery.md`（其中关于 private agent delivery object 的旧定义，在 unified Space + auto-publish 新模型下将需要重写）

legacy `owner-private-agent-delivery.md` 仍覆盖兼容入口；当前 Agent Space transcript 主路径已被 parent target + hidden owner-private context 模型取代。

## 1. Goal & non-goals

### 1.1 Goal

`01-space-core` 统一了 `channel` / `thread` / `dm` / `agent_space` 的存储底座，但还没有回答：

1. 一条新消息在什么条件下会进入某个 viewer / agent 的默认待处理面；
2. 进入默认待处理面之后，哪些结果属于 human，哪些结果属于 agent；
3. `active` / `quiet` / `mute` 以及 `unsubscribe` 这一操作到底影响的是哪一层；
4. `@mention`、explicit override、space-type defaults 之间的优先级如何；
5. auto-published parent messages 应该如何进入普通 fan-out，而不引入额外特判。

历史实现曾尝试把这些语义锁成一份 **delivery / wake contract**；当前实现不再使用 Agent Space transcript / publish / auto-publish delivery 作为主路径。

### 1.2 Non-goals

本篇不解决：

- `03-share` / `publish` / `auto-publish` 的 authoring pipeline 细节；
- `04-surface` 中 inline / panel / compare / browse 之类 render surface 的交互细节；
- group / role mention 机制（v1 不存在此产品能力）；
- APNs / FCM / desktop notification adapter 的实现细节；
- queue / scheduler / busy-turn continuation 的 runtime 可靠性实现细节（这些仍由 `../message/delivery-runtime.md` 约束）；
- generic unread backlog 是否自动启动 sleeping runtime 之类 daemon 行为策略。

## 2. Vocabulary

本篇使用下面几层语义。它们必须明确分离。

### 2.1 Readable

`readable` 指：

> 某 viewer / agent 是否有权限进入这个 space，并读取该 space 的历史消息。

`readable` 是权限 / membership / access 的问题，不是 delivery 的问题。

### 2.2 Pending / default attention intent

`pending` 指：

> 系统是否把这条消息或这个 space 视为“默认需要你处理/关注的对象”。

对 human 来说，`pending` 最终投影成 unread / badge / inbox / followed list 等“待处理感”。

对 agent 来说，`pending` 先决定这个 space / event 是否默认进入 agent delivery contract；再继续投影成 wake target、可读取上下文的当前作用域等运行时结果。

这里最重要的边界是：

> **`scope_notification_settings` 只表达 default eligibility / preference / intent truth，不直接等于每个具体事件是否已经被 durable delivery。**

也就是说，`pending` 是抽象层；而 `scope_notification_settings` 只是其中一部分共享语义真相，不是所有 execution fact 的替代物。

### 2.3 Message-backed durable execution truth

当某条 message-backed 事件已经真正进入 agent delivery pipeline 时，server 仍然必须保留**按事件记账**的 durable execution truth，用于：

- historical server `/receive` unread authority
- busy-turn continuation / replay
- diagnostics / rewind / archive replay

这层 truth 是：

- **per-event 的 routed recipient fact**；
- 它与 `scope_notification_settings` 的“默认 eligibility / preference”语义不同；
- 它可以由 O2 的默认规则推导出来，但一旦推导并写入，就不再只是实现 cache。

因此，O2 需要同时承认两层 durable truth：

1. **intent truth**：`scope_notification_settings` 的 row presence + state
2. **execution truth**：具体事件是否真的被写成 per-message durable delivery fact

在实现层，O2 本轮显式声明一张 canonical support table：

```
scope_fanout_outbox
  space_message_id      (FK scope_messages.id)
  member_id             (recipient — human or agent)
  member_type           ("human" | "agent")
  delivery_reason       (mention / subscription / owner_quote / auto_flow / explicit / ...)
  execution_result      (agent: delivered_wake | delivered_no_wake | not_delivered
                         human: mention_hit | subscription_hit | none)
  space_id              (denormalized for sharding / index locality)
  workspace_id          (denormalized for sharding / index locality)
  seq                   (= scope_messages.seq at insert time)
  workspace_global_seq  (= scope_messages.workspace_global_seq at insert time)
  created_at
  PK (space_message_id, member_id)
```

这张表与 `scope_notification_settings` 的分层关系是：

- `scope_notification_settings` 仍然只表达 default eligibility / preference / intent；
- `scope_fanout_outbox` 记录单次 message-backed 事件命中的 durable truth；
- server 在消息写入时，先根据 `scope_notification_settings` 与 mention-routing 规则计算 recipients，再把结果写成 `scope_fanout_outbox`；
- 之后的 historical server `/receive`、replay、diagnostics、rewind、archive replay 都消费这张 per-message fact，而不是现场重算。

### 2.4 Wake / notification

`wake` 指：

> 系统是否因为这条消息而立刻打扰你或启动 runtime。

对 human：

- push notification
- desktop / mobile banner
- 其他即时注意力打断

对 agent：

- runtime wake
- 当前 turn 边界上的处理启动

### 2.5 Unread / badge / inbox surfacing

这类是 **human-specific downstream surfaces**：

- unread count
- red dot / badge
- inbox / followed list 冒泡

它们不是 `readable`，也不等于 `wake`。它们是 pending 在 human 侧的可视化产物。

### 2.6 Runtime context access

这类是 **agent-specific downstream surface**：

> 当 runtime 因某个 explicit trigger 醒来时，当前 wake 只把触发消息本身交给 agent；若 agent 需要更多上下文，应通过 `read_history` 按需读取。

注意：

- v1 runtime **不**自动把当前 space 的最近上下文塞进 prompt；
- agent 需要上下文时，自己调用 `read_history` 按需拉取；
- 无论是 runtime 提供的最小 trigger 信息，还是 agent 后续主动读取，作用域都必须限制在 **触发 wake 的当前 space** 内；
- v1 不允许因为 A space 的 wake，而顺手带入 B quiet space 的内容；LLM 只要看到 B 的内容，就会对 B 发生反应。

这里的 **当前 space** 指：

- 该事件在 O1 routing 之后，真正对这个 agent 生效的 **receiving space**；
- 如果 owner 在 parent channel / thread 中 direct mention private agent，private agent 收到的当前 space 是对应的 `(owner, parent)` Agent Space；
- parent row 在这种场景里只是 provenance / source ref，不是 private agent 的 current readable space。

## 3. Scope

### 3.1 v1 只定义 personal mention

v1 只定义 **direct personal mention** 的覆盖规则。

显式不在 v1 scope 内：

- group mention
- role mention
- user-group mention
- 其他集合型 @ 机制

原因很简单：AgentTeam v1 当前并不存在对应的 user-group / role 产品能力。

### 3.2 auto-published parent message 不做特殊 fan-out

已经拍定：

> **auto-published parent message 按普通 parent fan-out 处理。**

也就是：

- 它是一条真正的 parent message；
- recipient calculation、unread、badge、notification、agent wake 都按 parent space 的普通规则走；
- 它来自 Agent Space 只影响 trace / structured refs，不影响 delivery 逻辑。

### 3.3 thread 可从 subscription set 中移除；其它 space 不支持此操作

v1 里，只有 `thread` 支持把对象从 subscription set 中移除；对应操作的具体语义见 §6.4。`channel` / `dm` / `agent_space` 不定义这一操作。

## 4. Recipient calculation

### 4.1 总原则

recipient calculation 先回答：

> 这条消息对哪些对象进入默认 pending / delivery 语义。

它建立在 `01-space-core` 的：

- `scopes`
- `scope_memberships`
- `scope_notification_settings`

之上，并按下面四步求值：

1. **先算 readable 边界**：这个对象是否有权限看见该 space；
2. **再算默认 pending 集合**：按 space type 把“默认该关注谁”算出来；
3. **再套 explicit override**：例如 thread unsubscribe 操作、direct personal mention、human 侧的 mute / quiet、agent row 的 `no-wake`；
4. **最后做 surface projection**：human 侧投影成 unread / badge / push；agent 侧不是投影，而是直接按 agent row 的 `auto-wake / no-wake` 决定 runtime 行为，但这一步不能把 `delivered_no_wake` 偷偷改写成 `not_delivered`。

delivery / wake 不应直接改写 readable；它们只是决定“默认要不要把这条消息算进你的待处理面”。

`scope_channel_profiles` 不属于普通 message routing 的 recipient truth。它是 channel 级 agent roster / config source：用于展示 channel 中有哪些 agent entry、驱动 public parent-space participant projection、驱动 private Agent Space participant projection，以及在 owner-private explicit mention 时解析可 materialize 的 private-agent candidates。parent channel / thread 的 ordinary mention、broadcast、default routing 必须从 `scope_memberships` 读取 recipients，并从 `scope_notification_settings` 读取 wake intent。

Parent channel 的普通 `@human` 或 `@agent` 不改变 dense/default delivery 的基础语义：public auto-wake agents 仍然按 `scope_memberships` / `scope_notification_settings` 收到普通 channel delivery；direct agent mention 只是在这个基础上额外记录 sparse direct-mention intent，并保证被点名 agent wake。

### 4.2 channel

对 channel shared timeline：

- **human members**：默认进入 pending；若无 override，表现为 `active`；
- **public agents**：默认也是 channel delivery 的候选；在无 override 时，其执行结果应写成 `delivered_no_wake`，而不是 `not_delivered`，agent row 默认 state = `no-wake`；
- **private agents**：普通 channel 消息不会直接作为“channel participant message”送给 private agent；owner 与 private agent 的默认协作通过 owner 对应的 channel-level Agent Space 承接；
- **owner direct mention private agent**：如果 owner 在 parent channel 中 explicit mention private agent，routing target 仍是对应的 channel-level Agent Space row；private agent 的 wake current space / `read_history` scope 都是这个 Agent Space，而不是 parent channel；
- **unauthorized private mention**：non-owner 对 private agent 的 direct mention 不产生任何 routing side effect；
- **auto-publish**：一旦 Agent Space 内容被 materialize 成 channel parent message，它就是一条普通 channel message，human members 和 public agents 都按 channel 普通规则接收。

换句话说，channel 里需要区分两类路径：

1. **shared timeline 本身**：human members + public agents；
2. **owner-private sidecar collaboration**：owner + private agent 的 channel-level Agent Space。

### 4.3 thread

对 thread timeline：

- **readable** 仍可能广于 pending（例如 channel participant 可以通过 parent 看见 thread），但默认 pending 只属于：
  - thread participants
  - explicit subscribers
  - 被 direct personal mention 的对象
- **human participants / subscribers**：默认 `active`，除非被用户改成 quiet / mute，或执行 unsubscribe 操作；
- **thread unsubscribe**：把该 human 从默认 followed / pending set 中移除；
- **direct personal mention on a not-subscribed thread**：这次 `@` 不只是穿透，而是会把该 thread 自动重新加入 subscription set；
- **current reply / current participation on a not-subscribed thread**：同样会把该 thread 重新加入 subscription set；
- **public agent**：一旦进入 thread collaboration scope（参与 / 订阅 / 被 direct mention），默认就是 `auto-wake`；
- **private agent**：owner 在 thread 中显式 mention private agent 时，routing 进入对应 `(owner, thread)` Agent Space；该 thread-level Agent Space 对 owner 默认是 `active`，对 private agent runtime 默认是 `no-wake`，即一次明确但默认低噪声的 scoped collaboration；private agent 的 wake current space / `read_history` scope 也都锁在这个 Agent Space 中，parent thread row 只作为 provenance；本次被 mention 的 private agent 仍会因这条当前事件 wake，而其他被手动设成 `auto-wake` 的 private agent 也照常 wake。

因此 thread 的核心特征是：

> **readable 可以是广义的，但默认 pending 是显式 scoped 的。**

### 4.4 dm

对 dm：

- 所有 DM participants 都默认进入 pending；
- 默认强度是 `active`；
- v1 不定义 unsubscribe；
- quiet / mute 只影响 surfacing / wake，不影响 readable；
- 如果 agent 作为 DM 对话方出现，它同样走 DM 默认 `auto-wake` 的 runtime 语义。

因此 DM 是前景对话，不存在“默认 quiet 跟随”的设计目标。

### 4.5 agent_space

对 Agent Space：

- 默认 recipients 只有 **owner + private agents**；
- observer / parent participant 的 browse 能力不等于 delivery；
- channel-level Agent Space 对 owner 的 human default = `active`，对 private agent runtime default = `no-wake`；
- thread-level Agent Space 对 owner 的 human default = `active`，对 private agent runtime default = `no-wake`；
- Agent Space 内的 authoring row 和 auto-published parent message 是两条不同的消息对象：
  - Agent Space row 只对该 Agent Space 的 recipients 计算 delivery；
  - auto-published parent message 只对 parent space 的 recipients 计算 delivery。

这保证了：

- owner/private agent 的私下协作不会因为 parent observer 存在而自动转化成 delivery；
- 一旦内容被 auto-publish，它就彻底按照 parent 的普通规则处理，而不是双重 fan-out。

## 5. Delivery outputs

### 5.1 Human outputs

human 侧，pending 主要投影成：

- unread count
- badge / red dot
- inbox / followed list / activity feed 冒泡
- push / notification

三档订阅状态加上“不在 subscription set 中”的区别，主要体现在这些 surface 是否出现：

- `active`：有 unread / badge，也有 push
- `quiet`：有 unread / badge，但无 push
- `mute`：无 unread / badge，也无 push
- not subscribed：thread 不在默认 followed / pending 集，也就不再有上述 surface

### 5.2 Agent outputs

agent 侧，runtime 真正有意义的输出只有两档：

- `auto-wake`
- `no-wake`

也就是说：

- `auto-wake`：当前 space 的消息会 self-wake；
- `no-wake`：当前 space 的消息不会 self-wake；
- runtime 是否维护显式 queue、cursor、polling，仍属于实现细节，不属于本篇语义真相；
- 但无论 runtime 内部怎么做，都不能违反 §2.6 的限制：**不自动 preload 当前 space 上下文，不跨 space 注入内容。**

### 5.3 delivery ≠ readable

无论 human 还是 agent：

> **不 delivery ≠ 不可读。**

双方都可以主动进入某个 readable space 翻历史；delivery 只决定“系统是否默认把它算作你的待处理面”，不是“你能不能看见”。

### 5.4 Author suppression

全局再加一条不变量：

> **一条消息永远不会给它的同一 `author_member_id` 自己制造新的 pending surfaces。**

这意味着：

- author 自己的消息，不会再给这个 author 本人产生 unread / badge / push，也不会把这条 event 记成 author 自己的一条新的 message-backed execution fact；
- 这条 suppression 同时作用在 recipient calculation 之后的 **execution ledger** 与 **wake fan-out** 上：author 仍可通过历史读取这条 row，但不会因为它再次成为自己的 pending / wake 对象；
- owner 在 parent authored 的消息自动 mirror 回自己的 private Agent Space 时，也不会仅因为这条 mirror 就给 owner 自己制造新的 pending surfaces；
- private agent 在自己的 Agent Space 中 authored row 时，也同样不会把自己重新算成一条新的 wake / delivery 结果；
- 这条 suppression 是 event-level 的，不改写该 space 的默认 state。

同时，authoring 本身**不推进 read cursor**：

- 某个对象在 space 中 authored 一条新消息，不代表它已经“看过了”之前所有别人的未读消息；
- 因此，authoring 不能把 cursor 无条件推进到自己最新写出的 row；
- cursor 只应在该对象明确 **view / open / consume** 这个 space 时推进；
- self-authored row 之所以不产生 unread / badge / push / self-wake，依赖的是 author suppression，而不是“写完就把 cursor 偷偷前推”；
- owner 在 parent authored 的消息自动 mirror 回 owner 自己的 private Agent Space 时，这条 mirror 也遵守同一规则：**mirror 不推进 owner 的 Agent Space cursor**。

因此，某个 space 默认是 `active`，并不意味着作者会被自己刚写出的消息再次打扰。

### 5.5 Message-backed vs signal-backed delivery

本篇的主语始终是 **message-backed delivery**：

- 由某条 `scope_messages` row 触发；
- 按 `scopes` / `scope_memberships` / `scope_notification_settings` 计算默认 pending 与 wake；
- 再落成 message-backed 的 execution truth。

但 agent server-side unread truth 不只有 message-backed 一种输入。

像 reaction 这类 **signal-backed delivery**：

- 不是新的 `scope_messages` row；
- 不复用 message-backed recipient ledger；
- 有自己独立的 durable ledger、suppression 语义与 diagnostics 语义；
- 仍然与 message-backed unread 一起组成 historical server `/receive` 与 unread existence 的总 truth。

因此，O2 在这里锁的边界是：

- **message-backed**：由本篇定义默认 delivery / wake contract；
- **signal-backed**：继续由对应 canonical spec 定义（例如 `../message/reaction.md`），但它们必须和 message-backed unread 一起进入同一个 server-side unread model；
- O2 不重新定义 reaction ledger、remove-before-consume suppression 或 signal-specific diagnostics，只要求这些 signal-backed unread 不能被 message-backed 模型“吃掉”。

## 6. Human state model

human 侧对**已订阅对象**保留三档状态：

- `active`
- `quiet`
- `mute`

`unsubscribe` 在 v1 不再是第四档持久状态，而是一个把 thread 从 subscription set 中移除的操作。

### 6.1 Active

`active` 表示：

- 进入默认工作集；
- 有 unread / badge / 冒泡；
- 有 push / notification；
- 点进去从未读位置落点。

这是最强的默认 attention-demand 状态。

### 6.2 Quiet

`quiet` 表示：

- 仍然进入默认工作集；
- 保留 unread / badge / 冒泡；
- 不发 push / notification；
- 点进去仍从未读位置落点。

产品含义是：

> “我仍然想看到这里有活跃，但不想被即时打扰。”

### 6.3 Mute

`mute` 表示：

- 仍然保留在默认结构 / membership 中；
- 无 unread / badge / 冒泡；
- 无 push / notification；
- read cursor 保留，点进去仍从未读位置落点。

> 已拍定：`mute` suppresses surfacing, not cursor semantics.

若 `mute` 状态下收到 direct personal `@mention`：

- 当前这条事件按 mention override 处理；
- 会产生 push / notification；
- 会出现一次性的 unread / badge surfacing；
- 该次事件过去后，后续未被 direct `@mention` 的新消息仍回到 mute 的默认行为。

产品含义是：

> “我还在这里，但连红点都不想看见。”

### 6.4 Unsubscribe operation

`unsubscribe` 只在线程上定义，而且它是一个**操作**，不是持久状态：

- 执行 `unsubscribe` 时，把该 thread 从 followed / default attention set / subscription set 中移除；
- 移除后，无 unread / badge / 冒泡，也无 push / notification；
- thread 仍然 readable，但不再被视为“默认在跟”的 thread；
- 若该 thread 随后收到 direct personal `@mention`，则这次 `@` 会把它自动重新加入 subscription set；
- 若用户在该 thread 中发生本次 reply / 本次参与，也会把它重新加入 subscription set；
- 被自动加回后，thread 回到该 space type 的默认状态（即 thread 默认 `active`），而不是恢复 unsubscribe 之前的 explicit state。

### 6.5 Mention 对 human 状态的影响

v1 下，human 侧 mention 语义是：

- direct personal `@mention` 穿透 `quiet` / `mute`；
- direct personal `@mention` 命中一个不在 subscription set 中的 thread 时，不仅产生这次注意力返回，还会把它自动加回 subscription set，并回到该 space type 的默认状态；
- broadcast / group / role mention 不在 v1 scope 内；
- mention override 不会穿透 private inert boundary。

### 6.6 Human states summary table

| 状态 | 默认工作集 | unread/badge | push/notification | 点进去落点 |
|---|---|---|---|---|
| active | ✓ | ✓ | ✓ | 未读起点 |
| quiet | ✓ | ✓ | ✗ | 未读起点 |
| mute | ✓ | ✗ | ✗ | 未读起点 |

不在 subscription set 的 thread 不适用上述三档；它只是 readable，但不再进入默认 followed / pending / surfacing。

## 7. Agent runtime projection

### 7.1 Agent runtime only has two meaningful states

agent runtime 层，v1 只保留两档：

- `auto-wake`
- `no-wake`

原因：

- agent 没有 human 那种 unread / badge / inbox / red-dot surface；
- 一旦禁止跨-space context injection / implicit preload，`quiet / mute` 以及“not subscribed”都在 runtime 行为上表现为 `no-wake`；
- 因此，agent runtime 的真正合同只剩：
  - 当前 space 的消息是否 self-wake。

### 7.2 Auto-wake

`auto-wake` 表示：

- 当前 space 的新消息本身就是 wake trigger；
- runtime 只把触发消息本身交给 agent；
- agent 若需要更多上下文，只能对当前 space 显式调用 `read_history`；
- agent 处理这次 trigger。

### 7.3 No-wake

`no-wake` 表示：

- 当前 space 的新消息本身不触发 wake；
- 如果该事件原本就在默认 delivery / pending 集合中，server 仍要把它记成 **`delivered_no_wake`** 的 execution truth，而不是直接降成 `not_delivered`；
- 该 space 也不会因为别的 space 的 wake 被顺手带入 prompt 或默认读取；
- 只有这个 space 自己出现 explicit trigger，或 owner 明确要求读取时，agent 才会对该 space 显式 `read_history`。

### 7.4 Intent truth vs execution truth

本篇在语义层锁定两层 durable truth：

1. **intent truth**
   - `scope_notification_settings` 的 row presence 表示对象是否在 subscription set 中
   - row 存在时，按对象类型取不同 enum：
     - **human row**：`state ∈ {active, quiet, mute}`
     - **agent row**：`state ∈ {auto-wake, no-wake}`；这里同时覆盖：
       - channel / thread surface 中 public agent 的 per-surface wake row；
       - Agent Space 中 private agent 的 wake row；
   - row 不存在时，表示该对象不在默认 pending set 中
2. **execution truth**
   - 具体 message-backed 事件是否真的被写成 agent delivery ledger
   - 这层 truth 用于 historical server `/receive`、busy-turn continuation、replay、diagnostics、archive replay
   - v1 至少区分三种结果：

| execution result | 含义 |
|---|---|
| `delivered_wake` | 事件进入 agent delivery ledger，并触发当前 space 的 runtime wake |
| `delivered_no_wake` | 事件进入 agent delivery ledger，但不触发 wake；之后只能在显式 trigger / `read_history` / diagnostics / replay 中被消费 |
| `not_delivered` | 事件没有进入该 agent 的默认 delivery ledger；若需要解释原因，只能通过 diagnostics 给出 exclusion reason |

`no-wake` 只能决定 `delivered_wake` 与 `delivered_no_wake` 的分界，不能把一个本来应被 delivered 的事件顺手改写成 `not_delivered`。

human 与 agent 的 intent enum 不同构，原因：

- human 需要 unread / badge / push 三档差异，因此三档是必要的 durable truth；
- agent 没有 badge / push / inbox 概念，runtime 只关心"当前 space 新消息是否 self-wake"这一件事，因此 agent row 的 durable state 本身就是 `auto-wake / no-wake`，不需要再从一个更上游的三档 enum 投影下来；
- 这意味着 agent row 不是 human 三档的 projection，而是独立的 durable intent truth。

not subscribed 对两类对象的含义一致：不进入默认 pending，因此不会产生 runtime wake target。

canonical product truth 在 agent 侧因此就是：

> **agent row 的 durable state = `auto-wake` / `no-wake`；它就是 intent truth 本身，不是别的 enum 的 projection。**

实现层如果为了性能需要额外 cache / materialized projection，可以自行增加，但它们不能替代这两层 canonical semantic truth。

### 7.5 Event-level wake override

虽然 agent 侧运行时只剩 `auto-wake / no-wake` 两档，但 direct personal mention 仍然可以对 **当前事件** 产生 wake override；这是 §8.2 在 agent runtime 侧的具体体现：

- 如果一个 `no-wake` space 自己收到 direct personal mention，当前事件可以 wake；
- 对 private agent 来说，这里的“当前事件”始终是 routing 后进入对应 Agent Space 的那条 row，而不是 parent channel / thread 原 row；
- 如果同一个 Agent Space 中另一些 private agent 已经被显式设成 `auto-wake`，则 mention 不会把它们压回 `no-wake`；当前事件的实际 wake set = `mentioned ∪ auto-wake`；
- 但这个 override 只作用于当前 space / 当前事件；
- 它不意味着别的 quiet / mute space，或不在 subscription set 的 thread，会被顺手带入 prompt。

### 7.6 Subscription write authority

v1 里，subscription state 的写权限也要与对象类型分开：

- **human row**：由该 human 自己或具备更高权限的 operator 修改，写入 `active / quiet / mute`；
- **agent row**：由 owner / operator 修改，直接写入 `auto-wake / no-wake`；
  - channel / thread 中的 public agent row，owner = 当前 public agent 的 owner，改的是 `(host surface, public agent)` 这一条 durable wake truth；
  - Agent Space 中的 private agent row，owner = 当前 private agent 的 owner，改的是该 Agent Space 里的 private-agent wake truth；
- **agent self-config**：暂不进入 v1 product scope；agent 可以在行为层选择是否主动 `read_history`，但不能把这种行为选择写回 canonical subscription truth。

### 7.7 Per-surface notification panel

v1 把 channel / thread / agent_space 的 notification entry 统一成 **header 上独立的 Notification 图标**。这表示 notification 不再塞在 generic `⋯` 菜单里，但原 `⋯` 中的 archive / settings / leave 等其它 action 仍然保留。点击 Notification 图标后打开一个 **per-surface notification panel**，面板内容固定分成两段：

1. **你自己**
   - 单行，左侧是当前用户自己的头像 + 名字；
   - 右侧是下拉框，值为 `Active / Quiet / Mute`；
   - 这行直接读写 §6 的 human 三档 durable truth，不再发明第二套 notification enum。
2. **你拥有的 agents**
   - **channel / thread**：列出当前 surface 中，owner = 当前用户的 **public agents**；
   - **agent_space**：列出当前 Agent Space 中，owner = 当前用户的 **private agents**；
   - **channel / thread** 的 public-agent 行：左侧头像 + 名字，右侧下拉 `Auto-wake / No-wake`；
   - **agent_space** 的 private-agent 行：左侧头像 + 名字，右侧至少包含 `Auto-wake / No-wake` 下拉；
   - 其中：
     - **channel-level Agent Space**：private-agent 行还包含一个可写的 `☐ Auto-publish to channel` 控件，它直接读写 `03-share-publish` 中 `(owner × private-agent × host-channel)` 的 channel-level policy；
     - **thread-level Agent Space**：private-agent 行还包含一个可写的 `☐ Auto-publish to thread` 控件，它直接读写 `(agent_space × private-agent)` 的 thread-level policy；该值默认 on，但与 host channel 开关独立；
   - 这些 wake 行直接读写 §7 的 agent row durable truth，不引入 per-user-per-agent 的额外 override。
   - channel / thread 的 Members 侧栏在“你拥有的 agents”分组提供 **全部唤醒 / 全部静音**；它们分别把当前 owner 在当前 surface 可切换的全部 agent row 批量写成 `auto-wake / no-wake`，不能跨 owner 或跨 surface 修改。

DM header 在 v1 **不纳入**这个 notification panel。DM 继续沿用本篇现有默认：human = `active`，agent runtime = `auto-wake`，不为 DM 单独增加这套 panel UI。

这两段的 durable truth 分别是：

- **human 行**：复用现有 `scope_notification_settings` human row（`active / quiet / mute`）；
- **private agent 行**：复用 Agent Space 里已经存在的 private-agent wake row；
- **public agent 行**：v1 新增 **per-(host surface, public agent)** 的 wake row，分别覆盖 channel / thread；
  - 它是当前 surface 上的 durable truth，不是 agent 全局 Start / Stop 的投影；
  - 这条 row 同样落在 `scope_notification_settings` 的 **agent row** 上，不新开另一张 public-agent wake 表；
  - 初始值沿用本篇默认表：
    - channel-level public agent row 初始为 `no-wake`
    - thread-level public agent row 初始为 `auto-wake`
  - seed 时机是：**首次该 public agent 在该 surface 进入 pending delivery / collaboration scope 时，lazy seed 成默认值**；此后任何 notification panel 写入都作为 explicit row 覆盖默认表。

visibility / ACL 规则：

- 任何 readable 成员都可以打开当前 surface 的 notification panel，并且永远能看到“你自己”这一行；
- agents 那一段只列 **owner = 当前用户** 的 agents；
- 非 owner 看不到、也不能修改别人的 public / private agent row；
- archived surface 进入只读状态；read-only non-owner Agent Space 同样只读；
- 上述只读是 **panel 整体只读**，包括“你自己”这一行，不允许在 archived / read-only 场景下继续切 `Active / Quiet / Mute`；
- v1 不提供 agent self-config，也不提供 agent 详情页里的 subscriptions 总表。

v1 明确不做：

- per-keyword highlight；
- per-user-per-agent override（wake 只按 `surface × agent` durable truth，而不是 `viewer × surface × agent`）；
- agent 详情页上的全局 subscriptions 总表 / 批量管理页。

## 8. Override precedence

当前已锁的高层顺序：

1. 硬边界（non-participant / private inert boundary）
2. explicit personal mention
3. subscription-set membership + explicit quiet/mute state
4. space-type defaults

### 8.1 Hard boundaries first

如果一个对象：

- 不是该 space 的 participant / readable subject；或
- 命中了 private inert 边界；

那么这条消息不会进入它的 default pending / wake 计算。

这一步优先级最高。

### 8.2 Personal mention then upgrades the current event

v1 只定义 personal mention：

- direct personal mention 必须是**服务端写入时**经过 canonical inert-syntax 过滤、权限校验并持久化为 routing fact 的 personal mention；纯文本长得像 `@someone` 但落在 fenced code / inline code / blockquote / 外部 payload 文本中的内容，不算 mention override；
- direct personal `@mention` 穿透 quiet / mute；
- direct personal `@mention` 命中一个不在 subscription set 中的 thread 时，会把该 thread 自动加回 subscription set；
- 对 agent 来说，direct personal mention 是当前 space / 当前事件的 wake override；
- mention override 不能跨越 private inert 或非-readable 边界。

### 8.3 Explicit state beats defaults

没有 explicit mention 时：

- thread unsubscribe 操作先把该对象从默认 followed / pending set 中移除；
- quiet / mute 决定 human 是否还有 unread/badge/push；
- agent row 自己的 `auto-wake / no-wake` 直接决定当前 space 新消息是否 self-wake；agent 行为不由 human 侧的三档映射决定；
- thread auto-follow 只基于**当前事件**（被 `@` / 本次 reply / 本次参与），不回溯 past participation / past reply history；
- 这些 explicit state / membership 结果都比 space-type default 更优先。

### 8.4 Defaults fill the remainder

只有在前面三层都没有改变结果时，才落回 space-type defaults：

- channel-level public agent：human n/a；agent runtime default = no-wake（用于 seed 该 surface 上的 public-agent wake row）
- thread-level scoped collaboration：human default = active；public agent runtime default = auto-wake（用于 seed 该 thread surface 上的 public-agent wake row）
- channel-level private Agent Space：owner human default = active；private agent runtime default = no-wake
- thread-level private Agent Space：owner human default = active；private agent runtime default = no-wake
- dm：human default = active；agent runtime default = auto-wake

## 9. Defaults table

本节把 `shared-spaces-and-agent-spaces.md §8.2` 的默认强度，映射到 O2 的 human / agent 合同中。

### 9.1 Human defaults

| 场景 | human 默认 |
|---|---|
| channel member on shared channel timeline | active |
| thread participant / subscriber | active |
| thread readable but not followed / not participating / not mentioned | 不进入默认 pending |
| dm participant | active |
| owner on channel-level private Agent Space | active *(self-authored / self-mirror events do not surface back to the same owner)* |
| owner on thread-level private Agent Space | active |

### 9.2 Agent defaults

| 场景 | agent runtime 默认 |
|---|---|
| public agent row on channel-level surface | no-wake |
| public agent row once in thread-level collaboration scope | auto-wake |
| private agent on channel-level Agent Space | no-wake |
| private agent on thread-level Agent Space | no-wake |
| dm participant agent | auto-wake |

### 9.3 Combined reading rule

默认矩阵的正确读法是：

- **human**：先问这个 space 默认是否进入我的 pending / surfacing；
- **agent**：先问这个 space 默认是否 self-wake；
- 两侧都允许再被 explicit state（quiet / mute）、subscription-set membership 和 explicit mention 覆盖；
- auto-published parent message 一律走 parent 所在行，不因为它来自 Agent Space 而套用 Agent Space 默认。

## 10. Relationship to runtime reliability / supersede notes

### 10.1 Relationship to runtime reliability

`specs/message/delivery-runtime.md` 仍然定义：

- busy-turn continuation
- daemon / runtime evidence
- historical deferred / replay / server `/receive` authoritative unread truth

本篇不重写这些 runtime reliability 细节，只定义它们之上的产品语义：

- 哪些消息是 wake trigger；
- 哪些消息只进入 pending / surfacing；
- human / agent 两侧的 state model 各自是什么（human 三档、agent 两档），以及 human 侧 state 如何投影为 unread / badge / push surface；
- 哪些上下文访问是允许的，哪些是不允许的。

反过来说，本文已经是 superseded historical record；若 `../message/delivery-runtime.md` 中的 reliability / 行为细节与本篇 Agent Space 历史产品语义出现冲突，以当前 canonical `../message/delivery-runtime.md`、Task / Scope Execution / storage specs 和代码为准。

### 10.2 Relationship to `shared-spaces-and-agent-spaces.md`

本篇是对 umbrella spec 中 delivery / wake 段落的 **细化实现合同**：

- umbrella 锁默认强度与边界原则；
- 本篇把这些原则细化为 human 三档 + unsubscribe 操作、agent row 两档（`auto-wake / no-wake`）、intent/execution truth 分层、override precedence、recipient calculation 与 defaults matrix；
- 因此后续实现若问“quiet 到底 suppress 哪一层”“not-subscribed thread + direct mention 到底怎么处理”，应以本篇为准，而不是回到 umbrella 的高层措辞重新解释。
- 特别是：channel-level public agent 默认 `no-wake` 的消息，在这条 historical design line 中曾被要求显式记成 `delivered_no_wake`，而不是含混地写成 suppressed/not-delivered；该要求不覆盖当前 daemon-local queued delivery / read_history 语义。

### 10.3 Pending follow-up sections

即便 draft 已经补全主干，下面几项仍待后续实现或相邻 spec 展开：

- 更细的 per-space implementation algorithm（特别是 `scope_notification_settings` 的持久化 / 索引形态）；
- attribution 标记若要进入 UI，应在 `04-surface` 中定义其 render 语义，但不得改动 O2 已锁定的 parent 普通 fan-out；
- 与 rollout / migration 阶段的切换时刻说明。

## 11. Current closure level

当前主干语义已经锁定完成：

- canonical product truth = default intent truth（`scope_notification_settings` row presence + `active / quiet / mute`）加 per-event execution truth；
- agent row 的 durable intent truth = `auto-wake / no-wake`，而不是 human 三档的下游投影；
- message-backed delivery 与 signal-backed delivery 已明确分层，reaction 等 signal 继续走各自 canonical spec 的 durable ledger；
- direct personal mention 必须是 canonical routing fact；
- self-authored 不产生 unread / badge / push，且 authoring 本身不推进 cursor；
- owner parent-authored message mirror 到 owner 自己的 Agent Space 也遵守同一条 cursor 规则：mirror 不推进 cursor，只 suppress self-surface。

换句话说：本篇当前已经没有必须阻塞兄弟 spec 的未决产品问题；后续剩余工作主要转向实现细化与相邻 spec 的 render / rollout 设计。
