---
status-class: historical
design-state: superseded-unshipped-v1
historical-refs:
  - specs/agent-space/owner-private-agent-delivery.md
  - specs/agent-space/shared-spaces-and-agent-spaces.md
---
# Agent Space 03：Share / Publish Design

日期：2026-04-24
状态：superseded historical record。本文只记录未上线 Agent Space transcript /
publish / auto-publish 实现线，不能作为当前产品、schema、API、runtime 或
用户可见 surface 的合同。当前实现已经删除 manual publish / auto-publish gate、
`scope_share_cards` linkage、Agent Space panel/API/realtime 以及
Agent Space transcript write/copy 路径；private-agent execution 使用 parent
channel/thread/task target + hidden owner-private context，private-agent 输出直接写
parent timeline。需要实现当前语义时请参考 `runtime-api-design.md`、Task 收敛
specs、storage spec 与代码。
范围：Agent Space 的 owner-gated share-in 与 publish-out 语义；single / packed multi / per-row multi share；manual publish；auto-publish policy；copy+link 模型；授权边界。
非目标：wake/default-state 语义（见 `delivery-wake-design.md`）；inline/panel render treatment（留给 04-surface）；rollout/migration sequencing；attribution 视觉设计；group/role mention 语义。

## 0. 在序列中的位置

本篇默认以下前置已经成立：

- `01-space-core` 定义统一 Space 对象模型、`scope_messages` / `scope_message_sources` / parent-child 关系，以及 structured refs 的 durable provenance 语义；
- `02-delivery-wake` 定义 pending / unread / wake 合同，并锁定 auto-published parent message 一旦 materialize，就按普通 parent fan-out 处理；
- 本篇只定义内容如何**进入** Agent Space（`share-in`），以及如何从 Agent Space **出去**（`publish-out`）。

因此，历史实现当时关心的是：

1. 哪些 parent 内容可以被 owner 显式带进 Agent Space；
2. Agent Space 里哪些内容有资格变成 parent shared message；
3. 这个“变成”过程如何保持 authoring history、delivery 边界与 traceability。

未上线 implementation line 曾把本篇的 share-in / publish-out 主路径落地；这些路径已被 parent target + hidden owner-private context 模型取代。本文后续旧 wording 只用于理解历史实现，不得作为当前实现依据。

## 1. 目标

`03-share/publish` 当时要回答四个产品问题：

1. owner 如何显式把 non-owner 的 parent context 带进 private Agent Space？
2. share 之后，真正进入私有协作的对象是什么？
3. private-agent-authored 的 Agent Space 内容如何变成一条真实的 parent shared message？
4. 如何在不打破 owner/private 边界的前提下，把这个 publish 自动化？

历史设计目标曾试图同时保住四件事：

- **private ingress 仍然 owner-gated**：别人消息不会自动进入 private Agent Space；
- **parent egress 仍然 owner-gated**：private agent 不会默认对外说话；
- **timeline 语义保持简单**：parent row 一旦存在，就只按 parent 规则运作；
- **trace / audit 可追溯**：share 与 publish 都能回到明确的 source refs / structured refs。

## 2. 词汇

### 2.1 Share-in

`share-in` 指：

> owner 显式授权 parent space 中的上下文进入 Agent Space transcript。

v1 的 canonical product form 是：

- 一条或多条**属于 Agent Space 的 owner-authored quote/share row**；
- 每条 row 带 1 个或多个指向 parent message 的 structured source refs。

### 2.2 Publish-out

`publish-out` 指：

> Agent Space 内 private-agent-authored 的内容，被 materialize 成一条真实的 parent shared message。

publish 的 target 永远是 **parent space**（`channel` 或 `thread`），而不是某个 UI surface。

### 2.3 Copy + link

`copy + link` 是 publish 的核心模型：

- Agent Space 中原始 row 继续保留为私有 authoring / collaboration row；
- parent 中再 insert 一条真实的 shared message；
- 两条 row 用 structured refs 关联；
- 这是一次真实的 parent timeline insert，不是 projection / inline render trick。

### 2.4 parent source row / Agent Space authoring row / parent published row

这里必须严格区分三种对象：

- **parent source row**：原本就在 parent 中存在、可能被 share 进 Agent Space 的消息；
- **Agent Space authoring row**：Agent Space 内的私有协作消息；
- **parent published row**：由 publish / auto-publish 产生的 parent shared message。

这三者可能在同一个用户动作链中先后出现，但它们从来不是“一条消息在不同 surface 的三种影子”。

## 3. Scope

### 3.1 本篇覆盖

