Files
Agent-System-Documents/docs/proposals/2026-10-13-fast-slow-u2-u3-plan.md
2026-10-19 09:24:07 +00:00

45 KiB
Raw Permalink Blame History

快慢通道 U2/U3 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking. 本计划建议在同一会话原生实施,最后独立 Review;若用户选择子代理方式,改用 superpowers:subagent-driven-development。实施批准依据:2026-10-13用户“确认,继续推进”;连续授权覆盖本计划U2/U3阶段5–10,不包含既有服务部署。任务复选框不是验证证据。

Goal: 让材料整理期间的真实活动和带来源的阶段发现更新同一张任务卡,刷新、并行与重启后仍能准确读取。

Architecture: Core 冻结可选 TaskSnapshot 与检查点候选契约;Backend 在现有 activity_cards.payload 内保存权威快照,并将卡、执行态投影和 card.* 事件原子提交。Runtime 只提交候选;Frontend 的现有 store 是唯一异步状态所有者,UI 继续使用当前渐进卡和 DocBridge。

Tech Stack: Go、既有 SQLite/PostgreSQL Store、agent-native 子代理循环、React/TypeScript、现有 UI/Collapsible、既有 SSE 与 Playwright。

Spec: 已批准快慢通道展示方案 r1.1,特别是 §8、§9、§13–15;界面基础以工作台设计 r0.2和界面实施计划 r1为准。

状态:部分收口:阶段5已批准;Task1–6与r0.6零付费机制已合入,Task7模型质量验收未通过(r0.4 A01 FAIL,r0.5/r0.6 A01 NOT_RUN),留待后续任务;未部署。版本 r0.1;日期 2026-10-13(北京时间);owner weichen;计划作者为本聊天 Codex。来源:用户“继续”并明确选择“推进快慢通道 U2/U3”。本文件补齐上一批明确排除的 U2/U3;不把上一批界面的持续授权自动移作本批授权。

Global Constraints

  • 沿用“左侧找上下文、中间推进对话、右侧处理工作”,不增设通道页面或状态微服务。
  • 默认展示真实状态、工作、最多三条发现、剩余/下一步;依据与执行记录按需展开,思维链不是成果。
  • “taskId 在排队前持久登记并关联同一张卡”;排队没有 runId 时不制造 run。
  • “重启对账不能把展示快照当作执行队列”;无可信执行所有者的排队记录必须中断,不自动重派。
  • “持久化与事件写入须原子一致”;快照与重放水位一致,不同会话/项目流的 seq 不可比较。
  • “不同内容不得复用旧任务成果”;内容 hash 与解析/处理配置进入输入身份,文件名和字数不是版本。
  • 当前批准前不改业务代码、不运行真实模型实验、不操作运行数据库或部署服务。实施批准后仅使用任务独立测试环境;既有实例、凭据、保护规则和正式发布仍不在本计划范围。
  • 颜色、圆角、字号、动效只消费既有 UI 令牌和 mk-*;折叠仍用 Collapsible。无新增视觉令牌需求。

Review Focus

  1. 同名、同字数、不同内容;更新解析器或截断配置后不得返回旧批材料结论。Task 2 给出固定红测。
  2. 卡已落盘但事件失败、并发终态与晚到阶段候选;不得出现数据库和可重放事件分裂或终态回退。Task 3 注入事务失败与竞争。
  3. 重启遗留无 runId 的排队卡、PG 中另一实例仍持有执行权;只中断确属本实例失去所有者的任务。Task 4 双实例及队列恢复用例。
  4. 用户收纳/置顶、窄屏阅读、快照期间后台更新和跨会话迟到响应;不得被工作更新覆盖偏好或移走焦点。Task 5 复用并扩展真实浏览器回归。
  5. 模型伪造 task/source/verified/动作、来源更新或访问失效、预算耗尽;不提升模型叙述的可信等级,不发布越权文本,不影响主任务收尾。Task 6、7 验证。

1. 当前固定基线及实际差距

对象 SHA
Dev R0 5212fef01e59b7f9e12fa7fc42cd987c6792a786
Core ba289e7b1780a29855aef8477bf1ac49bd563eb2
Backend 0f192a481388184df39f81c28b0587a7e16ec3ca
Frontend 43d1933b8c3377ae548eb495305e9f0de29dcb59
UI 0da2e3acd6c8ae8957825b64ddb54b250c0979c0
Documents 写作起点 cf74a200e8

来源 SELF:本轮读取固定源码与远端开放 PR;原始审计由 Dev 本任务卡的 make evidence 记录。以下是代码事实,不是新能力运行通过声明。

事实 精确入口 本批处理
U1 已有 TaskSummaryView 和固定骨架 KV;没有持久 TaskSnapshot Core task_summary.go、activity.go 添加可选结构化字段,保留既有 Body 回退
材料键仅取排序后的 filename + runeCount Backend internal/runmgr/material.go:44 内容与配置身份修正是 U2 前置
卡整块 JSON 已持久化,且有卡级 version/CAS internal/store/cards.go;card_version_test.go 使用现有 payload/version,不新增任务表
SaveCard 与 emitCard 分开;finishBackgroundCard 更新 Body 后另发状态 internal/cardsvc/cardsvc.go;internal/runmgr/runmgr.go 新投影事务一次写入正文、状态、tone、事件
AppendEvents 在事务中分配 seq,并在 PG 同事务 NOTIFY internal/store/messages.go:268;dialect.go 提取事务内事件助手;沿用现有锁与通知
bgQueue 在内存;ReconcileInterrupted 只扫描已有 run internal/runmgr/runmgr.go:95、2240 持久排队卡对账,禁止用展示记录恢复执行
子代理 text_delta 的前 80 字成为 agent.progress internal/runtime/sub_loop.go 不把该文本截断当业务发现;新增受控检查点
前端已隔离后台 notes,保存卡级观察和恢复保护;无业务 TaskSnapshot.revision Frontend src/store.ts、test/work-state.test.ts 扩展既有 store,保留既有锁/恢复/预览意图
新界面与阅读稳定性已交付 Dev PR493 / 归档 PR494;上述 FE/UI SHA 复用现有渐进卡、确认和窄屏,不再重复改版

