Files
Agent-System-Documents/docs/proposals/2026-09-30-card-stream-artifact-unify-project-inbox.md
weichen 4a69886952 Mark card stream / artifact / project inbox proposal approved (D49 confirmed)
Why: user approved the proposal 2026-09-30 and directed implementation.
Evidence: approval recorded in status line and D49 note.
Impact: docs-only; unlocks CF-S task start.
2026-09-30 17:44:57 +00:00

10 KiB
Raw Permalink Blame History

卡片流远期三项(内容流式 / artifact 统一 / 跨会话收件箱)—— 设计与实施提案

状态:已批准(2026-09-30 用户批准;D49 聚合口径按提案建议拍板:项目视图=项目级卡∪各会话未决卡,done 不聚合);owner:weichen。 前置:卡流机制与中栏过程/回复分离(D39–D43 已实施)、界面交互底座(D25–D33 收件箱/卡片/互定位)。 输入:用户 2026-09-30 指示——将 cardflow-r1 遗留的三项远期(卡片内容流式、artifact 统一、跨会话收件箱 CF3)推进实施,先出方案与计划。


1. 背景与现状盘点

cardflow-r1(PR #90)落地了显式路由(submit_card)、卡片权威载体(ctxasm 回填)与锚点互定位,同时把四项留作远期。本提案承接其中三项(飞书/微信投影仍留远期——通道依赖外部,单独立卡)。2026-09-30 代码盘点(core cafd9a5 / backend 076abfe / frontend 6752b78 / ui efd87f9 之后,R0=d64742c):

项 现有地基 缺口
内容流式 cardsvc.Update 已支持整卡变更 + card.updated 事件(progress 卡流式区在用);前端 store 已消费 card.updated → upsertCard submit_card 一次性出卡(llm.go 单次 JSON 参数 → CreateCard(done)),无流式产卡协议
artifact 统一 工具大结果已走 blobstore Artifact + PartArtifactRef + REST/查看器 + artifact.created;UINode 已有 doc-ref 词汇(UIRenderer 已渲染) 双轨不互通:card 部件 ctxasm 回填全文(12000 字上限),artifact_ref 部件只渲染一行 [产出工件 name]——模型下一轮对工具大结果失忆;前端两套呈现入口(收件箱卡 vs 工件列表)
跨会话收件箱 core.ActivityCard.ConversationID 注释已预留「空 = 项目级卡片」;activity_cards 表已有 project_id 列 列表/事件/前端三层全部按会话:ListCards WHERE conversation_id=?;SSE 严格按 conversationId 订阅(/api/events 必填参数);前端 loadCards(convId) 切会话即清空

2. 决策(D44 起)

# 决策 理由
D44 流式产卡三段式:内置工具 card_begin(title, summary?) / card_append(text) / card_commit()(同 update_todos 先例不经注册表,仅 ui.cardflow=true 时声明);submit_card 保留用于一次性短产物 与显式路由裁定同构(拆分发生在生成侧,可断言可审计);不依赖 LLM 网关流式工具参数能力;失败模式安全(不 begin = 全文留中栏)
D45 流式卡生命周期:begin 建卡 kind=result, status=active(收件箱可见、不计 pendingCount,与现状 active 语义一致);append 增量并入 body 尾部 markdown 节点,节流合并发布 card.updated(默认 250ms 窗口);commit 置 done + 消息追加 Part.card 引用部件 + finishRun 锚点回填(复用 submit_card 登记路径);run 终止(含失败/取消)仍有未 commit 的流式卡 → 兜底置 done(成功)或 failed(失败 run),tone 标注「未正常收尾」 不发明新状态机;断连/幻觉的恢复语义显式;pendingCount 不受流式过程影响
D46 artifact 上下文回填对称:ctxasm 对 PartArtifactRef 增加 ArtifactResolver 回填正文(默认与卡同 12000 字上限,超限记 Truncations;无 resolver 留现占位行) 先修「下一轮失忆」缺口——独立有价值、无 UI 依赖、可与 CF-S 并行
D47 artifact 门面卡(统一呈现层):工具结果 Truncated=true 时 runner 自动产 kind=result 门面卡:body=[markdown(工具摘要), kv(来源/工件名), doc-ref(工件)],status=done,消息同时追加 Part.card 引用与既有 PartArtifactRef(兼容+审计);PartArtifactRef 前端渲染并入锚点样式(展开显 doc-ref、跳工件查看器) 判据复用既有 Truncated 信号,不新造阈值;artifact 保持字节权威(可编辑、REST 查看器),卡是门面+锚——不在卡内复制字节;未截断的小结果不建卡(防收件箱噪音)
D48 项目级卡数据语义:ConversationID 空 = 项目级卡(core 既有注释转正);来源三个——① 项目级后台作业 producer;② 会话卡新增「留档到项目」动作(to_project,走命令总线,幂等);③ card_begin/submit_card 增加 scope=project 可选参数(默认 conversation);activity_cards 增加 project 维度索引 数据模型零迁移成本(列已在);生产端渐进开放
D49 项目收件箱聚合口径:项目视图 = 项目级卡 ∪ 该项目全部会话 action_required 未决卡;done 卡不聚合(留在原会话,防噪音);会话视图 = 该会话全部卡 ∪ 项目级卡(项目卡带「项目」徽标,动作跨会话直达) 跨会话的真正诉求是「待办不丢」;已完成产物按会话归档保持时间线清晰。已拍板(2026-09-30 用户批准本提案即确认)
D50 项目事件频道:/api/events 支持 scope=project&projectId=… 订阅(项目流独立 seq、持久化、断线重放——与会话流同 Stream 契约);项目级卡及跨会话聚合口径内的状态变更发布到项目流;会话内动作回执仍写会话流;StreamEvent 信封增加可选 ProjectID 字段(只增不删) 不向全部会话流广播(事件放大且 seq 语义混乱);项目流自成单调序,重放语义与会话流一致
D51 前端双视图:收件箱 tab 两级(会话/项目切换),会话 pendingCount = 会话未决 + 项目未决;项目视图复用 InboxList + 分组头(「项目级/其他会话待办」);切会话不清项目级卡缓存 store.cards 拆两层(projectCards / convCards);openInbox 定位逻辑按卡归属路由
D52 完备性剧本增补:零页面走查增加——流式卡双态(流式中/终态)、artifact 门面卡展开全文(doc-ref 打开查看器)、项目收件箱跨会话可见性(A 会话产待办 → B 会话/项目视图可见且可决) 沿 D43 惯例:「内容被藏起来」与「待办跨会话丢失」是本批最高风险

3. 实施切分(三任务卡,串行)

期 内容 仓 依赖
CF-S 内容流式(feature/card-stream-r1) D44/D45:backend 三段式工具 + cardsvc Append(节流)+ run 终止兜底 + style-cardflow outputContract 更新 + mock #cardflow 流式演示;frontend/ui 仅可选视觉(流式中 shimmer);documents 提案转正 + 指南 backend / documents(frontend、ui 可选) 无
CF-A artifact 统一(feature/artifact-unify-r1) D46/D47:backend ctxasm ArtifactResolver + golden 更新、runner Truncated→门面卡、mock #upload 演示门面;frontend artifact_ref 锚点样式统一;documents 指南 backend / frontend / documents 建议在 CF-S 合入后开工(llm.go/runner.go 文件面重叠,串行省冲突)
CF3 跨会话收件箱(feature/project-inbox-r1) D48–D51:core 信封 ProjectID + conformance;backend store 索引迁移/聚合查询、REST GET /api/projects/{id}/cards 与 to_project 动作、项目 SSE 频道与 emit 路由;frontend 双视图 + pendingCount;ui 徽标/分组;D52 走查 core / backend / frontend / ui / documents 体量最大,放最后;CF-A 的门面卡经项目视图获得跨会话可见性

跨仓顺序遵守 multi-repo 五步:core 先合入 → backend → frontend/ui → Dev gitlink PR → 组合验证。三卡各自走 task-start/task-sync/task-finish 全流程。

4. 契约与行为要点

  • card_begin 返回 cardId 供后续调用绑定(工具轮间状态由 runner 持有,同轮多次 begin 以最后一卡为活跃卡——outputContract 约束单 run 单活跃流式卡)。
  • card_append 文本追加语义为 markdown 段落拼接;卡 body 总长不设硬限(权威载体),仅 ctxasm 回填时按上限截断留痕(与 D40 一致)。
  • 幻觉调用拒绝沿用现状模式:ui.cardflow 关闭时三工具不声明,幻觉调用返回「未启用」提示不执行。
  • 门面卡判据只挂 Truncated=true(既有 4.10 截断信号);同轮若已有显式 submit_card/card_commit 产物则不再自动建门面卡(防双卡)。
  • to_project 动作语义:卡复制为项目级卡(新 ID,AnchorMessageID 指向原卡),原会话卡置 dismissed 留痕——不移动(会话时间线不可变)。
  • 项目流事件持久化复用事件表机制,scope 列区分;重放从 Last-Event-ID 起(与会话流同构)。

5. 边界与不做

  • 不动 ResolveSystem 签名与既有 SSE 会话流协议(项目频道是新增端点形态,非版本升级)。
  • 飞书/微信通道投影仍留远期(外部依赖,单独立卡)。
  • 不做项目级卡的细粒度权限(单用户体系内 owner 隔离已够;多用户项目共享收件箱随认证体系远期)。
  • card_append 不支持修改已提交内容(流式只追加;改稿 = 会话追问新卡)。
  • v1 不做「跨会话 done 卡聚合搜索」(产物检索归 RAG/工件库域)。

6. 验证计划

  • CF-S:backend 单测(Append 节流合并/幂等、三段式 happy path、幻觉拒绝、run 失败/取消兜底置态)+ e2e TestMockCardflowStreamLoop(begin→append×N→commit → 卡 done + cardId 部件 + 锚点回填 + card.updated 序列断言)+ ctxasm golden 不变;组合 make test。
  • CF-A:ctxasm golden 更新(artifact 回填 + Truncations 记录);e2e(#upload 大结果 → 门面卡 + doc-ref + artifact_ref 并存 + 下一轮上下文含工件正文截断);前端锚点渲染 vitest。
  • CF3:store 迁移测试(project 索引、聚合查询、双方言);项目 SSE 频道 e2e(订阅/断线重放/seq 单调);前端双视图 vitest + pendingCount;D52 零页面走查双态 9+N 用例;组合 make test + make build。