- single-message share-in
- packed multi-message share-in（1 条 Agent Space row + 多个 parent source refs）
- per-row multi-message share-in（一次 share action 生成多条 Agent Space row，每条 1 个 parent source ref）
- owner-authored quote/share object 语义
- share-in / publish-out 的 structured refs 与 publish attribution 最小合同
- manual publish from Agent Space to parent
- auto-publish authorization policy 与 channel/thread 独立配置
- channel main 中针对 private agent 的 per-message thread-routing modifier（最终文案留给 04-surface）
- published row 的 copy+link 语义
- parent delivery 与 Agent Space delivery 的边界

### 3.2 本篇不覆盖

- inline / panel / 其它 UI render 细节
- folded summary 的最终视觉稿
- publish attribution 的视觉样式 / iconography
- rollout / cutover sequence
- trigger 作为 automation 入口时的具体 wiring 细节
- owner-side dual-post / send-to-parent convenience 的独立产品合同
- richer share 的默认入口偏好、packed / per-row 的互转 UX，以及是否需要额外 higher-level share-action 对象（这些作为 §12 的剩余 open question 讨论）

## 4. 核心不变量

### 4.1 没有隐式 ingress

没有 owner 行为时，non-owner 的 parent message 不会进入 private Agent Space。

这里的 `private` 指 **owner-gated ingress / egress authority**，不是“parent observer 永远无法 browse Agent Space transcript”的可见性承诺；是否可 browse 仍由 O1 / umbrella 的 observer 规则决定。

### 4.2 没有隐式 egress

没有 owner 开启 publish authority 时，Agent Space-authored 内容不会变成 parent shared message。

### 4.3 Parent message 和 Agent Space message 永远不是同一 row

如果内容从 Agent Space 发布到 parent，parent row 必须是一个独立的消息 row。

### 4.4 Parent fan-out 保持普通语义

一旦 parent row 已经 materialize，它的 delivery / unread / badge / wake 全部按 `02-delivery-wake` 的普通 parent 规则走（尤其是 `§3.2「auto-published parent message 不做特殊 fan-out」` 与 `§4 Recipient calculation`），不再为“来自 Agent Space”额外开一层 delivery 特判。

### 4.5 Render 不等于 routing

inline / panel / 其它 surface 可以渲染 Agent Space transcript，但 render 本身不创造 parent delivery。

## 5. Share-in model

### 5.1 为什么 share 是核心能力

private agent 默认不能自动看到 non-owner 的 parent message；如果没有 share，它就只能跟踪 owner 自己的发言流。因此 share 不是边角功能，而是 private Agent Space 成立的必要条件。

### 5.2 当前规格支持的 share 形状

当前 O3 规格直接覆盖三种 share 形状：

- **single-message share**：把一条 parent message share / attach / forward 进当前 Agent Space；
- **packed multi-message share**：一次 share action 生成 **1 条** Agent Space row，并在该 row 上挂 **N 个** parent source refs；
- **per-row multi-message share**：一次 share action 生成 **N 条** Agent Space row，每条 row 各挂 **1 个** parent source ref。

这里的 “当前” 指 owner 当前所处的 Agent Space 上下文：

- channel-level Agent Space，则 source 来自该 channel 主 timeline；
- thread-level Agent Space，则 source 来自该 thread，或当前 thread 所在 host channel 中被显式选择的相关 parent context。

进一步地，本篇锁定 richer share 的 **canonical selection 对象** 是 **message-set / thread-set**，而不是 time-range primitive；任何 UI 侧的时间筛选都只作为帮助选取消息的 pre-filter，最终 share action 依然只携带明确的 message refs。

### 5.3 最小 share 对象与两种 multi-message shape

无论 single / packed / per-row，share-in 的最小协作单元都仍然是：

- 一条 **owner-authored quote/share row**，归属在 Agent Space transcript 中；
- 该 row 的 payload 必须带上 **owner 授权后的不可变 quoted snapshot**，使 private agent 只读取当前 Agent Space transcript 时，也能直接获得被 share 的上下文本体；
- 该 row 带 1 个或多个指向 parent message 的 structured source refs；
- owner 可以附 message，也可以只转发 source context；若附 message，则该 message 必须在目标 transcript 中 materialize 成一条独立的 owner-authored ordinary row，排在本次 share rows 下方，而不是被塞进 share row body。

三种 shape 的差别只在 row 数与 ref cardinality：

- **single**：1 条 owner-authored row + 1 个 source ref；
- **packed**：1 条 owner-authored row + N 个 source refs；
- **per-row**：N 条 owner-authored row + 各自 1 个 source ref。