远端核对未发现本方向的开放 U2/U3 PR。已存在审计导出、账户事务、SQLite 活动实例保护及其文档/组合 PR;本批不修改这些任务的记录。它们后续合入时依规 task-sync,并对受到影响的组合重新验证。

2. 本批决定与验收边界

推荐分期:U2 → U3,每期独立可验收;下面 Task 1–5 属 U2,Task 6–7 属 U3。不把 U1 和 U2 再合并成一个含糊出口。

  • 首期结构化任务只使用现有卡 JSON。Core 的可选字段是扩展兼容;旧卡无该字段仍使用 Body。首次不增加 schema migration、不做旧库回填;旧材料卡无法证明内容身份时只保留历史展示,不自动复用其整理结论。
  • 第一批真实业务成果覆盖附件材料整理。运行时检查点能力保持通用;研究、生成、审查的专门提示配方与业务校验作为后续消费者增量,不以本批材料样例宣称全部场景完成。
  • 摘要附加模型调用 0 次:当前子代理在自然检查点提交候选。每次 attempt 最多接受 6 次有实质变化的非终态成果发布;相同检查点重投不扣新次数。首条有效成果立即发布,后续按内容去重;本批不建设额外定时摘要模型或持久延迟候选队列。
  • 高频活动的展示更新上限 1 次/秒,终态、必要确认与重要更正即时;执行记录不因显示节流而丢失。
  • “已纳入输入/已提供片段”由服务端输入账本确认;不把它说成模型已完整分析。阶段发现分别标材料记载、分析判断、初步判断、待确认;“已核对”只能来自受控检查结果。
  • 支持读取、展开依据、打开既有文件和现有 HITL/命令能力。无新暂停、重试或独立取消按钮;后续新尝试保存新 attempt/卡,原失败历史保留。
需求 实施项 可判定出口
R03-b/R04 活动 1、3–5 真实工具/执行状态持续更新同一卡;无阶段成果时诚实占位
R03-c/R04/R05 成果 1、2、6、7 长材料至少两个有依据、有工作价值的真实检查点;短任务可直接收尾
R06 并行 3–5、7 3 个后台 + 前台 + 第四任务排队,任务与正文归属正确
R09 恢复 3–5、7 刷新/断线/乱序后不回退;排队失去执行者不永久“排队”
R12 阅读 5、7 不抢文件、焦点和滚动;原 FE/UI 阅读用例继续通过
R17 尝试与结果 1、3、4、6 attempt 与执行/展示/回投分开;旧执行者与终态后候选不能覆盖结果
R07 确认 5–7 复用既有两入口,后台进展不覆盖提交中/未知结果;服务端 CAS/命令收据证明无重复效果
R10 最终结果 3、6、7 结论、默认最多三点、范围/缺项、真实结果引用;有摘要不等于文件保存成功
R14 成本与失败 6、7 0 次额外摘要模型;限制发布次数,失败保留成果并允许主任务完成

3. 契约与文件职责

Task 1:冻结 Core 数据与旧客户端兼容(U2)

Files: 新增 Core task_snapshot.go、task_snapshot_test.go;修改 activity.go、data.go、ts/types.ts、task_summary.go;随后 Dev make contracts 同步 FE/UI 的 src/contracts.ts。

Interfaces: ActivityCard.Task *TaskSnapshot,JSON 名 task、omitempty。TaskSnapshot 是当前权威投影;检查点类型先随契约定义,U3 才注册执行入口。最低字段如下,子类型全部在本任务定义:

type DocumentOrigin struct {
    ArtifactID ID
    ContentHash, ParserRevision string
}
// DocMeta 增加 Origin *DocumentOrigin,json:"origin,omitempty";旧调用方可不提供。
type TaskPhase struct { ID, Label string; ObservedAt time.Time }
type TaskSourceRef struct {
    ID, ArtifactID, ContentHash, ParserRevision string
    BlockIndex int
    Label, Quote, Availability string
}
type TaskResultRef struct {
    ArtifactID, SurfaceID ID
    Version, ContentHash, Availability string
}
type TaskFinding struct {
    ID, Text, Impact, Kind, Status string
    Revision int64
    Sources []TaskSourceRef
    Assumptions string
    Corrects string
}
type TaskSnapshot struct {
    SchemaVersion int
    TaskID, AttemptID, CardID, ProjectID, ConversationID, SourceMessageID ID
    ParentRunID, RunID ID
    OwnerInstanceID string
    ExecutionEpoch int64
    Revision, SourceSeq int64
    StreamScope, InputRevision, Title, Goal string
    ExecutionState, OutcomeCompleteness, DeliveryState string
    Phase TaskPhase
    Focus, Coverage, Remaining, NextStep string
    Findings []TaskFinding
    Actions []UIAction
    ResultRefs []TaskResultRef
    LastActivityAt, LastMeaningfulUpdateAt time.Time
}
type TaskSourceQuote struct { SourceID string; BlockIndex int; Quote string }
type WorkCheckpointCandidate struct {
    CheckpointID, PhaseID, Focus, Remaining, NextStep string
    Findings []WorkFindingCandidate
}
type WorkFindingCandidate struct {
    FindingKey, Text, Impact, Kind, Assumptions, Corrects string
    Sources []TaskSourceQuote
}
func (s TaskSnapshot) Validate() error
func ValidateWorkCheckpoint(c WorkCheckpointCandidate) error
func (s TaskSnapshot) SummaryBody() []UINode