若 confirm step 同时填写了 message，这条 message **不计入**上面的 shape cardinality：single / packed / per-row 只描述 shared-context rows 的数量；附加 message 永远是额外的 1 条 ordinary row，排在这些 shared-context rows 之后。

这意味着：

- `scope_message_sources` 在 richer share 下允许 **n:1**（packed）与 **1:1**（per-row）两种合法 cardinality；
- richer share 不需要新的 delivery object type，仍然落在同一套 row + refs 模型里；
- per-row 模式下，N 条 row 仍属于同一次 share action，便于 audit / undo / batch provenance；
- private agent 的 runtime 输入以这些 Agent Space row 的 **quoted snapshot payload** 为准，而不是在处理时跨 space 反查 parent source。

之所以选 owner-authored quote/share row，而不是“把 source row 本体送进 Agent Space”，是因为 share-in 不是 delivery shadow，而是一次显式的 owner authoring act。

### 5.4 Share-in 是 owner 的 authoring act，不是 source delivery

source parent message 不会直接 delivery 给 private agent。

真正发生的是：

- owner-authored Agent Space quote/share row 成为实际协作对象；
- quoted snapshot 承担“这次明确授权给 private agent 读取的上下文内容”；
- source refs 提供 trace / audit continuity；
- 当前与后来加入的 private agents，都通过这些 quote/share row 及其 refs 看到上下文。

因此，share-in 之后的“真实私有协作对象”是 **quote/share row**，而不是 source row 的某种私有副本。

### 5.5 Share-in 的行为合同与结果

一次成功的 share-in 之后：

- single / packed 模式会新建 **1 条** Agent Space row；
- per-row 模式会新建 **N 条** Agent Space row，但这些 row 仍属于同一次 share action；
- parent source row 继续留在 parent timeline，不被改写；
- 新 row 的 payload 中已经冻结了 owner 当次授权给 Agent Space 的 quoted snapshot；后续 runtime 读取不依赖再去 parent space 反查 source 内容；
- source refs 成为两侧之间的 durable bridge；
- 不会产生额外的 direct parent→private delivery side effect，除了这些新的 Agent Space row 本身。

这意味着：

- private agent 看到的是 owner 明确提交给私有协作的对象；
- parent participant 仍只看到 parent 中的原始 source row；
- share 行为不会反向改变 parent delivery。

share row payload 的 snapshot 语义也在这里一并锁定：

- quoted snapshot 在 share 完成时即被冻结（freeze at share time）；
- 如果 source message 后续被编辑或删除，既有 Agent Space row 的 body 不会回写；
- attachments / file-like content 若被包含在 share 中，也必须以 owner 当次授权的 snapshot / quoted metadata 进入 row payload，而不是要求 private agent 运行时回 parent 取原件；
- source refs 继续负责 provenance / navigation / explainability，但不替代 share row payload 中的 quoted snapshot。

对 share surface 再补一条最低 render 合同：

- 如果某个 source ref 后续已无法解析，既有 shared-context row 仍按冻结 snapshot 渲染；
- `Jump to source` 一类导航 affordance 应降级为 disabled state 或明确的 `Source unavailable` placeholder；
- packed share 中若只有部分 refs 失效，也只退化这些 refs 的导航能力，不回写或隐藏整条 packed row。

### 5.6 当前 richer share 规格与不变量

当前规格中，richer share（例如 message range / thread snapshot / multi-message batch）不是 future extension，而是直接通过 **packed** 与 **per-row** 两种 shape 表达：

- 当 owner 想“分享一整段讨论”时，可以使用 **packed**：1 条 Agent Space row + N 个 source refs；
- 当 owner 想“把几条消息逐条贴进来”时，可以使用 **per-row**：N 条 Agent Space row + 各自 1 个 source ref。

其中，**thread snapshot = packed richer share 的一个特例**，而不是新的 shape：

- `share thread` 的结果是：在 share 时刻取该 thread 的 message-set，生成 **1 条 owner-authored packed row + N 个 refs**；
- 这组 refs 在 share 完成时即被冻结（freeze at share time）；之后该 thread 新增的 replies **不会自动**进入这个 snapshot；
- 如果 owner 想把 thread 后续的新消息也带进来，必须再做一次新的 share，它会成为另一条新的 packed share action；
- thread root ref / thread id 可以作为 convenience metadata 存在，但 canonical truth 仍然是这组明确的 message refs。

入口分工也在此锁定：

- `share thread` → 默认走 **packed thread snapshot**；
- `share selected messages` → 可以走 **packed** 或 **per-row**；
- 若想逐条分析 thread 内的若干消息，也应走 `share selected messages as per-row`，而不是把 `thread snapshot` 本身做成 per-row。

这组规则的目的，是在 richer share 下继续保住 §4.1 “没有隐式 ingress”：thread snapshot 是 frozen snapshot，不是 live mirror / thread subscription，后续 thread 中 non-owner 的新消息不会通过 live link 自动进入 private Agent Space。

两种 shape 可以并存，且都必须遵守同一个核心模型：

- 产物依然是 Agent Space 内的 owner-authored quote/share object；
- 与 parent 之间依然靠 structured refs 关联；
- 不是直接把 source row 搬成 private delivery object。

两种 shape 的下游粒度也按普通 row 语义处理，不做额外特判：

- **reply / reaction / edit / delete**：packed 挂在整条 packed row 上；per-row 则各条独立；
- **cursor / unread**：packed 作为 1 条 row 产生 1 个增量；per-row 则按 N 条普通 row 处理；
- **audit / undo**：一次 share action 可以对应 1 条 row（single / packed）或 N 条 row（per-row），但 action-level provenance 仍应可追踪。
- **per-row 分组 identity**：当一次 per-row richer share 生成 N 条 Agent Space row 时，这 N 条 row 必须具备某种 durable 的可解析分组 identity（例如 share-action id / batch id），使 action-level audit / undo / batch provenance 可以被还原；具体字段形式留给后续 schema / migration design。

## 6. Authoring 与 materialization 责任归属

### 6.1 Share-in 的 author 语义

对于 share-in，author 永远是 **owner human**。

因此当前 share object 必然是 owner-authored quote/share row，而不是隐藏的 source-message delivery shadow。

### 6.2 Publish candidate 的语义

对本篇定义的 publish-out 来说，candidate row 必须是一条 **private-agent-authored Agent Space row**。

这同时意味着：

- owner-authored 的 Agent Space quote row 不属于 publish-out candidate；
- owner-authored 的 Agent Space 内部讨论 row 也不属于 publish-out candidate；
- 如果 owner 想对外说话，应直接在 parent authored，而不是先在 Agent Space 写一条再 publish。
- 如果 owner 想借用 private agent 在 Agent Space 中已经产出的草稿/回复，以 **owner 自己** 的身份对外发言，也不走 publish-out；正确路径仍然是 owner 在 parent 手动 author 自己那条消息。

本篇不依赖“任意 Agent Space row 都可 publish”的宽模型。这里真正要解决的产品问题是：**private agent 的产出如何进入 parent shared timeline**。

### 6.3 Parent published row 的 author 语义

当一条 candidate row 被发布到 parent：

- 生成的 parent published row 的 `author` 是**原始 private agent**；
- parent published row 的作者始终是 **private agent 本身**；owner 只是决定这条 agent 消息是否允许发布到 parent；
- 这与 O2 / umbrella 已经锁定的“agent 在 parent 里说话”模型保持一致。

换句话说：

- share-in 解决的是“owner 如何把上下文带进私有协作”；
- publish-out 解决的是“private agent 如何在 owner 授权下对外说话”；
- 两者的 author 语义故意不对称。

### 6.4 Manual publish 与 auto-publish 的责任边界

manual publish 与 auto-publish 都不会改变 `author=private_agent` 这条 parent published row 的 author 语义；它们只是在**触发责任**上不同：

- **manual publish**：owner 对某条 `private-agent-authored` row 执行一次显式 publish action；
- **auto-publish**：owner 维持默认开启、可显式关闭的 output policy，让符合窗口条件的 **private-agent-authored rows** 自动 materialize。channel-level Agent Space 使用 `(owner, private agent, channel)` policy；thread-level Agent Space 使用 `(agent_space, private agent)` 独立 policy，默认开启但不继承 channel 开关。

因此：

- “谁写这条 parent row” = private agent；
- “谁允许它出现” = owner；
- “为什么现在出现” = manual act 或 policy act。

## 7. Publish-out model

### 7.1 Manual publish

manual publish 指：

- owner 对一条 private-agent-authored Agent Space row 执行一次显式 `publish out` 动作；
- 它被 materialize 成一条真实的 parent shared message；
- parent row 按普通 parent 语义存在并 fan-out；
- Agent Space row 自身继续保留。

manual publish 的产品含义是：

> owner 针对某一条私有产出，逐条触发一次对外发布动作。

### 7.2 Auto-publish

auto-publish 指：

- owner 为某个 private agent 在当前 Agent Space 层级维持一条默认开启、可显式关闭的 output policy，在无需每条再确认的情况下，把该 Agent Space 中符合窗口条件的 **private-agent-authored 内容** materialize 成对应 parent message。