实现必须写显式 camelCase JSON tags,TS 采用同一字段名。候选没有 owner/taskId/runId、动作、URL、CardStatus 或“已核对”开关;归属、时间和 revision 由服务端盖章。ExecutionState 固定 queued/running/awaiting_user/completed/failed/interrupted;OutcomeCompleteness 固定 none/partial/complete;DeliveryState 固定 pending/available/reported。Phase.ID 允许稳定活动阶段及经注册检查点阶段,不由计时推进。

  • 先写字段缺失、未知状态、非安全整数 revision/seq、超过限制和旧卡 JSON 回退的红测。单快照总 JSON ≤64KiB;候选 ≤16KiB、单次 findings ≤6、当前有效发现最多12;默认只展示3项,不截掉风险/金额/日期而伪造完整性。超限拒绝候选,已有结果不变。
  • 在 Core 运行 go test ./... -run 'TaskSnapshot|WorkCheckpoint|TaskSummary' -count=1,确认测试因新契约缺失或不变量失败。
  • 按上述类型与枚举实现校验和 Body 纯投影。保留旧 KV 常量;执行状态映射状态词与 tone 一次产生。UI 收纳状态与 executionState 分离;dismissed 不代表已停止。
  • Go/TS 字段逐项核对后 make contracts;运行完整 Core 测试和 FE/UI 类型检查。提交只包含本单元的契约与兼容变更。

核心负例:

func TestTaskSnapshotRejectsMissingIdentity(t *testing.T) {
    s := TaskSnapshot{SchemaVersion: 1, Revision: 1, ExecutionState: "running"}
    if s.Validate() == nil { t.Fatal("missing scope/attempt must be rejected") }
}
func TestCheckpointCannotDeclareVerified(t *testing.T) {
    c := WorkCheckpointCandidate{CheckpointID: "cp1", PhaseID: "crosscheck", Focus: "核对材料",
      Findings: []WorkFindingCandidate{
        {FindingKey: "f1", Text: "日期不一致", Impact: "需要确定口径", Kind: "verified",
         Sources: []TaskSourceQuote{{SourceID: "s1", BlockIndex: 0, Quote: "上线六月"}}},
    }}
    err := ValidateWorkCheckpoint(c)
    if err == nil || !strings.Contains(err.Error(), "kind") {
        t.Fatal("expected kind rejection, not a missing unrelated field")
    }
}

Task 2:材料内容身份与可核对来源登记(U2 前置)

Files: 修改 Backend internal/runmgr/material.go、material_test.go、runmgr.go、internal/server/server.go 的材料接收装配;新增 internal/runmgr/material_sources.go、material_sources_test.go。不修改解析器实现或检索排名。

Interfaces: 保留 materialKey([]core.ParsedDocument) string 作为解析内容级键,改为确定性 hash 完整 IR、parser 和 warnings;排除 ParsedAt。新增私有 materialInputRevision(items []materialInputItem, processingRevision string) string 构成完整复用身份。materialInputItem 定义 ArtifactID core.ID、Parsed core.ParsedDocument、OriginalHash string;Server在接收并授权具体artifact_ref、解析其bytes时赋予 Task 1 的DocMeta.Origin,Manager成对记录到runCtx并传给后台binding,禁止按文件名猜匹配。保留现有StartConversationRun/StartClaimedConversationRun方法签名,新Origin是可选扩展。不能假设当前DocMeta/Artifact已存在hash/version字段,也不信任客户端或模型自填Origin;Manager核对实际artifact归属与版本。没有原始hash时使用完整解析内容摘要并标识fallback。配置摘要包含实际解析版本及材料内联/截断设置;集合排序保留重复文件,不能仅按文件名覆盖。

内容级键排除Origin.ArtifactID等传输标识及ParsedAt,避免同内容重新上传仅因新ID重做。processingRevision还包含实际材料指令/子代理配方内容版本,不能在提示配方已改后复用旧解释。重新上传复用前仍校验旧结果来源可用与权限,没有可取回依据时创建新attempt。

material_sources.go 定义 materialSourceRegistry:由当前消息 artifact_ref 和可信 RunCtx.Artifact 建立 sourceId→工件/内容hash/解析版本/实际 DocBlock 的映射;记录已提供全文或片段范围。不得用待发布模型文本构造该账本。

  • 在现有 material_test.go 写同名等长不同内容、顺序交换、ParsedAt变化、重复同名文件、parser/配置变化和图片/空解析的红测。
  • 运行 go test ./internal/runmgr -run 'MaterialKey|MaterialSource|MaterialInput' -count=1;同名等长变体必须先失败。
  • 替换内容身份并记录完整键到受控 TaskSnapshot.InputRevision。新去重只复用同 owner/会话、相同输入/配置、当前来源仍可访问且已完成或实际有执行者的记录。旧12字符 Body KV 不能作为新材料已整理的证明。
  • 从真实 ArtifactContent 及 DocBlock 建立来源 registry;无页码就用块/章节片段,不生成页码。新尝试保存新 attempt/卡,旧失败/撤回结果保留。
  • 跑相关材料完整测试并提交该前置。普通无附件对话不建任务,也不改变已有材料派发时机。
func TestMaterialKeySameNameSameLengthDifferentText(t *testing.T) {
    a := []core.ParsedDocument{{Meta: core.DocMeta{Filename: "决定.txt", Parser: "builtin:md"},
        Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "上线六月"}}}}
    b := []core.ParsedDocument{{Meta: a[0].Meta,
        Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "上线七月"}}}}
    if materialKey(a) == materialKey(b) { t.Fatal("different content reused an old result") }
}

Task 3:卡快照、状态、事件与一致水位原子提交(U2)