它是 **owner-controlled output capability**，不是 private 边界的普遍放开。换句话说，auto-publish 的产品语义不是“让 agent 自己挑哪条可发”，而是 owner 直接决定“这个 agent 在这个 channel 及其子 thread 里的产出默认都对外发”。

v1 的 authoritative 配置入口放在对应 surface 的 **Agent Space notification panel** private-agent 行中：

- channel-level panel 控制 `(owner, private agent, host-channel)` policy，底层落在 `scope_channel_profiles.auto_publish_*`；
- thread-level panel 控制 `(thread Agent Space, private agent)` policy，底层落在 `agent_space_agent_auto_publish_settings.auto_publish_*`；
- thread-level 默认开启，但与 channel-level 独立；关闭 channel 不会关闭 thread，关闭 thread 也不会反向影响 channel 或 sibling thread scopes。

v1 不定义 selective suppression：一旦 auto-publish 已开启，该 `(owner, private agent, channel)` 范围内全部 private-agent-authored rows 都默认 publish-out；如果未来真要支持个别不外发，也只能沿 owner-side override 方向另行设计，而不是回到 agent 自己当 gatekeeper 的 marker 模型。

### 7.2A Auto-publish 的时间边界

auto-publish 不是对既有 transcript 的追溯发布，而是一个带 `effective_from` 边界的持续授权：

- owner 把某个 private agent 以 private 方式加到 channel 时，系统必须以**当前 workspace 最新 global seq** 作为该 channel-level policy 的首个 `effective_from`，也就是“默认开启，但只从加进来的那一刻开始生效”；
- owner 开启（或重新开启）某个 channel-level 或 thread-level auto-publish 时，系统必须记录一个新的 `effective_from` cut；
- 只有 **创建于该 `effective_from` 之后**、且满足 §7.4 eligibility 的 private-agent-authored rows，才会被该 policy 自动 materialize；
- `effective_from` 之前已经存在的 Agent Space transcript 仍保持私有，不会因为默认开启、再次开启或重新开启 auto-publish 而 retroactively publish；
- 如果未来要支持“把历史 transcript 批量发布到 parent”，那必须是一个独立的显式产品动作，而不是开启 auto-publish 的隐含副作用。

### 7.3 Publish target

target 永远由**当前 Agent Space 所在层级**决定：

- **channel-level Agent Space** → publish 到 parent `channel` main；
- **thread-level Agent Space** → publish 到对应的 parent `thread`。

因此 v1 不需要单独的 `target mode` 概念；publish target 不是独立配置维度，而是当前 routing 已经落到哪个 Agent Space 层级的自然结果。

永远不是 UI surface，也不会沿 parent tree 自动再往上冒。更细的 channel-level / thread-level 独立配置边界见 §9.4。

### 7.3A Channel main 中的 per-message routing modifier（auto-publish only）

为了解决“同一个 private agent 有时应该回 channel main、有时应该回新 thread”这个当前产品需求，v1 在 **channel main compose** 场景下允许一个轻量的 per-message routing modifier。其最终 UI 文案（例如“Also create a thread”或“Reply in a thread”一类 wording）留给 `04-surface` 决定，本篇只锁定它的行为语义。

它的语义是：

- 仅当 owner 在 **channel main** compose 一条消息，且消息中 `@` 到至少一个 **可路由的 private agent** 时，UI 才显示这个 modifier；
- **未勾选**：本次 interaction 仍 routing 到该 channel 的 **channel-level Agent Space**，后续 auto-publish 自然发回 channel main；
- **勾选**：系统先在这条 mention 消息下创建一个新 thread，再把本次 interaction routing 到该新 thread 对应的 **thread-level Agent Space**；与此同时：
  - 系统必须把这条 owner-authored channel main mention 作为一条 **普通 owner-authored Agent Space row** 写入该 thread-level Agent Space：它不额外生成 quoted snapshot payload，也不挂 source refs；后续 private-agent-authored reply 若命中该 thread-level Agent Space 的 auto-publish setting，就自然发到该 thread；
  - 系统也必须把同一条 owner-authored channel main mention 按 **channel-level auto-flow copy** 写入该 `(owner, channel)` 的 **channel-level Agent Space**，作为 owner×agent×channel ledger 的 durable 记录；这条 channel-level copy 允许带 source refs，但必须强制记为 **no-wake**，不能因为 mention 再次唤醒任何 private agent；
- 默认值为 **off**；如果 owner 已经在某个 thread 内 compose，则该 modifier 不显示，因为当前 routing target 已经明确是该 thread 的 thread-level Agent Space；
- 这个流程不会在 parent thread 中额外伪造一条 owner-authored root reply；private agent 在 current space 中读取到的触发上下文，来自该 ordinary owner row 本身与这次普通 mention routing，而不是一条 ghost parent message。

这个 modifier 只改变**当次 interaction 的 routing target**，不改变 publish authority：

- auto-publish 仍只回答“这条 private-agent-authored row 会不会自动对外发”；
- modifier 只回答“这次 interaction 应该先落到 channel-level 还是新建的 thread-level Agent Space”。

本篇 v1 不为 manual publish 提供对应的 per-message thread modifier；manual publish 的 target 仍然直接取决于 row 当前所在的 Agent Space 层级。

### 7.4 Eligibility

v1 在这里锁一个更直接的规则：

> publish-eligible row = **private-agent-authored Agent Space row**。

也就是说：

- owner-authored 的 Agent Space quote row 不属于 publish-out candidate；
- owner-authored 的 Agent Space 内部讨论 row 也不属于 publish-out candidate；
- auto-publish 不依赖 per-row marker / publish intent 字段；
- agent 在 auto-publish 模式下也不承担“逐条决定哪条能发”的 gatekeeper 角色。

### 7.5 Policy decision 与 row-level eligibility 的关系

row-level eligibility 回答“这条 row **属于不属于 private-agent publish domain**”；policy 回答“这条属于该 domain 的 row **现在要不要 materialize 到 parent**”。

因此完整判定链是：

1. 先判断 row 是否满足 `private-agent-authored`；
2. 再判断 owner 是否为该 private agent 打开了对应 channel 的 auto-publish authority，或是否在当前 row 上执行了 manual publish action；
3. 若是 manual publish，则 owner 需对该 row 触发一次显式 publish action；
4. 若是 auto-publish，则只有当前 Agent Space / agent 的对应 setting 已开启、且 **创建于当前 `effective_from` 之后**的 private-agent-authored rows，才会默认 materialize 到各自 parent；
5. 条件满足后，才执行 materialize。

换句话说，manual publish 是 **逐条 owner 决策**，auto-publish 是 **per-Agent-Space-level owner 决策**：channel-level 用 host channel policy，thread-level 用 thread Agent Space policy。

`effective_from` 还锁定 disable / re-enable 的时间语义：

- 每次从 off 切到 on，都产生一个新的 `effective_from` 窗口；
- policy 关闭期间产生的 private-agent-authored rows，不会因为之后再次打开 auto-publish 而 retroactive materialize；
- 若 owner 之后仍想把这些“关着时产生的 rows”发出去，只能逐条 manual publish；
- v1 不要求提供“如果现在重新打开会补发哪些 rows”的额外 preview / backfill 工具。

## 8. Copy + link semantics

### 8.0 Canonical durable tables introduced by O3

O3 在 O1 的 canonical set 之上只新增一张 durable support table：

- `scope_share_cards`：publish / auto-publish 的 durable linkage，回答“哪条 Agent Space row materialize 成了哪条 parent row”。

与此同时，O3 继续复用 O1 已声明的 `scope_message_sources` 作为 share-in / source provenance relation。也就是说：

- `scope_message_sources` 是 **O1-inherited**，继续只回答“这条 Agent Space row 的来源是什么”；
- `scope_share_cards` 是 **O3-declared**，只回答“这条 parent row 是由哪条 Agent Space row materialize 出来的”。

两者职责不同，不能复用。

### 8.1 Two rows, one publish event

一次 publish event 会涉及两条 transcript entry：

1. 现有的 Agent Space authoring row
2. 新创建的 parent published row

这两条 row 来自同一个 publish event，但各自属于不同 timeline。

同时，publish-out 的 durable linkage 不是 O1 `scope_message_sources` 的实例，而是独立的 **`scope_share_cards`** 关系域：`scope_message_sources` 继续只回答“这条 Agent Space row 的来源是什么”，`scope_share_cards` 则回答“这条 parent row 是由哪条 Agent Space row materialize 出来的”。

在 v1 中，同一条 Agent Space source row 指向同一个 target parent space 的 publish materialization **至多发生一次**。换句话说：

- 重复 manual publish；
- auto / manual 并发命中同一条 source row；
- 网络重试 / 双击 / 幂等重放

都必须收敛到同一个 `scope_share_cards` relation 与同一条 parent published row，而不是生成第二条 parent message。

### 8.2 Linking 后两边独立生活

publish 之后：