Files: 新增 Backend internal/store/task_projection.go、task_projection_test.go、internal/cardsvc/task_projection.go、task_projection_test.go;修改 internal/store/messages.go、cards.go、internal/runmgr/events.go、internal/cardsvc/cardsvc.go、internal/server/server.go。

Interfaces: Store 是唯一持久写入 owner。新增以下输入/输出与方法;不把 sql.Tx/DB 句柄交给 Runtime、UI 或插件:

// store 包;Card 与 Task 必须一致,Channels 由宿主策略生成。
type TaskProjectionWrite struct {
    Owner string
    Card core.ActivityCard
    Ref string
    ExpectedCardVersion int
    ExpectedTaskRevision int64
    Channels []string
}
type CardsSnapshot struct { Cards []core.ActivityCard; ReplaySeq int64 }
func (s *Store) CommitTaskProjection(w TaskProjectionWrite) ([]core.StreamEvent, error)
func (s *Store) CardsAtCursor(owner, conversationID string) (CardsSnapshot, error)

cardsvc 新增 CommitTask(owner string, next core.TaskSnapshot, expectedRevision int64) error,内部读取当前卡及卡 version、保留用户 pin/dismiss 状态,派生 Body/状态/tone;提交时再次校验 owner、归属、attempt、合法迁移和两种版本。读写冲突明确返回 ErrConflict;不得盲写覆盖。

  • 在 Store 复用 openTestStore 与 newVersionedCard 写事件插入失败回滚、owner不同、CAS竞争、终态后晚到更新、多流seq独立和快照水位测试。SQLite 用测试 trigger 让 INSERT events 失败;PG 使用本测试独立 schema 的同等触发器,不能修改运行库。
  • 运行 go test ./internal/store ./internal/cardsvc -run 'TaskProjection|CardsAtCursor' -count=1,先确认故障条件能暴露旧双写问题。
  • 提取现有 AppendEvents 的事务内助手,供旧入口和新事务复用。按排序后的 channel 获取现有会话咨询锁,卡 CAS、完整 JSON、冗余状态列、会话/允许的项目 card.updated、PG NOTIFY 同事务;失败全部回滚。唯一性/CAS不能只由 mutex 或“先查后写”保证。
  • eventBuffer 增加受控投影提交方法:在现有 flushing 串行边界内取出相关流缓冲、按同一事务先后保存并发布已提交事件;禁止持 flushing 再调用会重取该锁的 flushConversation。新路径不能另走 emitCard 二次分配 seq。只发布成功提交的对象,内存缓存提交失败时不前进。
  • 列表保持既有 {cards: [...]},新增可选 replaySeq;事务中读取一致的卡与会话水位,PG 使用一致快照。旧消费者忽略新增字段。项目列表水位独立,不与会话水位比较;Task.SourceSeq只表示其会话流。
  • 卡收纳/置顶、动作裁决、复制到项目等触及带 Task 的写入口要保持 Task 或拒绝非法覆盖。to_project 是成果引用投影,不创建第二个工作执行 owner;本批优先保留原任务引用,不自动复制可更新执行状态。
  • SQLite/PG 原子、并发和故障测试通过后提交。本批不增加任务表或迁移;若证据表明现有 schema 无法实现不变量,先修订计划,不临时加入迁移。

故障注入骨架(store 包,newVersionedCard 是现有测试 helper):

st := openTestStore(t)
c, owner := newVersionedCard(t)
p, err := st.CreateProject(owner, "task projection")
if err != nil { t.Fatal(err) }
conv, err := st.CreateConversation(owner, p.ID, "task projection", "")
if err != nil { t.Fatal(err) }
c.ProjectID, c.ConversationID = core.ID(p.ID), core.ID(conv.ID)
c.Task = &core.TaskSnapshot{
    SchemaVersion: 1, TaskID: "t1", AttemptID: "a1", CardID: c.ID,
    ProjectID: c.ProjectID, ConversationID: c.ConversationID,
    SourceMessageID: "m1", ParentRunID: "parent1", RunID: "run1", Revision: 1,
    OwnerInstanceID: "test-instance", ExecutionEpoch: 1,
    StreamScope: "conversation:" + conv.ID,
    InputRevision: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    Title: "测试任务", Goal: "读取测试材料", Phase: core.TaskPhase{ID: "reading", Label: "读取材料"},
    ExecutionState: "running", OutcomeCompleteness: "none", DeliveryState: "pending",
    LastActivityAt: c.CreatedAt,
}
c.Body = c.Task.SummaryBody()
if err := st.SaveCard(owner, &c, ""); err != nil { t.Fatal(err) }
// SQLite case;PG case 在独立 schema 使用拒绝 INSERT 的函数/trigger。
_, err = st.db.Exec(`CREATE TRIGGER reject_task_event BEFORE INSERT ON events
BEGIN SELECT RAISE(ABORT, 'task event injection'); END`)
if err != nil { t.Fatal(err) }
next := c
task := *c.Task
task.Revision = 2
task.Focus = "核对材料"
next.Task = &task
next.Body = task.SummaryBody()
_, err = st.CommitTaskProjection(TaskProjectionWrite{
    Owner: owner, Card: next, ExpectedCardVersion: 1,
    ExpectedTaskRevision: 1, Channels: []string{conv.ID},
})
if err == nil { t.Fatal("injected event failure must roll back") }
if !strings.Contains(err.Error(), "task event injection") { t.Fatal("wrong failure trigger") }
got, ver, err := st.GetCardVersion(owner, c.ID)
if err != nil || ver != 1 || got.Task.Revision != 1 { t.Fatal("card changed without event") }
events, err := st.EventsAfter(conv.ID, 0, 100)
if err != nil || len(events) != 0 { t.Fatal("rolled-back event leaked") }

Task 4:实际执行生命周期及排队重启对账(U2)