- parent row 上的 reply / reaction / edit / search 走 parent 语义；
- authoring row 周围继续发生的私有讨论 / 编辑仍留在 Agent Space；
- 两条 row 通过独立的 durable `scope_share_cards` relation 保持 traceability，但它们不是一条共享 interaction state 的“逻辑单行”；
- `scope_share_cards` 至少保留三项 core attribution：source Agent Space row ref、relation type = `publish_out`、`publish_mode ∈ {manual, auto}`；
- `scope_share_cards` 还必须带一个 durable publication identity（例如 publication id / publish_event_id 或等价键），使 manual/auto race、重试与撤回审计都能锚定到同一条发布关系；
- `publish_mode` 记录的是**首次成功 materialize** 的路径；后续命中的重复 publish 尝试只返回现有 relation / parent row，不会再改写这条 relation 的 core attribution；
- 该 relation 不复用 O1 `scope_message_sources`；后者继续只承担 `auto_flow/share` 的 source provenance。`scope_share_cards` 的 durability 则遵守与 O1 `§11 Source refs semantics` 同级的 hot→cold→tombstone provenance 解析精神，而不是依赖 hard-delete cascade semantics。

### 8.3 删除 / 编辑后的行为边界

copy+link 模型下，两条 row 在后续 life cycle 上独立演进：

- parent row 的 reply / reaction / edit / delete 不会回写 Agent Space authoring row；
- Agent Space row 的后续私有讨论 / 编辑也不会改写 parent published row；
- structured refs 负责说明“这两条 row 通过哪次 publish 相关”，而不是把它们强行绑定成一个共享状态对象。

### 8.4 为什么 copy+link 最优雅

这个模型同时保住：

- 私有 authoring history
- 普通 parent fan-out 语义
- 清晰的 trace / audit linkage
- private collaboration 与 public/shared delivery 之间的明确边界

## 9. Authorization model

### 9.1 Share-in authorization

只有 owner 可以授权 private Agent Space 的 share-in。

### 9.2 Publish-out authorization

只有 owner 可以授权 private Agent Space 向 parent 的 publish-out。

### 9.3 执行时的授权 / 生命周期复核

无论是 share-in、manual publish，还是 auto-publish materialization，在真正写入 row / relation 的 **fire-time** 都必须重新检查当前生命周期与权限条件，而不是只相信较早的 policy / UI 状态。

最少需要复核：

- owner 仍然能读取 source parent content（对 share-in）；
- owner 仍然能在 target parent 中发言（对 publish-out）；
- 对应 private agent entry 仍然 active；
- 当前 Agent Space 尚未 archived；
- target parent space（channel / thread）仍然存在、未归档、未变成 owner 当前不可访问的对象。

如果这些条件在 fire-time 不再成立：

- 该次 share / publish / auto-materialization 必须失败；
- 不得创建半成品 row、ghost relation 或补偿性 parent message；
- 过期的 auto-publish policy 也不能绕过当前 lifecycle / auth 真相。

### 9.4 Auto-publish 粒度

v1 把 auto-publish 的控制粒度拆成两层：

> channel-level：`owner × private agent × channel`
>
> thread-level：`agent_space × private agent`

也就是说：

- owner 在某个 `channel` 上为某个 private agent 保持 auto-publish 默认开启，除非之后显式关闭；
- owner 在某个 `thread` 上首次 materialize thread-level Agent Space 时，该 thread-level Agent Space 对相关 private agent 默认 auto-publish 开启，除非之后在该 thread 的 Agent Space notification panel 显式关闭；
- 每次开启或重新开启，都会为对应层级形成一个新的当前 `effective_from`；
- channel-level 与 thread-level 不互相继承开关，只共享同一个原则：只 publish 当前 `effective_from` 之后的新 private-agent-authored rows。

target 仍然只由 Agent Space 层级决定：

- channel-level Agent Space 的 row 仍然 materialize 到该 channel；
- thread-level Agent Space 的 row 仍然 materialize 到那个 thread 自己；
- thread-level auto-publish 不会越过 thread 再 materialize 成 parent channel message。

本篇 v1 不定义反向继承或 sibling 继承：

- 在某个 thread 上的局部行为，不会反向改写 parent channel 的 auto-publish；
- 一个 thread 的 publish 行为，也不会扩散到其它 sibling thread scopes。

### 9.5 Non-owner 边界保持不变

即使 private agent 已经有 publish authority：

- non-owner 仍然不能通过 parent mention 直接驱动它；
- non-owner 仍然不获得 private-input rights；
- publish authority 只是 owner-controlled output capability，不是对 private 边界的整体放开。

这里再明确一层容易混淆的边界：

> 本篇所说的 private collaboration，指的是 **ingress / egress authority 的私有边界**，不是“parent participant 永远看不到 Agent Space transcript”的保密边界。

也就是说：

- share / publish 的 owner-gated 语义，首先控制的是“什么内容会进入 Agent Space”与“什么内容会 materialize 到 parent timeline”；
- 至于 parent observer 是否仍可 browse 这段 Agent Space transcript，继续由 O1 / umbrella 中的 observer browse 规则定义；
- “不会出现在 parent timeline / 不会产生 parent delivery” 与 “observer 能否 browse Agent Space transcript” 必须分开理解，不能互相替代。

## 10. Relationship to O2

### 10.1 Share-in 不重写 delivery

share-in 只是创建一条新的 Agent Space row；随后这条 row 仍然按 O2 的普通 Agent Space delivery / wake 规则处理。

### 10.2 Publish-out 不重写 parent delivery

一旦 parent row 已经 materialize，它就按 O2 的 ordinary parent delivery / wake 规则走，不引入额外 special-case fan-out。

### 10.3 Attribution 不是 O2 的职责

如果 parent row 之后需要渲染出某种“from Agent Space” / “via private agent”之类的标记，那是 04-surface 要定义的 render 问题，不属于 O2。

## 11. Relationship to older owner-private delivery design

### 11.1 仍保持对齐的部分

旧的 `owner-private-agent-delivery.md` 锁定了 owner-authored quote-message delivery boundary。放到 unified Space + auto-publish 模型下：

- **share-in** 这一半仍然对齐：owner-authored quote/share object 依然是 canonical private ingress object；
- source refs 依然是 shared parent context 与 private collaboration 之间的 durable trace。

### 11.2 Supersede legacy owner-private delivery 的部分

该未上线 Agent Space publish 设计线当时 supersede `owner-private-agent-delivery.md` 中那些要求 **owner-authored quote message 必须是唯一 owner-private delivery/publish boundary** 的段落，具体包括：

- `§2 Product Goal` 中默认“final owner-authored quote message is the only delivery object”的部分；
- `§3.2 The real delivery object is an owner-authored quote message`；
- `§5.1 Minimal model` 中把 durable recipient facts 全都锁在 owner quote message 上的部分；
- `§6.2 Owner quote message drives delivery and wake` 中排除了 Agent Space-authored publish-out row 的部分；
- `§10 Acceptance Criteria` 中把 owner quote message 写成唯一 owner-private delivery / wake boundary 的那些 bullets。

`shared-spaces-and-agent-spaces.md` 中关于 `auto-publish` 粒度的高层口径，现已与本篇对齐：

- channel-level `auto-publish` 以 **owner × private agent × channel** 为粒度；
- thread-level `auto-publish` 以 **agent_space × private agent** 为粒度，默认开启但独立于 channel 开关；
- thread-level row 仍然只 materialize 到各自 thread，不越级升到 parent channel。

因此，后续实现与迁移不应再保留“thread 从 channel blanket policy 继承开关”的并行模型。

这些地方现在应遵守新的统一拆分：

- share-in -> owner-authored Agent Space quote row
- publish-out -> private-agent-authored Agent Space row 通过 copy+link materialize 成真实 parent row

### 11.3 Legacy compatibility authority

legacy owner-private hidden context-delivery 写入口已移除；新 Web ShareFlow / Agent Space publish path 按本篇与 O5 的实现分层执行，并通过 concrete `owner_quote` message / source refs 表达用户可见分享语义。

## 12. 当前 closure of richer share entry / shape

### 12.1 Packed / per-row richer share 的产品入口已收口

本篇现在进一步锁定：

- `share selected messages` 通过 **同一个 ShareFlow surface** 进入，不再拆成两个顶级入口；
- selection mode 中的 `Forward as` action group 负责选择 shape；
- `Combine` = packed richer share（1 row + N refs）；
- `Separate` = per-row richer share（N rows + 1 ref each）；
- v1 的默认 forward mode = `Combine`；
- share 提交之后不提供 pack ↔ explode 的 in-place 互转；若 owner 想改成另一种 shape，应重新发起一次新的 share action。

实现层如果需要 share-action abstraction / batch identity / internal helper，可以自行引入，但不得改变以上产品合同。

还补一条 action 级去重边界：

- **单次 share action 内** 必须 dedup，同一 source message 不得因为重复勾选而在同一 action 中重复进入结果；
- **跨 share action** 不做 dedup：owner 之后再次 share 同一 source message，会产生新的 share action 与新的 provenance / transcript row 结果。