Files: 修改 Backend internal/runmgr/runmgr.go、material.go、recovery_test.go;新增 internal/runmgr/task_projection.go、task_projection_test.go。复用现有 run/instance/lease,不新建调度器。

Interfaces: task_projection.go 定义私有 taskBinding(taskId、attemptId、cardId、owner、项目/会话、parentRunId、instanceID、已确认执行epoch);新增 registerBackgroundTask、updateTaskActivity、finishTaskProjection、reconcileTaskProjections,只由 Manager 调用。其持久更新统一走 Task 3 的 cardsvc.CommitTask。

func (m *Manager) registerBackgroundTask(parent core.Run, opts bgSpawnOpts) (taskBinding, core.ActivityCard, error)
func (m *Manager) updateTaskActivity(binding taskBinding, phase core.TaskPhase) error
func (m *Manager) finishTaskProjection(binding taskBinding, payload core.AgentResultPayload, runErr error) error
func (m *Manager) reconcileTaskProjections() error
func shouldInterruptQueued(ownerInstanceID, currentInstanceID string, hasLiveExecutionWitness bool) bool
  • 写四任务槽位、排队晋升保留ID、终态正文/灯一次出现、无run排队重启、PG他实例活租约、失去租约旧写和用户已收纳卡的测试。
  • go test ./internal/runmgr -run 'TaskProjection|TaskQueue|TaskReconcile' -count=1 先取得失败证据。
  • 入队前创建 TaskSnapshot 和卡并持久登记;成功持久后才可说“已登记”。后台实际启动才绑定 runId。工具开始/结束只生成确定性活动,绝不从 progress.Note 截断文本提炼发现。
  • 阶段状态、最终摘要、真实工件引用和旧卡 Body 一次提交。completed/failed/interrupted 不接受晚到运行中写;新 attempt 必须有新记录,不擦除旧失败。执行完成与 notice 已汇报分别更新 DeliveryState。
  • 启动对账在监听前运行:先沿用既有 ReconcileInterrupted,再对属本实例的 Task 做账。没有 runId/有效持久入队及执行权的 queued→interrupted,文案“排队任务未恢复,未开始执行”,解除相应材料去重。PG不得扫描并封存其他活实例任务;实例/执行epoch必须有可信关联记录,不能以“找不到本地goroutine”推断全局失败。
  • OwnerInstanceID/ExecutionEpoch 是服务端持久绑定,不由模型设置;读取执行凭据失败必须停止对账并报告,不能当作“没有执行者”。旧卡没有可信Task/内容身份时仅作历史并标明执行状态未核实,不保持虚假的排队承诺,也不擅自认定失败或重派。
  • 将持久通知ID和相同 task/attempt 关联,重投同一结果不会再派一次材料任务。回投失败不撤销已可读结果。跑恢复/材料/通知既有回归后提交。
func TestQueuedReconcileRequiresOwnershipAndLostExecution(t *testing.T) {
    if shouldInterruptQueued("B", "A", true) { t.Fatal("foreign live instance was sealed") }
    if shouldInterruptQueued("A", "A", true) { t.Fatal("live execution witness was ignored") }
    if !shouldInterruptQueued("A", "A", false) { t.Fatal("orphan owned queue remained queued") }
}

以上是归因策略的最小红测;出口还必须用现有双档 openRecoveryStore/newRecoveryManager 建立实际两实例、真实租约和持久队列卡重跑,单纯布尔策略测试不能作为恢复能力证据。

Task 5:单一前端投影、来源入口与阅读恢复(U2)

Files: 修改 Frontend src/api.ts、src/store.ts、src/components/ConversationWork.tsx、test/work-state.test.ts、e2e/work-companion.playwright.ts;UI src/activitySummary.ts、src/ProgressCardBody.tsx、src/progressive-cards.test.tsx。契约同步归 Task 1,不另手写分叉。

Interfaces: 新 api.listCardsSnapshot 读取 Task 3 的 {cards,replaySeq};只在现有 store 合并 TaskSnapshot。新增纯 mergeTaskSnapshot<T extends {taskId: string; attemptId: string; revision: number}>(current: T, incoming: T): T:归属相同且新 revision 才替换;未知 schemaVersion 回退 Body,不猜枚举。函数只返回给现有store,不拥有异步状态。UI 摘要从同一个 card.task 派生;旧卡仍可读,但无可信运行绑定的历史队列显示“当前执行状态未核实”,不能伪装正在推进。

  • 写会话/项目独立seq、重复/乱序revision、快照期间到达新事件、游标缺号、切会话迟到回复和任务终态后的旧更新用例。
  • 运行 npx vitest run test/work-state.test.ts(Frontend);UI 运行 npx vitest run src/progressive-cards.test.tsx,确认新字段/恢复触发尚不能通过。
  • 扩展现有卡观察与epoch守护:查询期间缓冲新事件,按快照水位重放;不可仅Math.max推进游标后丢失缺号事件。确认状态仍归现有cardActionStates,unknown不能因阶段更新被清除;不再创建第二套pending map。
  • 结构化 findings 默认3条,展开保留当前有效全部项和修正历史。来源只使用受控 DocBridge;UI普通层不显示inputRevision、模型参数或工具脚本。Body回退与结构化字段不能各自更新成不同结果。
  • 必要确认、失败原因和关键限制保持默认可见,不占普通发现“三条”的截断预算。来源的处理版本与当前文件不同,在唯一预览中给明确版本提示;无法读取处理时版本时提供保存的合法引用片段并说明限制,不能把最新文件伪装成旧版本。
  • 在既有五宽度/大字号、窄屏水合导航、原生DOM移动滚动/焦点与文件预览用例上重跑;新事件不打开 Surface、不更换文件、不夺取焦点。
  • 来源更新/访问失效时显示可读限制并请求权威对账;会话/身份切换清理相关缓存。类型、knip、design-guard及相关浏览器检查通过后提交。
expect(mergeTaskSnapshot(
  { taskId: 't1', attemptId: 'a1', revision: 9 },
  { taskId: 't1', attemptId: 'a1', revision: 8 },
)).toMatchObject({ revision: 9 })
expect(mergeTaskSnapshot(
  { taskId: 't1', attemptId: 'a1', revision: 9 },
  { taskId: 't1', attemptId: 'a2', revision: 10 },
)).toMatchObject({ attemptId: 'a1', revision: 9 })

Task 6:可信检查点候选及首批材料成果(U3)

Files: 新增 Backend internal/runtime/work_checkpoint.go、work_checkpoint_test.go;修改 sub_loop.go、runner.go;新增 internal/runmgr/task_checkpoint.go、task_checkpoint_test.go;修改 material.go 的材料指令与来源描述;必要读取权限过滤放入 internal/cardsvc/task_visibility.go、internal/server/server.go,通过窄接口接现有授权策略,不导入RDM域规则。

Interfaces: Runtime 可选 WorkCheckpointSink,避免破坏所有 RunCtx/fake 的基础接口:

type WorkCheckpointSink interface {
    PublishWorkCheckpoint(core.WorkCheckpointCandidate) error
}

新增本地内置工具 publish_work_checkpoint,参数严格按 Task 1 候选结构,unknown JSON keys 拒绝。仅当前材料后台 run、支持 sink 且宿主/现有 ToolPolicy 允许时注册;不绕过 deny/NeedsConfirm,不覆盖同名插件工具。无 sink 的消费者仍走旧路径。实际 task/attempt/run、instance/epoch、检查点序号、来源账本从受控 runCtx 取得,模型不能选这些身份。

  • 在 Runtime fake stream 与 Manager 测试覆盖:两个有效检查点、原子发布后重读、伪造来源/跨项目/不存在片段、verified提权、重复checkpoint异载荷、终态后/旧attempt候选、次数耗尽、发布失败而主任务完成。
  • 分别运行 go test ./internal/runtime -run 'WorkCheckpoint' -count=1 与 go test ./internal/runmgr -run 'TaskCheckpoint' -count=1 先取得红测。
  • 模型调用工具后,校验引用确属本次提供材料或已授权真实工具结果,quote是指定块实际片段,版本/hash仍一致;检查点序号拒绝迟到回退。同checkpointId同载荷幂等返回,异载荷冲突。没有来源的关键事实拒绝,推断必须是analysis/provisional并带依据/限定;verified只能由受控确定性检查入口生成。
  • 源文档 quote 只证明片段真实存在,不证明模型解释必然正确。不得用JSON校验取代真实模型质量验收。
  • 候选findingKey只是当前attempt内的局部业务键;只由服务端生成全局Finding ID/修订状态,替换或撤回以前结论时保留正确历史关系。曾提示用户采取行动的重要更正立即幂等提醒;无动作的新发现仅改同一卡,不反复发聊天消息。
  • 撤回、更正、终态不消耗普通成果发布次数,不因次数用尽保留已知错误。历史保存以明确的finding/revision关系和既有持久事件为依据;超限拒绝新增大载荷,关键撤回仍可提交精炼状态,不能靠剪掉金额/日期满足限制。
  • 默认自然检查点目标为读入可用材料、发现实质差异/缺项、形成可用结论;不是所有任务必经动画。没有有用发现就诚实继续,短任务可直接完成。不消费thinking_delta作为发现。
  • 必要用户决定绑定已有HITL/命令记录;模型候选不能创建可执行命令。复用已交付两入口反馈,并重跑后端 CAS/命令收据的跨浏览器重复确认与未知结果对账场景。
  • needs_user发现本身不是HITL或执行暂停;没有真实待确认记录时,只显示问题/影响和现有输入入口,不造确认按钮,也不将executionState改成awaiting_user。
  • 首批0次额外摘要模型;有效非终态发布最多6次,重复不计,超过上限返回保留成果的说明,终态仍可提交。子代理预算通过现有 loopCaps/toolBudget 及 RunBudget 绑定,不能沿用无界maxTurns而声称预算有效。
  • 每次新读、重放与重新呈现缓存均重新校验任务和来源访问。拒绝时隐藏受限发现正文、来源名称及由其生成的Body,而不只是禁用链接;从当前账号撤权不意味着已合法送达用户的信息能够收回。不扩大会话卡项目镜像权限,沿用MirrorConvCards策略。
  • 引用绑定处理当时不可变的输入内容/hash。当前工件更新后旧版本仅进入有明确版本提示的历史,当前摘要/建议标明依据已更新并重新核验;不能把旧quote伪装成当前文件内容。权限过滤同时覆盖Focus、Coverage、Remaining、NextStep、结果摘要等由受限来源派生的文本。耗时的文件hash和模型调用在事务外进行,提交时校验可信版本/范围凭据;无法提供可证明版本一致性的来源不进入有效发现。
  • Runtime、Manager、源权限与工具策略回归通过后提交 U3 业务单元。

有效候选格式(示例数据只定义合同,不是验收证据):

{
  "checkpointId": "material-difference-1",
  "phaseId": "crosscheck",
  "focus": "核对需求与会议决定",
  "remaining": "评审意见仍在核查",
  "nextStep": "整理需要确认的口径",
  "findings": [{
    "findingKey": "launch-date",
    "text": "两份材料中的上线月份不一致",
    "impact": "安排交付前需要确定权威版本",
    "kind": "material_record",
    "sources": [
      {"sourceId": "src_a", "blockIndex": 0, "quote": "上线六月"},
      {"sourceId": "src_b", "blockIndex": 0, "quote": "上线七月"}
    ]
  }]
}

Task 7:真实模型、真实接口及固定组合验收(U2/U3)

Files: 新增 Backend eval/live/work_checkpoints_test.go,复用该目录现有 helpers_test.go/live_test.go 的凭据与模型入口;新增 Frontend e2e/work-checkpoints.playwright.ts;更新本计划关联的 Dev 任务卡和既有设计进展入口。真实材料、模型输出和评测记录存仓外。

Interfaces: live harness 固定 baseline/candidate 的 Dev与组件SHA、同材料、同任务、同模型/参数;采集actual task/attempt/run/card IDs、事件seq/revision、原始来源和Usage。浏览器通过真实HTTP取回记录,不能仅给DOM注入示例卡冒充后端链路。

当前live_test.go使用//go:build live和AGENT_EVAL_BASE/AGENT_EVAL_MATERIALS/AGENT_EVAL_OUT。新测试复用HTTP基础助手,但不复用其自动选第一个HITL选项的策略;必要决定保留或使用明确的测试专属答复,不能无条件批准业务动作。

测试启动器提供WORK_CHECKPOINT_EVAL_OWNERSHIP_MANIFEST,内容只含本任务PID、二进制/组件SHA、地址和独立data目录;不含凭据/DSN。新测试先核验该进程和隔离目录,再允许HTTP写入;只有localhost地址不能证明是本任务实例。缺少/不匹配该清单即失败,不使用旧harness的默认实例作为兜底。

  • 先完成所有机制红绿与固定组合 make test && make build;PG执行 make test-backend-pg,缺环境不能把SKIP记录为通过。真实评测前先验证预算与当前chat路由,服务与数据目录均为任务独立临时实例。
  • 初批真实模型对照3种材料整理任务:A01三长附件无文字、A02带明确分析重点、A04部分解析缺失;baseline/candidate各一次,共最多6个素材任务(每个含其前台即时答复和后台run),不自动重跑失败样本。A06同名等长更新另由真实HTTP/机制用例覆盖,无需为哈希问题额外消费模型。
  • 每个run建议MaxTurns=8、MaxToolCalls=12、MaxTokens=20000、300秒;每个素材任务最多1个前台和1个后台,本批最多6个素材任务/12个run、0次附加摘要模型。必须验证主/子run均受预算约束,统计逐调用实际token和可得费用;按已返回Usage对账的全批tokens目标上限240000。请求内可能发生的预算超额须由单次输出上限和停止规则约束,未回报Usage的重试不能冒充已计费上界。当前provider无法给出可核费用/限额时,真实实验标NOT_RUN并先确认上限,不修改密钥或擅自更换路由。
  • A01长任务至少两个在结束前落盘的真实成果点;对照已知材料独立检查每条结论、影响和引用,记录错误/遗漏/更正。实际生成但没有有用成果不算通过;不使用模型自评作为独立质量证据。短任务直接完成合法,不延长任务凑次数。
  • 真实接口/浏览器覆盖3后台+前台+第4排队、刷新、断线补号、会话切换、终态乱序、来源更新/移除/拒绝、双入口确认、无run排队重启以及PG活实例不被错封。记录Actor、被测版本及受信来源等级,不能将SQLite单机结果写为多实例证明。
  • 比较材料整理、主要发现、需要处理与来源定位的可读性;10秒目标作为待测指标记录实际时间/错误,不以作者直觉宣称达标。精炼度首先看风险和限定是否保留。
  • 验收、请求审核、合入前按project门禁task-sync,固定新候选,原始命令由make evidence记录。最终独立审阅者先重放最小检查,再读作者证据;组件提交精确SHA及Dev组合当前HEAD全部CI通过后才可按依赖合入。

建议运行入口(实验门控变量及现有 harness 行为应在本任务开工时核对):

# 机制:在Backend根目录,真实实验不由这些命令自动启动
go test ./internal/store ./internal/cardsvc ./internal/runmgr ./internal/runtime -count=1
# 组合:在Dev任务工作区
CMD='make test && make build' make evidence
# 真实模型:只在本计划和评测预算批准、两实例均为任务独立环境后运行
: "${WORK_CHECKPOINT_EVAL_BASE:?必须指定本任务独立实例地址}"
: "${WORK_CHECKPOINT_EVAL_MATERIALS:?必须指定仓外材料目录}"
: "${WORK_CHECKPOINT_EVAL_OUT:?必须指定仓外原始记录路径}"
: "${WORK_CHECKPOINT_EVAL_OWNERSHIP_MANIFEST:?必须指定本任务进程与数据归属清单}"
AGENT_EVAL_BASE="$WORK_CHECKPOINT_EVAL_BASE" \
AGENT_EVAL_MATERIALS="$WORK_CHECKPOINT_EVAL_MATERIALS" \
AGENT_EVAL_OUT="$WORK_CHECKPOINT_EVAL_OUT" \
go test ./eval/live/ -tags live -run TestWorkCheckpoints -count=1 -v -timeout 35m
# 浏览器:使用本任务的现有Playwright运行器及独立HTTP实例配置
# 新测试文件名固定为e2e/work-checkpoints.playwright.ts,原展示/导航/附件用例继续运行。

4. 交付、停止条件与当前状态

每个业务单元沿用模块各自任务分支与固定组合版本;Core契约先行,Backend和消费方在同一Dev候选组合验证。U2达到出口后才进入U3真实成果发布。先保留确实需要的图形/日志在展开层,不另造示例业务状态。

触发以下任一情形,停止受影响增量并保留现场:来源越权;状态/结果串任务;原子性破裂;终态或修正历史被旧写覆盖;无有效执行权的任务被再次派发;预算无法证明有效;真实模型连续生成无依据成果。试验失败如实记FAIL/NOT_RUN,不降低“真实且有用”的出口。

项目 本次状态
当前实现与在途核对 SELF,基线/原始审计见Dev任务卡
既有r1.1方向、阶段1–4 原批准继续有效;本计划新增具体接口、约束和验证需阶段5评审
U2/U3范围来源 2026-10-13本聊天用户明确选择推进U2/U3
本实施计划 r0.1,2026-10-13用户“确认,继续推进”批准,阶段5–10连续授权
Task1–6 本地实施与独立代码复审已完成;原子持久化、来源绑定、队列/恢复、渐进展示与预算守卫有SELF/REPLAY依据
Task7真实模型 原六样本、第一次额外A01及第6节另行批准的v7后续A01均已执行;后续A01两付费run、五次provider调用、57997 reported tokens;两次running态检查点确已落盘,但无后台最终结果且有无依据的“已批准决定”断言
当前出口 U3质量验收仍FAIL;第6节一次性授权已用完,不自动重跑
平台CI/合入/既有实例部署 Dev旧HEAD 7692503的三项平台CI已通过;新组合待验,质量门禁未通过故未合入;部署不在本批授权内

计划评审入口只维护本文件;Dev任务卡维护当前动作、授权、原始证据及交付进展。用户已批准并按本文件的U2→U3顺序实施;下一阶段建议不自动启动U4或既有实例部署。

5. 实施核验与剩余出口(2026-10-13)

实际进展及证据边界见本批核验报告。原计划Task1–6源码变更已完成,Task7保留失败而非将引用存在视为语义正确。原六样本不因后续修复自动改为PASS。

等价整改:材料记录由宿主以经过核验的原文和文件名展示,省略模型对该类型的转述、影响和前提;分析/推断保持独立类别及前提,不提升为verified。正文与执行过程分开;失败/中断的下一步不得承诺仍在自动处理。输入配方版本已递增,旧成果不因文件名相同而复用。评测仅收紧输出上限,不能放大生产预算。

第6节的一次后续A01质量复验已另行获批、执行并失败。下一步先分析无依据的批准状态断言、第二检查点紧贴预算失败与缺失最终结果,再评审修订方案及资源边界;本次授权不延伸为再次付费试验。U3质量出口未通过,不进入合入。U4和既有实例部署保持未启动。

6. 质量出口后续试验(另行批准一次并执行;结果FAIL)

触发和证据。 已批准的额外A01实际使用两个run、四次provider调用、42203 returned Usage token,零接受的阶段发现。模型先提交含未知字段和错误嵌套的五条发现,随后尝试用省略号拼接来源引文;严格校验正确拒绝,两轮后台调用达到原每run 20000 token阈值,任务失败。独立逐项分析与原始收据见核验报告及额外A01评估。不能把错误引用当有效证据,也不能重用旧失败run补写成果。

已准备的等价整改。 模型侧检查点声明最多引导1–3条新增发现,要求单段落连续的短原文引用;宿主仍只接受可信来源、完整原文和当前版本,不更改Core最多6项的受控上界或来源权限。材料处理配方递增至v7,避免把旧结果复用为新行为。SQLite完整后端套件通过;后续真实模型结果见下文。

已批准的资源边界。 用户2026-10-13另行明确批准同一A01合成材料的一次受控试验:两个付费run为上限,每run MaxTurns=8、MaxToolCalls=12、MaxTokens从20000调为30000、最长300秒;已返回Usage的本次合计目标不超过60000 token,单次输出上限4000并继续保留生产更低的剩余额度,缺Usage立即停止后续调用。不换模型/路由、不使用重试或回退、不自动确认HITL。该目标不是严格账单上界,一次在途请求可能越过剩余额度。

验收和停止。 仍要求至少两个真正有用、结束前保存的不同成果点;逐条检查数字、意见/决定、假设、引文与文件归属,明确无最终结果或因预算中断。模型JSON错误、非原文引用、未知Usage、超出两run或额度目标时停止,保留现场且不重跑。该一次性授权已经执行,不覆盖任何后续付费重跑;CI通过也不放行合入。

实际结果与读数修正。 Backend 7f8674f/Dev c1c1942 的隔离样本确有两个付费run、五次provider调用、57997 returned Usage token;通知run零调用。检查点1含3项发现,检查点2累计6项发现、10处引用均可匹配原文;两次检查点均以running态原子落盘。第二次仅比失败终态早约1毫秒,250毫秒轮询客户端漏读并误报“少于两个落盘成果”;独立数据库事件重放纠正该读数,不追溯改写原评测exit1。后台run因每run 30000 token阈值失败,没有最终结果;首批已发布内容把材料中的预算/范围错误称为“已批准决定”,缺乏依据。因此有两次持久化并不等于两个可用成果,U3质量出口仍FAIL。原件和独立复审见核验报告;没有再次付费试验授权。

执行停止边界偏差。 原计划上段明确要求“非原文引用时停止”。本次后台第2次候选因非原文引文被拒绝后,同一个子run又发起第3次付费provider请求,返回14231 Usage token并修正引用;它没有新开run或执行传输重试,但仍违反该停止条件。57997低于本次60000目标也不能抵消这一偏差。评测入口已增加“检查点工具拒绝后阻止任何后续provider请求”的零付费RED→GREEN门禁;原样本、原exit1和偏差照实保留,不因代码修复追溯改为合规或质量通过。

7. U3 质量修订 r0.2(书面方案已批准,实施计划待评审)

用户已在本会话确认“来源一次呈现、批准状态不由模型推断、为最终答复留预算”的修订方向。可审阅的质量修订设计 r0.2另行固定三个职责、备选取舍、旧失败证据、零付费门禁与真实模型出口;本文 r0.1 已实施的 U2 契约、持久化、权限和三栏界面不因方向确认而重写。r0.2 书面方案已获批准;实施计划 r0.2仍待阶段 5 人工评审,新的付费样本尚未授权。