diff --git a/docs/proposals/2026-10-12-fast-slow-channel-display.md b/docs/proposals/2026-10-12-fast-slow-channel-display.md index c99b957..0d459fe 100644 --- a/docs/proposals/2026-10-12-fast-slow-channel-display.md +++ b/docs/proposals/2026-10-12-fast-slow-channel-display.md @@ -501,6 +501,8 @@ taskId 在排队前持久登记并关联同一张卡;runId 表示一次实际 ## 16. 评审与当前进展 +以下为本设计r1.1交付时的记录;后续实施进展见§16.1。 + | 项目 | 当前状态 | |---|---| | 当前源码核对 | SELF;固定对象与输出在任务卡;不等于运行验收 | @@ -515,3 +517,9 @@ taskId 在排队前持久登记并关联同一张卡;runId 表示一次实际 辅助审阅覆盖一致性、范围、可行性、设计、产品、对抗与安全七个视角(其中一致性/范围、设计/产品/对抗/安全分别由同一子代理分析,并非七名独立审阅者)。采纳并自行核对后修订五类问题:两次成果检查点只用于代表性长任务;新增要求的生效文案不误导;确认双入口补提交与未知结果状态;材料版本复用需实际内容身份;无 runId 排队任务重启时不能假装仍有执行者。 r1.1 相对 r1 的定稿修订:R03 切分为 a/b/c;U1 出口白名单与排除项写入 §14;首批锁定仅 U1。摘要次数/窗口与性能数字仍为候选参数。**设计批准不等于实施授权**;批准 U1 实施计划后另开实施任务,本设计任务不自动启动写代码。 + +### 16.1 U2/U3候选进展(2026-10-13) + +用户已批准[U2/U3具体实施计划r0.1](2026-10-13-fast-slow-u2-u3-plan.md)并要求连续推进。当前已实施TaskSnapshot/内容身份/原子事件、队列与重启对账、渐进任务/来源预览以及检查点和预算守卫,保留既有三栏、默认简洁及来源/执行详情展开。已有局部、组合、实际HTTP/browser及独立代码重放;具体固定对象和原始证据见[核验报告](../reports/2026-10-13-fast-slow-u2-u3-validation.md)。 + +U3真实模型质量验收尚未通过:原六样本包含只有一次阶段发布、预算语义误读和预算耗尽无最终结果。已据此收紧原文记录与终态摘要、修复评测上限代表性;原失败不改写。追加一次A01已获授权并执行,仍因来源引用格式与运行额度失败;当前尚未合入、未部署。优先完成该出口及后续精确HEAD CI/合入,U4与既有实例部署保持未启动,10秒用户指标仍未验证。 diff --git a/docs/proposals/2026-10-13-fast-slow-u2-u3-plan.md b/docs/proposals/2026-10-13-fast-slow-u2-u3-plan.md new file mode 100644 index 0000000..ead4a8c --- /dev/null +++ b/docs/proposals/2026-10-13-fast-slow-u2-u3-plan.md @@ -0,0 +1,462 @@ +# 快慢通道 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](2026-10-12-fast-slow-channel-display.md),特别是 §8、§9、§13–15;界面基础以[工作台设计 r0.2](2026-10-12-work-companion-ui-design.md)和[界面实施计划 r1](2026-10-12-work-companion-ui-plan.md)为准。 + +状态:**部分收口:阶段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 写作起点 | cf74a200e877d898d928052e6738681be2b670fc | + +来源 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 才注册执行入口。最低字段如下,子类型全部在本任务定义: + +```go +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 类型检查。提交只包含本单元的契约与兼容变更。 + +核心负例: + +```go +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/卡,旧失败/撤回结果保留。 +- [ ] 跑相关材料完整测试并提交该前置。普通无附件对话不建任务,也不改变已有材料派发时机。 + +```go +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 或插件: + +```go +// 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): + +```go +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。 + +```go +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 关联,重投同一结果不会再派一次材料任务。回投失败不撤销已可读结果。跑恢复/材料/通知既有回归后提交。 + +```go +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(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及相关浏览器检查通过后提交。 + +```ts +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 的基础接口: + +```go +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 业务单元。 + +有效候选格式(示例数据只定义合同,不是验收证据): + +```json +{ + "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 行为应在本任务开工时核对): + +```bash +# 机制:在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) + +实际进展及证据边界见[本批核验报告](../reports/2026-10-13-fast-slow-u2-u3-validation.md)。原计划Task1–6源码变更已完成,Task7保留失败而非将引用存在视为语义正确。原六样本不因后续修复自动改为PASS。 + +等价整改:材料记录由宿主以经过核验的原文和文件名展示,省略模型对该类型的转述、影响和前提;分析/推断保持独立类别及前提,不提升为verified。正文与执行过程分开;失败/中断的下一步不得承诺仍在自动处理。输入配方版本已递增,旧成果不因文件名相同而复用。评测仅收紧输出上限,不能放大生产预算。 + +第6节的一次后续A01质量复验已另行获批、执行并失败。下一步先分析无依据的批准状态断言、第二检查点紧贴预算失败与缺失最终结果,再评审修订方案及资源边界;本次授权不延伸为再次付费试验。U3质量出口未通过,不进入合入。U4和既有实例部署保持未启动。 + +## 6. 质量出口后续试验(另行批准一次并执行;结果FAIL) + +**触发和证据。** 已批准的额外A01实际使用两个run、四次provider调用、42203 returned Usage token,零接受的阶段发现。模型先提交含未知字段和错误嵌套的五条发现,随后尝试用省略号拼接来源引文;严格校验正确拒绝,两轮后台调用达到原每run 20000 token阈值,任务失败。独立逐项分析与原始收据见[核验报告](../reports/2026-10-13-fast-slow-u2-u3-validation.md)及额外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](2026-10-13-fast-slow-u3-quality-revision.md)另行固定三个职责、备选取舍、旧失败证据、零付费门禁与真实模型出口;本文 r0.1 已实施的 U2 契约、持久化、权限和三栏界面不因方向确认而重写。r0.2 书面方案已获批准;[实施计划 r0.2](2026-10-13-fast-slow-u3-quality-implementation-plan.md)仍待阶段 5 人工评审,新的付费样本尚未授权。 diff --git a/docs/proposals/2026-10-13-fast-slow-u3-quality-implementation-plan.md b/docs/proposals/2026-10-13-fast-slow-u3-quality-implementation-plan.md new file mode 100644 index 0000000..bfa7b8e --- /dev/null +++ b/docs/proposals/2026-10-13-fast-slow-u3-quality-implementation-plan.md @@ -0,0 +1,259 @@ +# 快慢通道 U3 质量修订 Implementation Plan r0.2 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. 本任务沿用已登记的 Dev worktree 和原生逐项实施方式;不再创建同名任务或重复启动 precheck。本文已获阶段 5 人工批准并开始零付费实施,**不是**新增付费模型授权。 + +**Goal:** 让同一任务卡的材料阶段成果忠于已提供原文、清楚标出待核对判断,并在受控预算内尽量形成真实最终答复。 + +**Architecture:** Backend 用现有 `materialSourceRegistry` 把来源编号和实际提供的原文块一次呈现,不改变来源权限或 Core JSON。Backend 在检查点提交前挡住模型生成的肯定批准断言,材料子代理依据已返回 Usage 为最终答复留请求空间;UI 在既有卡片中区分材料记载与待核对判断。旧失败样本、执行偏差和现有三栏仍是验收基线。 + +**Tech Stack:** Go、`agent-native/runtime` 子代理循环、SQLite/PostgreSQL、React/TypeScript、现有 `@ui`/Vitest/Playwright、Dev `make evidence` 与 Gitea CI。 + +**Spec:** [已批准书面设计 r0.2](2026-10-13-fast-slow-u3-quality-revision.md)。原 [U2/U3 计划 r0.1](2026-10-13-fast-slow-u2-u3-plan.md) 的 U2 契约、执行权及失败恢复不重做。用户已批准本实施计划开始零付费阶段;真实模型样本的资源边界仍待单独批准。 + +## Global Constraints + +- 保留现有三栏、同一任务卡及右栏「工作 / 文件」;不增加通道页、命令详情或新的确认能力。 +- 不改变 Core `WorkCheckpointCandidate` JSON、Store 表、SSE 事件、owner/project 约束及来源 quote 必须为连续原文的校验。 +- 不新增摘要/审核模型回合;旧样本及其原始 `exit=1`、57,997 Usage、14,231-token 停止规则偏差不重写。 +- 本轮只做零付费代码与浏览器验证。任何新真实模型样本须**另行明确批准** run 数、单 run/总 Usage 目标、在途越额风险和停止条件;未获批准为 `NOT_RUN`。 +- UI 颜色/圆角/字号只消费 `@ui/tokens.css`,动效只用 `@ui/motion.css`,折叠只用 `@ui` Collapsible;异步状态仍由现有 Frontend store 单一所有者按 seq 处理。 +- 组件先各自提交;Dev gitlink 每次推进后跑 `make test && make build`。正式验收、请求审核与合入前按 `policy/delivery.md` 和 AGENTS.md 执行 `make task-sync`;SELF 原始输出经 `make evidence` 进入任务卡。新候选的 GATE 只能引用准确 HEAD。 + +## Review Focus + +1. **重复文字/无 Origin/截断末块**:模型只得到注册表授权的 sourceId,输入文字仍与实际提供前缀一致;Task 1 的 `TestMaterialSourceInlineAnnotationsPreserveProvidedPrefix` 和 `TestMaterialSourceInlineAnnotationsNeverExposeHiddenBlocks` 覆盖。 +2. **否定、疑问与肯定审批表达**:“审批尚未完成”“是否已批准”“防止把50万写成已批准”不能被误拒;“预算是已批准决定”必须整批拒绝;Task 2 的表驱动测试覆盖。 +3. **默认摘要把 analysis 当事实**:既有材料记载可见,待核对判断带限定且总默认项数不超过三;Task 3 的 UI 组件测试覆盖。 +4. **两个检查点后仍无限继续/只有一个检查点时余额不足**:优先真实最终答复或诚实部分结果,不多发 provider 请求;Task 4 的假 provider 用量序列覆盖。 +5. **正常通知、来源导航与停止边界**:测试通知不代表生产通知质量;Task 5 的实际 HTTP/browser 用例及 Task 6 的独立真实样本出口分别覆盖,任何工具拒绝后不继续付费。 + +--- + +### Task 1:一次呈现原文和可引用来源(Backend) + +**Files:** Modify `modules/backend/internal/runmgr/material_sources.go`、`material.go`、`material_sources_test.go`、`material_test.go`;update the live-tag, zero-paid mechanism fixture in `modules/backend/internal/server/work_checkpoint_eval_test.go` and its regression in `work_checkpoint_eval_budget_test.go`;不改 `modules/core/task_snapshot.go`。 + +**Interfaces:** `materialSources(docs, perDoc)` 继续产出唯一授权注册表。给内部 `materialSource` 增加 `Start, End int`(相对于 `strings.TrimSpace(d.PlainText())` 的 byte offset),新增 `annotateProvidedMaterial(d core.ParsedDocument, provided string, sources materialSourceRegistry) string`。`buildMaterialTask` 仅渲染各文档提供的前缀与紧邻的 `sourceId/blockIndex`,删除末尾重复索引;`validate` 原语义不动。测试专用 `mechanismCheckpointSources(text string) [][]string` 仍返回正则组 `[full,id,blockIndex,providedLine]`,只适配新内联格式。 + +- [ ] **Step 1: 在既有测试写 RED。** `material_sources_test.go` 用含重复段落、截断段落、缺 Origin 和表格的文档,断言只对注册表里的块插入 `sourceId=... blockIndex=...`;移除这些标记后,正文与当前 `strings.TrimSpace(d.PlainText())` 的实际提供前缀逐字节相同。`material_test.go` 断言同一个短事实只出现一次、每个可引用标记紧邻其实际块、超长提示仍出现。 + + ```go + func TestMaterialSourceInlineAnnotationsPreserveProvidedPrefix(t *testing.T) { + d := materialDocs()[0] + d.Meta.Origin = &core.DocumentOrigin{ArtifactID: "art1", ContentHash: strings.Repeat("a", 64), ParserRevision: "docx1"} + docs := []core.ParsedDocument{d} + provided := strings.TrimSpace(d.PlainText()) + got := annotateProvidedMaterial(d, provided, materialSources(docs, 48000)) + markers := regexp.MustCompile(`(?m)^sourceId=src_[a-f0-9]+ blockIndex=[0-9]+\n`) + if !markers.MatchString(got) { t.Fatal("provided block has no source marker") } + if markers.ReplaceAllString(got, "") != provided { + t.Fatal("source annotations changed the provided material bytes") + } + } + ``` + + 在 live-tag 夹具新增 `TestMechanismSourceExtractionAfterSourceOnce`:给它 `sourceId=src_... blockIndex=0\n上线六月。验收条件需要补齐`,断言读取到真实 ID、索引和连续原句。当前夹具只接受尾部“引用定位片段:”行,故此测试须先 RED;它不发真实模型请求。 + + ```go + func TestMechanismSourceExtractionAfterSourceOnce(t *testing.T) { + id := "src_" + strings.Repeat("a", 24) + got := mechanismCheckpointSources("sourceId=" + id + " blockIndex=0\n上线六月。验收条件需要补齐") + if len(got) != 1 || got[0][1] != id || got[0][2] != "0" || !strings.Contains(got[0][3], "上线六月") { + t.Fatalf("inline source unavailable to mechanism fixture: %v", got) + } + } + ``` +- [ ] **Step 2: 运行 RED。** 在 Backend 分别运行 `go test ./internal/runmgr -run 'TestMaterialSourceInlineAnnotations|TestBuildMaterialTaskInlineBudget' -count=1` 和 `go test -tags live ./internal/server -run '^TestMechanismSourceExtractionAfterSourceOnce$' -count=1`;期望新增的“一次呈现/标记权限”和夹具内联读取断言各自失败,而旧来源 CAS 测试仍可运行。 +- [ ] **Step 3: 实现最小改动。** 在 `materialSources` 记录已提供块的 byte 起止;按 `Start` 排序,在原 `provided` 字符串里只插入来源标记,保持所有非标记字节、顺序和截断位置不变;缺 Origin 不赋 ID。`buildMaterialTask` 只构造一次注册表,不再调用末尾的 `prompt()`;`materialRevision` 的 `material-recipe.v7` 更新为 `v8`,旧完成结果不被新输入布局复用。同步更新 `evalMechanismStream` 的测试专用 source 提取器,使它读取新内联标记后面的已提供原句;它仍只提交来自可信 ID 和字面引用的假 provider 检查点,不替代真实模型验收。 + + ```go + sources := materialSources(docs, perDoc) + // 原有每份文档的 cap/left 计算保持不变。 + take := cap + if runes < take { take = runes } + provided := string([]rune(text)[:take]) + b.WriteString(annotateProvidedMaterial(d, provided, sources)) + ``` +- [ ] **Step 4: 验证 GREEN 与权限。** 重跑 Step 2,加 `go test ./internal/runmgr -run 'TestMaterialSourceQuoteRequiresActualProvidedBlock|TestMaterialSourceDuplicateTextDoesNotAuthorizeUnprovidedBlock|TestMaterialInputRevisionIncludesProcessingVersion' -count=1`,以及 `go test -tags live ./internal/server -run '^TestMechanismSourceExtractionAfterSourceOnce$' -count=1`。用旧 A01 材料在离线诊断中记录新旧输入字符数、sourceId 集合与 hash;只声明实际测得的长度,不推断新模型 token 使用量。 +- [ ] **Step 5: 自审并提交 Backend 独立提交。** 检查 `git diff --check`、新增 ID 与注册表一一对应、没有未提供文本或权限放宽;按本仓 `Why/Evidence/Impact/Refs` 提交。此项可被独立退回而不影响 Task 2 的审批防误判实现。 + +### Task 2:检查点审批断言守卫(Backend) + +**Files:** Create `modules/backend/internal/runmgr/checkpoint_claim_guard.go` 与 `checkpoint_claim_guard_test.go`;modify `task_checkpoint.go`、`task_checkpoint_test.go`、`internal/runtime/work_checkpoint.go` 的工具说明。生产拒绝只作用于系统材料整理的受控检查点,不修改一般对话正文。 + +**Interfaces:** `validateMaterialApprovalClaims(candidate core.WorkCheckpointCandidate) error` 在 `PublishWorkCheckpoint` 的 Core 基础校验后、任何持久写入前调用。检查 `analysis/provisional` 的 `Text/Impact/Assumptions` 及默认可见的 `Focus/Remaining/NextStep`。`material_record` 继续由宿主重写成原文;`needs_user` 可询问审批状态。肯定审批表达至少覆盖“已批准/已获批/审批通过/批复同意/已通过审批”,否定与待核对表达单列测试;返回稳定的 `checkpoint.claim: approval needs explicit source record` 错误,不把它包装成已通过校验。 + +- [ ] **Step 1: 写 RED。** 在 `task_checkpoint_test.go` 复用 `checkpointEnvironment` 的合法“上线六月”quote,却让 analysis 无依据声称“预算是已批准决定”,断言 `PublishWorkCheckpoint` 返回 `checkpoint.claim` 且卡 revision/发现数/事件数均不变。表驱动测试覆盖 `Text/Impact/Assumptions/Focus/Remaining/NextStep`,并包含“审批尚未完成”“是否已批准”“防止把50万写成已批准”、确有批准原文的 `material_record` 与 `needs_user` 问句,断言不误拒。 + + ```go + func TestTaskCheckpointRejectsUnsupportedApproval(t *testing.T) { + c := checkpointEnvironment(t) + cp := validCheckpoint(c, "unsupported-approval") + cp.Findings[0].Kind = "analysis" + cp.Findings[0].Text = "需求规定50万元,是已批准决定" + cp.Findings[0].Assumptions = "把需求条目作为项目基线" + if err := c.PublishWorkCheckpoint(cp); err == nil || !strings.Contains(err.Error(), "checkpoint.claim") { + t.Fatalf("unsupported approval was published: %v", err) + } + } + ``` +- [ ] **Step 2: 运行 RED。** `cd modules/backend && go test ./internal/runmgr -run 'TestTaskCheckpointRejectsUnsupportedApproval|TestMaterialApprovalClaims' -count=1`;期望错误分析目前被接受,修复后转为拒绝。 +- [ ] **Step 3: 实现有限守卫。** 新文件按中文句子边界检查明确的肯定审批短语,排除同句的未批准/疑问/防止误写语境;仅以此处理可确定的表达,不宣称通用语义正确。`PublishWorkCheckpoint` 先运行守卫,再走原有来源、owner、版本、CAS 与事务路径;工具说明要求批准事实只作为明确原文 `material_record`,推断为待核对。 + + ```go + if err := core.ValidateWorkCheckpoint(candidate); err != nil { return err } + if err := validateMaterialApprovalClaims(candidate); err != nil { return err } + // 原有来源、权限、版本及 CommitTaskSources 路径继续执行。 + ``` +- [ ] **Step 4: 跑 GREEN 与邻接回归。** Step 2 加 `go test ./internal/runmgr -run '^TestTaskCheckpoint' -count=1` 和 `go test ./internal/runtime -run '^TestWorkCheckpoint' -count=1`。若短语误拒或遗漏已批准变体,补夹具后只调整这个守卫;不借更宽泛的模型分数降低原来源门禁。 +- [ ] **Step 5: 自审并提交 Backend 独立提交。** 复核分析仍保留来源和 assumptions、材料记载的原文不被删除、被拒批次无半落盘;`git diff --check` 后按四段提交。 + +### Task 3:任务卡把原文与判断分层(UI) + +**Files:** Modify `modules/ui/src/ProgressCardBody.tsx`、`src/activitySummary.ts`、`test/progressive-cards.test.tsx`;仅在现有卡内分组,不修改 Frontend store 或新增 UI token。 + +**Interfaces:** `TaskProgressBody` 保持同一个 `ActivityCard.Task`。默认最多三条:优先展示至多两条 `material_record`,其余位置可放带“待核对的判断”标识的 analysis/provisional;`needs_user` 与更正继续即时可见。`activitySummary` 给模型判断加限定,不得把 analysis 的原始句子作为无保留的摘要事实;完整依据仍在现有 Collapsible。 + +- [ ] **Step 1: 写 RED。** 在 `test/progressive-cards.test.tsx` 构造两条材料记载、一条 analysis “预算已批准”、一条 `needs_user` 和第四条普通记录,断言默认三个以内且 analysis 所在区域有“待核对的判断”标签;`activitySummary` 的摘要不能出现无前缀的“预算已批准”;点击现有“查看依据/全部发现”仍能读原 quote 与历史更正。另用仅 analysis 的卡断言说明“已有待核对判断”,不冒称“已核对”。 + + ```tsx + // 在 test/progressive-cards.test.tsx 增加 activitySummary 与 TaskFinding import。 + const subject = card() + const source = { id: 's', artifactId: 'art', contentHash: 'h', parserRevision: 'txt', blockIndex: 0, label: '需求.txt · 段落1', quote: '预算50万尚未审批', availability: 'available' } + const finding = (id: string, kind: string, text: string): TaskFinding => + ({ id, kind, text, status: 'active', revision: 1, sources: [source] }) + subject.task = { schemaVersion: 1, taskId: 't', attemptId: 'a', cardId: subject.id, + projectId: subject.projectId, conversationId: subject.conversationId!, ownerInstanceId: 'i', + revision: 3, sourceSeq: 3, streamScope: 'conversation:' + subject.conversationId, + title: subject.title, executionState: 'running', outcomeCompleteness: 'partial', + deliveryState: 'available', phase: { id: 'crosscheck', label: '核对材料' }, + findings: [finding('r1', 'material_record', '材料记载1'), finding('r2', 'material_record', '材料记载2'), + finding('a', 'analysis', '预算已批准'), finding('q', 'needs_user', '请确认预算'), + finding('r3', 'material_record', '材料记载3')] } + render() + expect(screen.getByText('待核对的判断')).toBeTruthy() + expect(screen.getByText('预算已批准').closest('.acard-findings')?.textContent).toContain('待核对') + expect(activitySummary(subject).findings).not.toContain('预算已批准') + ``` +- [ ] **Step 2: 运行 RED。** `cd modules/ui && npm test -- test/progressive-cards.test.tsx`;预期当前同组“当前发现”会把 analysis 当普通项。 +- [ ] **Step 3: 实现最小 UI。** 沿用 `@ui` Collapsible 和现有 `acard-*` 类;按 kind 投影到材料记载/待核对判断/需要留意,默认总数上限三,剩余到“查看全部发现与更正记录”;更新摘要选择,不改变卡片状态或来源导航。若需 CSS,只消费现有 `@ui/tokens.css`、`--fs-ui-*` 和 `mk-*`。 + + ```tsx + const records = ordinary.filter(f => f.kind === 'material_record') + const judgments = ordinary.filter(f => f.kind === 'analysis' || f.kind === 'provisional') + const visibleRecords = records.slice(0, 2) + const visibleJudgments = judgments.slice(0, 3 - visibleRecords.length) + // needs_user/更正仍走既有关键项路径;未展示项保留在原 Collapsible。 + ``` +- [ ] **Step 4: 跑 GREEN 与设计守护。** `cd modules/ui && npm test -- test/progressive-cards.test.tsx test/design-guard.test.ts test/contrast-guard.test.ts && npm run typecheck`;检查浅/深色和窄屏文本层级,没有新裸色、私有 keyframes 或 `
`。 +- [ ] **Step 5: 提交 UI 独立提交。** 自审普通用户能一眼区分原文与模型判断,`git diff --check` 后按四段提交;它独立于 Task 4 的预算实现。 + +### Task 4:材料子代理的最终答复预留(Backend) + +**Files:** Create `modules/backend/internal/runtime/material_final_reserve.go`、`material_final_reserve_test.go`;modify `sub_loop.go`、`usage_n3_test.go`;replay the existing `run_budget_stream_test.go` unchanged。一般子代理、前台、生产模型路由和 `budgetedStream` 的已返回 Usage 守卫不扩大。 + +**Interfaces:** `type materialBudgetState struct { Limit, Used, LastPrompt, OutputCap, SavedCheckpoints, Responses int; UsageKnown bool }` 与 `materialFinalDecision(s materialBudgetState) (finalOnly, stop bool)` 只处理 `agent.sub.material`。`Responses=0` 是尚未产生调用的初态;已有响应但 Usage 未知则 fail-closed。增长余量 `margin=max(1024, LastPrompt/4)`;最终预留 `reserve=LastPrompt+OutputCap+margin`,再做一次检查点的估算成本也取同一值。`Limit-Used <= LastPrompt+margin` 时停止;否则 `SavedCheckpoints>=2` 或 `Limit-Used <= 2*reserve` 时进入 final-only。首个响应前不套用该估算。这样保留一次最终请求的空间,且一次成果后发现下一次发布会挤占预留时优先收尾,明确这是部分结论而非第二项。final-only 不再向 provider 声明 `publish_work_checkpoint`,并添加系统拥有的简短最终答复指令;若剩余额度不容纳预计最终 prompt,则不发新请求,保留已保存卡和真实失败状态。它不保证在途请求不越过总目标。 + +- [ ] **Step 1: 写 RED。** `material_final_reserve_test.go` 用假 stream 顺序返回用量,断言“初始零响应允许首个请求”“两个合法检查点→下一次仅最终答复”“只有一个检查点且额度临界→真实部分最终答复”“额度不足→0 次额外 provider 请求”“已有响应而全部 Usage 缺失→停止”“非材料子代理保持原行为”。`usage_n3_test.go` 补一条 `TotalTokens=0` 但 prompt/completion 有值时按两者之和入账的回归,与现有 `budgetedStream` 口径一致。故意让假 provider 在 final-only 中仍请求隐藏的检查点,断言 `runTool` 拒绝、设置本次执行的停止标记,下一轮在 provider 前停止且没有持久发布。测试检查 provider 请求里的工具声明和系统提醒,而非只检查内部布尔值。 + + ```go + func TestMaterialFinalReserveAfterTwoCheckpoints(t *testing.T) { + finalOnly, stop := materialFinalDecision(materialBudgetState{Limit: 30000, Used: 16000, LastPrompt: 5000, OutputCap: 4000, SavedCheckpoints: 2, Responses: 2, UsageKnown: true}) + if !finalOnly || stop { t.Fatalf("want one final-only request: final=%v stop=%v", finalOnly, stop) } + finalOnly, stop = materialFinalDecision(materialBudgetState{Limit: 30000, Used: 27000, LastPrompt: 8000, OutputCap: 4000, SavedCheckpoints: 1, Responses: 2, UsageKnown: true}) + if !stop { t.Fatalf("insufficient final prompt budget started another request: final=%v", finalOnly) } + } + ``` +- [ ] **Step 2: 运行 RED。** `cd modules/backend && go test ./internal/runtime -run 'TestMaterialFinalReserve|TestRunBudgetStreamStopsReportedTokenExhaustion' -count=1`;期望当前两检查点之后仍继续暴露发布工具或临界余额耗尽。 +- [ ] **Step 3: 实现调度。** `subRun.runTool` 仅在检查点真实成功时增加 `savedCheckpoints`,在 final-only 阶段拒绝模型凭旧记忆仍提交的检查点并设置停止标记;`wrapped` 下一次调用先检查此标记,直接返回错误而不接触 provider;`onEvent(message_end)` 用返回 Usage 更新 `Used/LastPrompt/Responses/UsageKnown`。`TotalTokens=0` 时仅在 prompt+completion 大于零才采用其和;三项均为零才标 Usage 未知并阻断后续请求。该回退也修复子 run 的 `AddUsage` 记账,不修改前台或 `budgetedStream`。在 `SubLLMRunner.Execute` 的 `wrapped` 调用前计算决策;final-only 只改当前请求的工具声明和系统提示,不写回历史、不提高 `MaxTokens` 或单次输出上限。预算不能证明可用时保留原失败/部分投影,不伪造正文。 + + ```go + if st.finalOnlyRejected { + out := make(chan agentllm.Event, 1) + out <- agentllm.Event{Kind: agentllm.KindError, Err: fmt.Errorf("budget_exceeded: checkpoint requested after final-only transition")} + close(out) + return out + } + limit := *rc.Profile().Budget.MaxTokens // 仅在 MaxTokens 非 nil 且 >0 的材料分支调用 + outputCap := outputTokenCap(rc.Profile().Budget, rc.Settings(), "models.chat.maxOutputTokens") + finalOnly, stop := materialFinalDecision(materialBudgetState{Limit: limit, + Used: st.usageTotal.TotalTokens, LastPrompt: st.usageTotal.LastPromptTokens, + OutputCap: outputCap, SavedCheckpoints: st.savedCheckpoints, + Responses: st.responses, UsageKnown: st.usageKnown}) + if stop { + out := make(chan agentllm.Event, 1) + out <- agentllm.Event{Kind: agentllm.KindError, Err: fmt.Errorf("budget_exceeded: final-answer reserve unavailable")} + close(out) + return out + } + requestDecls := append([]agentllm.ToolDecl(nil), decls...) + requestMessages := append([]agentllm.Message(nil), messages...) + if finalOnly { + filtered := make([]agentllm.ToolDecl, 0, len(requestDecls)) + for _, d := range requestDecls { if d.Name != workCheckpointTool { filtered = append(filtered, d) } } + requestDecls = filtered + requestMessages = append([]agentllm.Message{{Role: agentllm.RoleSystem, Content: "阶段成果已保存;现在用现有材料写出真实最终结论与未决项,不再发布检查点。"}}, requestMessages...) + } + in := stream(ctx, model, requestMessages, requestDecls, opts) + ``` +- [ ] **Step 4: 跑 GREEN 与现有子代理回归。** Step 2 加 `go test ./internal/runtime -run 'TestWorkCheckpoint|TestSub|TestRunBudget' -count=1`。用 A01 离线 Usage 序列(6,778/10,134/11,343 prompt,整体 35,632)验证决策会在原子预算检查之前转入 final-only 或诚实停止;不能把这个离线判断当新版真实模型通过。 +- [ ] **Step 5: 自审并提交 Backend 独立提交。** 检查无隐藏的附加模型回合、无流式并发数据竞争、无“两个检查点”计数伪造;按四段提交。 + +### Task 5:固定组件组合、零付费回归与独立 Review(Dev) + +**Files:** Modify Dev `modules/backend`、`modules/ui`、`modules/documents` gitlinks、`.task/feature-fast-slow-u2-u3-plan.md` and Documents 核验报告;Core/Frontend 不变。若测试证明公开契约或 Frontend store 必须改变,先返回方案评审再扩围。 + +- [ ] **Step 1: 确认每个组件工作树干净、提交 SHA 与 PR HEAD 一致。** Backend 完整 `go test ./... -count=1`,UI `npm test && npm run typecheck`,Frontend 受影响的现有 store/阅读测试重放;每项保留真实命令、SHA 和输出。 +- [ ] **Step 2: 在已登记 Dev 任务 worktree 固定 gitlink 和任务卡。** 先提交组件,再推进 Dev 指针;不要把旧样本的 `SELF` 或旧 HEAD CI 当新候选证据。 +- [ ] **Step 3: 正式组合验收。** 在干净的 Dev HEAD 以 `RECEIPT=/tmp/fast-slow-u2-u3-r02-sync-20261013.json CMD='make task-sync' make evidence` 同步最新主干,导入工具收据;随后以 `RECEIPT=/tmp/fast-slow-u2-u3-r02-combination-20261013.json CMD='make test && make build' make evidence` 捕获完整 SELF 输出并导入任务卡。执行前检查同名收据不存在;若已占用则用新的任务专用时间后缀,不覆盖。对数据库、来源 CAS 和预算路径执行直接相关 SQLite/真实 PG 测试。 + + ```bash + test ! -e /tmp/fast-slow-u2-u3-r02-sync-20261013.json + RECEIPT=/tmp/fast-slow-u2-u3-r02-sync-20261013.json CMD='make task-sync' make evidence + IMPORT_RECEIPT=/tmp/fast-slow-u2-u3-r02-sync-20261013.json make evidence + # 先提交任务卡中的同步收据,确保组合测试从干净 HEAD 开始。 + test ! -e /tmp/fast-slow-u2-u3-r02-combination-20261013.json + RECEIPT=/tmp/fast-slow-u2-u3-r02-combination-20261013.json CMD='make test && make build' make evidence + IMPORT_RECEIPT=/tmp/fast-slow-u2-u3-r02-combination-20261013.json make evidence + ``` +- [ ] **Step 4: 实际浏览器验收。** 用任务自有、带 PID/二进制 SHA/数据目录 manifest 的 mechanism 实例运行 `e2e/work-checkpoints.playwright.ts`;该 manifest 必须由本轮启动过程写入并在执行前验证,不复用旧文件。证明来源跳转、后台不抢阅读、刷新/旧版本、材料记载标签与浅色默认阅读;analysis 的限定标签由 Task 3 组件测试证明。不得使用旧端口或旧服务作为新版本证据。 + + ```bash + mkdir -p /tmp/fast-slow-u2-u3-r02 + test ! -e /tmp/fast-slow-u2-u3-r02/mechanism-data + test ! -e /tmp/fast-slow-u2-u3-r02/mechanism-manifest.json + test ! -e /tmp/fast-slow-u2-u3-r02/server-eval.test + cd modules/backend + go test -tags live -c ./internal/server -o /tmp/fast-slow-u2-u3-r02/server-eval.test + # 在保持打开的任务终端中执行前台服务,不用 nohup 后台启动。 + backend_sha="$(git rev-parse HEAD)" + dev_sha="$(git -C ../.. rev-parse HEAD)" + WORK_CHECKPOINT_SERVER_MODE=mechanism WORK_CHECKPOINT_SERVER_DATA=/tmp/fast-slow-u2-u3-r02/mechanism-data WORK_CHECKPOINT_SERVER_MANIFEST=/tmp/fast-slow-u2-u3-r02/mechanism-manifest.json WORK_CHECKPOINT_BACKEND_SHA="$backend_sha" WORK_CHECKPOINT_DEV_SHA="$dev_sha" /tmp/fast-slow-u2-u3-r02/server-eval.test -test.run='^TestServeWorkCheckpointEval$' -test.v + ``` + + 在任务终端 B 从 Dev 根目录验证 manifest,启动预览。代理目标必须从本轮 manifest 读取,不能沿用旧端口;4179 若已占用则停止并选择另一个本任务专用空闲端口,不能接管既有预览: + + ```bash + cd modules/frontend + npm run build + python3 -c 'import hashlib,json,os; m=json.load(open("/tmp/fast-slow-u2-u3-r02/mechanism-manifest.json")); assert m["mode"]=="mechanism" and m["dataDir"]=="/tmp/fast-slow-u2-u3-r02/mechanism-data" and m["base"].startswith("http://127.0.0.1:") and os.path.realpath("/proc/%s/exe" % m["pid"])==m["binary"] and hashlib.sha256(open(m["binary"],"rb").read()).hexdigest()==m["binarySHA256"]' + api_target="$(python3 -c 'import json; print(json.load(open("/tmp/fast-slow-u2-u3-r02/mechanism-manifest.json"))["base"])')" + PLAYWRIGHT_API_TARGET="$api_target" PLAYWRIGHT_PREVIEW_PORT=4179 npm run preview -- --host 127.0.0.1 --strictPort + ``` + + 在同时保持打开的任务终端 C,从 Frontend 目录执行浏览器用例;结束后只停止这两个任务拥有的前台进程并保存截图/追踪: + + ```bash + cd modules/frontend + PLAYWRIGHT_BASE_URL=http://127.0.0.1:4179 WORK_CHECKPOINT_HTTP_MANIFEST=/tmp/fast-slow-u2-u3-r02/mechanism-manifest.json npm run e2e -- e2e/work-checkpoints.playwright.ts + ``` +- [ ] **Step 5: 独立代码审阅与草稿 PR。** 审阅准确的 Backend/UI/Docs/Dev SHA,重放来源映射、审批误判、预算、停止边界及浏览器最小集;修正发现后只重跑受影响的检查。推送草稿 PR 并核验当前 HEAD 的 Gitea CI 运行身份、SHA、每个必需 job/step;质量出口仍 `NOT_RUN` 时不合入。 + +### Task 6:真实模型质量出口与交付决策(单独资源授权) + +**Files:** Backend `eval/live/work_checkpoints_test.go` 仅在固定候选所需的受控入口/读数缺口确有证据时修改;Dev 任务卡、Documents 核验报告写入结果。旧原始样本不覆盖、不补写。 + +- [ ] **Step 1: 先准备可审查资源提案。** 固定 Backend/Dev/Frontend/UI/Docs 精确 SHA、模型路由来源及受保护配置引用、A01 同一合成材料、Run/单 run/returned Usage 目标、输出上限、300 秒、缺 Usage、任何检查点拒绝、越额的停止规则与成本边界。明确在途调用可能越过目标;不给出虚假的账单硬上限。 +- [ ] **Step 2: 等待用户另行明确批准该一次性付费范围。** 未获批准时只记录 `NOT_RUN`,不以本计划或“继续”推定新样本授权;保持六个 PR 草稿。 +- [ ] **Step 3: 只在批准范围内运行一次。** 启动新的隔离数据目录与一份独立结果文件,启动前验证 PID/二进制哈希、route 文件权限及零既有 run;运行后逐调用对账真实付费 run ID、Usage、检查点持久事件、来源字面与语义、用户可读窗口和最终正文。拒绝工具候选后全实例停止,不自动重试或补写。 +- [ ] **Step 4: 独立审阅与门禁。** 若两个阶段、逐项语义、最终结果或停止规则任一 FAIL,就保留证据与草稿 PR,不合入或部署。若全部 PASS,依 `policy/delivery.md` 再同步 base/head、复核独立 Review 与当前 HEAD CI,然后由智能体合入、核验实际结果并按任务卡规则安全收口;部署仍不在本计划授权内。 diff --git a/docs/proposals/2026-10-13-fast-slow-u3-quality-revision.md b/docs/proposals/2026-10-13-fast-slow-u3-quality-revision.md new file mode 100644 index 0000000..4b1051b --- /dev/null +++ b/docs/proposals/2026-10-13-fast-slow-u3-quality-revision.md @@ -0,0 +1,45 @@ +# 快慢通道 U3 质量修订设计 r0.2 + +状态:三处修订方向、本文书面设计及[实施计划 r0.2](2026-10-13-fast-slow-u3-quality-implementation-plan.md)已于 2026-10-13 在本会话获用户确认,零付费实施已获批准。新的付费模型样本仍须另行明确授权;合入须满足真实模型质量出口,既有实例部署不在范围。原 [U2/U3 计划](2026-10-13-fast-slow-u2-u3-plan.md)的 U2 持久化、执行权、来源权限和三栏工作台目标继续有效;其 Task7 质量出口仍为 FAIL。 + +## 目标与已证实的问题 + +目标仍是让普通用户在同一张卡里及时看到有用、可追溯的阶段成果,随后得到真实的最终整理结果。材料原文、模型判断、待用户确认的决定须易于区分;默认视图不把未经证实的判断当成已批准事实。保留左侧找上下文、中间推进对话、右侧处理工作,不新增页面或命令展示。 + +已获批的 v7 A01 样本在 Backend `7f8674f` 上产生两次 running 态落盘,但后台因 30,000-token 运行目标耗尽而没有最终正文;首批 analysis 把需求里的“规定总预算/包含12项”说成“已批准决定”,引用原文没有该批准事实,会议还写明审批尚未完成。第二批距失败终态仅 1.076 毫秒,不能作为两个用户可感知阶段窗口的证明。该样本使用两个付费 run、五次 provider 调用、57,997 reported tokens;非原文引用被拒后仍继续一次 14,231-token 调用,违反原计划停止条件。原始失败与[独立核验](../reports/2026-10-13-fast-slow-u2-u3-validation.md)保留,不追溯改判。后续评测入口的实例级停止门禁已独立复审通过,但没有新的模型行为证据。 + +离线复盘:后台首轮输入 10,657 字符,其中单独追加的 33 条来源索引约 6,355 字符,主要重复已内联的材料。后台三次调用的 prompt tokens 分别为 6,778/10,134/11,343,累计 Usage 35,632;最后一次请求前没有为最终答复留下可用额度。来源引文的字面校验只证明片段存在,不能证明 analysis 对批准状态的解释正确。上述成本结构是可测的改进机会,**尚不能证明压缩后模型必定完成**。 + +## 方案选择 + +| 路径 | 取舍 | 结论 | +|---|---|---| +| 仅加强提示词或提高预算 | 改动少,但 v7 已经证明提示词无法独自防止批准误判;提高额度会增加付费且不修正语义 | 不作为下一候选 | +| 增加第二个模型审核/摘要回合 | 可检查部分判断,但违反本批零附加模型调用边界,也引入新成本与时序 | 本轮不选 | +| 来源一次呈现、宿主限制批准断言、为最终答复留额度 | 利用既有来源账本和检查点,不扩大权限或页面;需要回归验证映射、误拒与模型实际行为 | **推荐,分三项实施** | + +## 设计一:来源与正文合并呈现 + +`materialSources` 仍是唯一可信来源注册表,`validate` 的 owner/project、原件 hash、blockIndex、连续原文 quote 与版本检查不放宽。构造子任务输入时,按原文块顺序把已提供片段与它的 `sourceId`/`blockIndex` 放在一起;不再把相同内容以最多 180 字的索引片段重复附在末尾。模型只看到本次真正提供的前缀及其来源标识,未提供、无 Origin 或已截断的块不得获得可引用标识;原解析警告、文件名、表格文字与超长提示仍保留。这样来源编号紧挨着可引用原文,模型无需从两份文本间反查。 + +改变输入布局时递增材料处理配方版本,旧完成结果不复用为新行为。验收不仅比较字数,还要逐块证明:可见文本与原已提供前缀等价,来源 ID 集合与注册表一致,重复文本、截断块、表格、空文档和缺失 Origin 不越权。目标是显著减少 A01 输入重复量;数字由代码和固定材料实测给出,不事先声称节省 token 的比例。 + +## 设计二:批准状态和用户可见层级 + +`material_record` 继续由宿主以文件名和通过校验的原文片段生成,不接受模型转述。模型生成的 `analysis`/`provisional` 不能把审批、批复、通过等**肯定的批准状态**直接写成已成立事实;有明确批准原文时,让用户看到该原文记录,需要推断时写成待核对或 `needs_user`。宿主在检查点提交前拒绝可确定的肯定批准断言,保持整批原子拒绝,不把“尚未批准/待确认是否批准”误当肯定句。该规则只收紧这一类高后果断言,不能冒称通用语义验证器;A01 的每条分析仍须由独立审阅逐项核对。 + +现有任务卡继续展示真实状态、材料记载、判断、剩余工作与下一步。默认摘要优先呈现来源字面可核对的材料记载;模型判断明确标为“待核对的判断”,与原文记录分组,来源仍可在原 Collapsible 中展开。既有阅读、焦点、窄屏和三栏布局不变;颜色、字号、圆角、动效只用已有 UI 令牌。若只剩判断没有原文记录,卡片说明“已有待核对判断”,不写“已核对”或隐含批准。错误分析在独立样本中出现时不得进入无保留的默认摘要。 + +## 设计三:阶段成果后的最终答复额度 + +只对系统派发的材料整理子任务增加最终答复预算调度,不改变其他子代理与前台运行。依据 provider **已返回 Usage**、最新 prompt tokens、当前剩余额度、生产单次输出上限和已成功保存的检查点数,在下一次请求前预留至少一次完整最终答复的空间。已有两个有用检查点时,下一轮仅提供最终答复所需工具/提示,不再鼓励新增检查点;若只完成一项而继续发布会吃掉最终预留,优先给用户真实的部分结论,不伪造第二项。若连预计最终请求都放不下,就在发起新付费请求前停止并保留已保存成果。 + +预算是基于已返回 Usage 的请求前守卫,不宣称对在途请求构成严格账单上限。未知 Usage、检查点拒绝、达到 run/批次目标均按受控评测停止规则处理;不得因为缺额而自动扩大额度、换模型、回退或重新启动样本。正常产品路径可以继续按现有策略报告失败/部分成果,不能把阶段卡冒充最终正文。具体预留公式和最小可接受答复由后续实施计划固定,并用假 provider 覆盖高输入、两次发布、只完成一次、缺 Usage 与临界额度。 + +## 范围、验证与出口 + +预计修改 Backend 的材料输入组装、检查点提交守卫和材料子代理预算路径;UI 仅调整现有任务卡的来源/判断分组和摘要选择。Core 候选 JSON、数据库表、SSE 事件、权限模型、确认入口、并行/恢复所有权、生产路由和既有服务部署不在本修订范围。若实施发现必须改变上述公开契约或数据/权限设计,返回方案评审,不把它归为局部修复。 + +零付费门禁:来源映射与前缀等价测试;“需求规定50万、会议审批未完成”的错误批准候选先 RED 后 GREEN,明确批准原文与否定/待确认表达不误拒;预算假 provider 的两阶段加最终、临界额度与失败保持;UI 现有阅读、窄屏、焦点、设计守护及实际 HTTP 浏览器回归;Backend SQLite/真实 PostgreSQL 与 Dev 组合测试;独立代码审阅和精确组件 SHA。旧样本可用于离线回归,**不能当成新版真实模型前后对照**。 + +真实模型出口仍用同一合成 A01 及正常材料整理路径,至少两个真正有用、结束前可感知的不同阶段成果,逐项数字/审批/意见/引文正确,并有真实最终正文和完整 Usage;同时遵守任何检查点拒绝后停止的独立边界。行为面改动依 [Dev 交付政策](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/src/branch/feature/fast-slow-u2-u3-plan/policy/delivery.md)须在固定新候选上做任务级真实模型前后对照。新的付费样本、run 数、token 目标及停止条件要在候选代码和零付费审阅准备好后另行明确批准;未获批准标 `NOT_RUN`,全部 PR 保持草稿,不合入、不部署。 diff --git a/docs/proposals/2026-10-13-fast-slow-u3-r03-evidence-backed-judgments-design.md b/docs/proposals/2026-10-13-fast-slow-u3-r03-evidence-backed-judgments-design.md new file mode 100644 index 0000000..337c445 --- /dev/null +++ b/docs/proposals/2026-10-13-fast-slow-u3-r03-evidence-backed-judgments-design.md @@ -0,0 +1,56 @@ +# U3 r0.3:有依据的阶段判断 + +状态:**书面设计与[详细实施计划](2026-10-13-fast-slow-u3-r03-evidence-implementation-plan.md)均于 2026-10-13 获用户批准,零付费实施与验证进行中**。用户选择“保留阶段判断,数字和归属须有可验证依据;不能证明就标待确认;不增加模型调用”。计划批准不授权新付费模型样本、合入或部署。 + +关联:[U2/U3 原计划](2026-10-13-fast-slow-u2-u3-plan.md)、[r0.2 质量设计](2026-10-13-fast-slow-u3-quality-revision.md)、[r0.2 实施计划](2026-10-13-fast-slow-u3-quality-implementation-plan.md)及[真实样本核验](../reports/2026-10-13-fast-slow-u2-u3-validation.md)。既有任务卡、来源权限、执行权和三栏工作台继续有效。 + +## 要解决的具体失败 + +r0.2 唯一获批 A01 在固定 Dev `501ae35` / Backend `d89c72e` 上有两个运行中阶段成果(相隔约13秒,第二次至终态约21秒)和真实最终正文,五次 provider 调用返回 Usage 共46,154 tokens。第二批 `analysis` 却把“总12项中3项依赖外部接口”和“第一期8项”推成“剩余4项**包含**这3项”。原文未说明接口依赖项位于哪一期。该条仍是最终卡的 active finding,独立只读重放判质量 **FAIL**;测试入口 exit0、11处字面引文正确都不能改变此结论。 + +当前材料配方 v8 已明确写着“原文没有给出的分期归属不能写成事实”,仍未阻止错误。现有宿主守卫只阻断部分肯定“已批准”措辞,无法判断集合关系;`assumptions` 是模型自述,也可能重复错误前提。用户要看到及时、可用的判断,但不能把不可推出的归属当事实。界面继续把原文、判断和待确认事项分层,后台细节不默认呈现。 + +## 方案取舍 + +| 路线 | 收益 | 主要代价 | 结论 | +|---|---|---|---| +| 只加提示词、自检句 | 接口变化小 | v8 的明确禁令已在真实样本失效,无法给宿主可复核依据 | 不单独采用 | +| 再加一个语义审核模型 | 可覆盖更多自然语言 | 新付费回合、延迟和第二模型的误判,且仍不能证明正确 | 本轮不选 | +| **结构化依据 + 宿主保守归一化** | 在不加模型调用的条件下保留可证明的判断,未知关系变成明确问题 | 需收紧候选契约和支持的关系范围;不认识的表达会降为待确认 | 推荐 | + +## 用户可见结果 + +沿用同一张任务卡和现有分组:原文仍显示为“材料记载”;能由宿主核对的算式显示为带前提的“待核对的判断”;无法确定的归属显示为“需要留意/待确认”,例如“已提供片段没有说明3项接口依赖需求分别属于哪一期,请核对”。宿主不复述模型原来的肯定归属句。来源仍可在原有“查看依据”中展开;没有新页面、命令详情或确认按钮。 + +本文限定的是**阶段检查点**。最终正文仍须在单独真实模型样本中逐项核验,不能因为阶段归一化就称全文语义已由宿主证明。已有 r0.2 错误卡和原始结果不追溯改写;后续纠正沿现有版本/更正历史走。 + +## 候选与宿主边界 + +`WorkFindingCandidate` 增加可选的 `evidence` 对象,仅给本轮材料整理工具使用;已有 `TaskFinding` 展示字段、来源引用、事件序号与持久化表保持不变。模型一次工具调用仍提交1–3条新增发现,不新增摘要或审核模型回合。处理配方从 `material-recipe.v8` 递增,旧完成结果不当成新规则结果复用。 + +`evidence` 先支持三种明确结果: + +1. **原文直接关系。** 候选给出 sourceId、blockIndex 和连续原句。宿主沿既有 owner/project、原件 hash、解析版本和可见前缀校验,只把原句明确说出的归属或决定作为材料记载展示;自由转述不提升可信等级。 +2. **可复算数量。** 候选给出“总量”“已明确子范围”两个带原文短引的整数前提及减法。宿主只在受支持的数量/范围表述能从各自原文定位时计算差额并生成文字,如“12−8=4(推算)”;这个差额不自动等于第二期清单,也不推出3项接口依赖位于差额中。未知措辞、单位不一致、前提超出已提供片段或算式不符,都不发布肯定数量判断。 +3. **关系未定。** 如材料只给总量12、第一期8、其中3项依赖接口,宿主能算出剩余4,但接口项与剩余部分的交集可能是0–3,不能确定为3。宿主将该条归一化为 `needs_user` 的待核对问题,保留经校验的来源;模型提供的肯定归属原句不进入任务卡。只有原文直接给出接口项的分期归属,或受支持的前提把可能范围收敛成唯一值时,才可发布确定关系。 + +任何带数字、集合包含、阶段/批次归属或“已决定/已批准”状态的**模型自由判断**,若没有受支持的 `evidence`,走保守待确认通道,不以模型 `assumptions` 代替证明。普通没有这些高后果关系的分析仍保留 r0.2 “待核对的判断”标签和来源展开,不宣称通用自然语言蕴含验证。输入语义解析只采用公开、有限、可测试的表述模式;遇到同义或复杂句不猜测而转待确认。校验器应记录归一化原因供审阅,但不把内部术语放到默认界面。 + +提交顺序仍是“校验全部来源与候选 → 归一化可确定/待确认条目 → 一次 CAS 事务提交同批结果”。来源无效、伪造 ID、版本漂移或候选结构不合法时整批拒绝,并遵守既有“拒绝后不再发付费请求”的受控试验边界;**关系证据不足但来源合法**时不是工具错误,而是由宿主在同一批中安全生成待确认条目,不丢弃同批有效发现。工具结果须明确告知模型该条已转待确认,供正常下一轮最终答复使用,不增加额外模型回合或自动重跑。 + +## 接口、权限和失败恢复 + +- Core 只扩展候选 JSON 的可选依据结构及校验;Backend 的工具声明、材料配方和检查点校验实现证据处理。Frontend/UI 跟随 Core 生成类型镜像,但既有卡片显示原语不变;若实施发现必须改展示契约,再回设计评审。 +- 来源登记表、actor/owner/project、引用必须是已提供原文连续片段、原件 hash/解析版本与原子提交边界不放宽。不存在“模型说有依据,所以宿主接受”的路径。 +- 新字段只作用于后续候选,旧卡、旧事件和原 r0.2 结果只读保留,无数据库迁移。中断恢复仍按宿主 attempt/epoch 执行;同一候选幂等键及原有更正语义不变。 +- 预算依赖真实返回 Usage 与已修复的宿主 run ID 分组;依据字段可能增加输入/输出 token,实施时需离线比较上下文体积。没有额外调用或静默提高额度。任何新付费 A01 必须重新提交 run 数、用量目标和在途越额边界并取得单独批准。 + +## 零付费验证与真实出口 + +先写失败用例再实现,至少覆盖:本次 A01 的错误归属不能作为分析落盘;同样数字但**明确写明**3项均在第二期的原句可作为材料记载;12−8=4可显示为推算但不附加接口归属;交集可能范围0–3转待确认;已批准/尚未批准/是否批准与“六月已决定”无直接决定原句时的状态区分;同批一条有效一条证据不足的原子归一化;伪造来源和版本漂移仍整批拒绝;旧检查点幂等、刷新/阅读焦点不回退。Backend SQLite/真实 PostgreSQL、Core/类型镜像、UI设计守护、Dev `make test && make build`、任务自有 HTTP 浏览器和独立代码复审均固定准确 SHA。 + +真实质量出口沿用同一 A01 合成材料:至少两个真正有用、终态前可感知的阶段成果;所有金额、决定状态、日期意见、12/8/3数字、来源及集合归属逐项正确;有完整最终正文与 Usage;任何拒绝后零后继付费请求。阶段问题可诚实写“未明确”,不得靠删除判断、隐藏错误历史或降低验收条件放行。新模型样本在书面设计与实施计划获批、零付费候选完成后另行申请资源;失败保留草稿 PR,不合入或部署。 + +## 已知边界与待审决定 + +有限原文模式可能把真实但陌生的表述降为待确认,这是偏保守的可用性成本;需在真实材料与测试中量化,不能把它描述为通用语义正确性证明。依据字段增加模型输出负担,可能缩短最终答复余量,仍由已返回 Usage 调度;质量样本必须验证不牺牲最终正文。设计审批时请确认这一“保守降级,保持来源可查看”的取舍;若不可接受,需重新评估独立语义审核或人工处理的成本与边界。 diff --git a/docs/proposals/2026-10-13-fast-slow-u3-r03-evidence-implementation-plan.md b/docs/proposals/2026-10-13-fast-slow-u3-r03-evidence-implementation-plan.md new file mode 100644 index 0000000..7902db0 --- /dev/null +++ b/docs/proposals/2026-10-13-fast-slow-u3-r03-evidence-implementation-plan.md @@ -0,0 +1,144 @@ +# U3 r0.3 有依据的阶段判断 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. 本计划已于 2026-10-13 获用户阶段5批准,授权在现有任务工作区内做零付费实施与验证;新付费样本仍须单独批准。 + +**Goal:** 保留同一任务卡中有用的阶段判断;数字、集合归属和决定状态没有可核对依据时,只显示待确认问题,不把模型的肯定句当成事实。 + +**Architecture:** Core 为材料检查点候选增加可选结构化依据;Backend 沿既有来源注册表校验短引与有限数量关系,把可复算结论或未定关系归一化,再用现有 CAS 一次提交。UI 仍消费原有 `material_record`、`analysis`、`needs_user`;不增页面、按钮、数据库表或模型回合。真实模型质量仍须另行授权验证。 + +**Tech Stack:** Go、Core Go/TypeScript 契约、Backend runmgr/runtime、SQLite/PostgreSQL、React/TypeScript、Vitest、Playwright、Dev `make evidence` 与 Gitea CI。 + +**Spec:** [U3 r0.3 书面设计](2026-10-13-fast-slow-u3-r03-evidence-backed-judgments-design.md);失败依据在[U2/U3 核验报告](../reports/2026-10-13-fast-slow-u2-u3-validation.md)。初始工作区沿用已登记的 Dev `feature/fast-slow-u2-u3-plan`,不重做本任务 precheck、不创建第二份任务卡。r0.2 唯一 A01 的 `46,154` Usage、语义 FAIL 和原始文件均不改判。书面设计与本计划均已获用户批准;付费资源未获新授权。 + +## Global Constraints + +- **阶段边界:** 本计划获人工批准后才改 r0.3 语义代码;真实模型样本仍须另外固定 run/Usage/在途风险并单独批准。旧授权已用尽,不自动重试。新样本任一语义或停止规则失败,六 PR 保持草稿,不合入或部署。 +- **既有能力:** 不改 TaskSnapshot 数据表、事件序号、owner/project/attempt/epoch 权限、连续原文引用、历史更正与最终正文所有者。来源无效/版本漂移仍整批拒绝;依据不足但来源有效则安全归一化,并将同批结果一次 CAS 保存。 +- **界面:** 左侧上下文、中间对话、右侧工作/文件不变;默认最多三项普通发现,关键 `needs_user` 可见,历史和依据仍用 `@ui` Collapsible。颜色/字号/圆角仅用 `@ui/tokens.css`,动效仅用 `@ui/motion.css`;异步状态唯一所有者仍是 Frontend store,旧 seq 丢弃。 +- **成本:** 不增摘要/审核模型回合,不改生产路由或默许提高预算;依据字段增加 token 的影响必须离线量化,最终答复预留按真实返回 Usage 继续生效。 +- **治理:** 实施首步先核对并同步已推进的权威 main,组件各自提交后推进 Dev gitlink;已有任务不重复 `make precheck`。验收、请求审核和合入前分别以 `CMD='make task-sync' make evidence` 同步;组合 `make test && make build` 的完整 SELF 收据由工具导入任务卡。独立 REVIEW/REPLAY 与精确 HEAD GATE 分开标记,禁止用旧 SHA CI 放行。 + +## File Map and Interfaces + +| 单元 | 文件 | 职责 | +|---|---|---| +| 候选契约 | `modules/core/task_snapshot.go`、`task_snapshot_test.go`、`ts/types.ts` | 可选 `WorkFindingEvidence`,只约束候选,不改持久 TaskFinding;`sourceIndex` 指向同条现有 `sources` | +| 原文计数 | `modules/backend/internal/runmgr/material_evidence.go`、`material_evidence_test.go` | 在已校验来源短引中识别有限的总量/首期/依赖数量,算差额和可能交集,未知降级 | +| 提交与提示 | `modules/backend/internal/runmgr/task_checkpoint.go`、`material.go`、`material_sources.go`、相关测试;`internal/runtime/work_checkpoint.go` | 全字段风险检查、单批归一化/CAS、工具 JSON、归一化回执与 v9 处理配方 | +| 类型镜像/展示 | `modules/frontend/src/contracts.ts`、`modules/ui/src/contracts.ts`(`make contracts` 生成);`modules/ui/test/progressive-cards.test.tsx`、`modules/frontend/test/work-state.test.ts` | 消费相同现有卡片类别,待确认默认可见、历史/阅读无回退;无新页面 | +| 组合和记录 | Dev gitlinks、`.task/feature-fast-slow-u2-u3-plan.md`、Documents 核验报告 | 精确 SHA、SQLite/PG/浏览器、独立评审、草稿 PR 和结果出口 | + +候选新增的内部契约固定为: + +```go +type WorkEvidencePremise struct { + Role string `json:"role"` // total, phase, dependent, direct_relation + SourceIndex int `json:"sourceIndex"` // index into this finding's Sources + Value *int `json:"value,omitempty"` +} +type WorkFindingEvidence struct { + Mode string `json:"mode"` // direct_relation, difference, unresolved_relation + Premises []WorkEvidencePremise `json:"premises"` + Result *int `json:"result,omitempty"` // only difference +} +// Add to existing WorkFindingCandidate only: +Evidence *WorkFindingEvidence `json:"evidence,omitempty"` +``` + +Core 只验证枚举、数量、索引、字段组合和现有 16KiB 候选上限。Backend 的 `normalizeMaterialEvidence(candidate core.WorkCheckpointCandidate, sources materialSourceRegistry) (core.WorkCheckpointCandidate, []string, error)` 对已验短引、数值角色和关系作安全归一化:返回归一化候选、给工具结果看的简短说明、或必须整批拒绝的错误。实现时用 `TaskFinding` 原有类别和 `Sources/Assumptions` 保存输出,不引入新表。`direct_relation` 仅允许 `material_record`,只在短引本身明确写出关系时按现有宿主路径展示原文;`difference` 由宿主生成 `analysis` 和 `total-phase` 算式及“推算”;`unresolved_relation` 对可验证的三个计数计算交集范围,范围不唯一时由宿主生成 `needs_user`,范围唯一时也只由宿主生成有前提的判断,均不复制模型肯定归属句。现有无依据的 `material_record` 原文路径继续有效。 + +有限识别只接受短引中明确的“总/包含 N 项”“第一期 N 项”“其中 N 项依赖外部接口”等受支持中文数量语法及直接关系原句;超过已提供前缀、单位不一致、多个无法消歧的数字、陌生表述均走待确认。**不声称通用自然语言蕴含证明**。风险检查同时覆盖 findings 的 text/impact/assumptions 和 focus/remaining/nextStep;后者不能携带未经证明的归属或决定肯定句,必要时由宿主替换为中性进度/待核对说明。结构合法但原文不能证明的依据按“待确认”处理;sourceId/版本/权限不合法仍整批拒绝。 + +## Review Focus + +1. **数字同句多义:** “第一期8项、第二期4项”不能把第二个数字当首期;单位“万元/项”不得交叉计算。Task 2 的多数字/混合单位 RED 用例必须保守降级。 +2. **来源重复与截断:** 相同文字在两个文件、无 Origin、或来源只提供了首段时,`sourceIndex` 不得越权引用另一个块。Task 2 的来源映射与版本 RED 用例必须整批拒绝伪造来源。 +3. **旁路字段:** findings 正确但 focus/remaining/nextStep 称“已决定第二期含3项”时,默认卡不能泄露该肯定句。Task 3 的全字段投影测试必须验证中性替换。 +4. **混合批次:** 一条有效差额、一条归属未定、一条来源无效:前两者可在来源均合法时原子归一化;来源无效时全部不提交,且没有后继付费请求。Task 3 的 CAS/拒绝/幂等测试覆盖。 +5. **预算和阅读回归:** 结构化依据扩大提示后,final-only 仍为最终正文留额度;待确认发现刷新/窄屏/来源跳转不抢文件和焦点。Task 4 的 fake-stream、UI/Frontend 与真实 HTTP 浏览器测试覆盖。 + +--- + +### Task 1:Core 候选依据契约 + +**Files:** Modify `modules/core/task_snapshot.go`、`task_snapshot_test.go`、`ts/types.ts`;不改 `TaskSnapshot`/`TaskFinding` 和数据库。 + +**Interfaces:** `WorkFindingCandidate.Evidence *WorkFindingEvidence`;`SourceIndex` 只引用同一 finding 的 `Sources`。Core 的 `ValidateWorkCheckpoint` 验证 `direct_relation` 恰一条关系前提且无 Result;`difference` 恰有 total/phase 两项且有 Result;`unresolved_relation` 恰有 total/phase/dependent 三项且无 Result。整数范围 `0..1,000,000`,同一模式不允许重复 role,索引须 `0 <= index < len(Sources)`;计算与原文语义交给 Backend。 + +- [ ] **Step 0: 在现有任务工作区同步基线。** 确认当前 Root/组件提交及任务卡收据已保存,使用 `RECEIPT=/tmp/fast-slow-u3-r03-first-sync-20261013.json CMD='make task-sync' make evidence`;导入原始收据。先核对最新 main 的项目成员/公开契约差异,遇到需改变本设计的冲突停在计划阶段。组件分支随后以同一权威主干处理各自合并;不得用旧 r0.2 组合假定兼容。 +- [ ] **Step 1: 写 RED。** 在 `task_snapshot_test.go` 用现有合法候选加 `Evidence`,表驱动覆盖三个模式的合法组合、索引越界、负数、重复 role、未知 mode、difference 缺结果、unresolved 偷带结果以及候选 JSON 不超过现有16KiB工具上限。TS `types.ts` 增加同名可选字段后,构建期校验生成镜像一致。 + + ```go + candidate.Findings[0].Evidence = &WorkFindingEvidence{Mode: "difference", Premises: []WorkEvidencePremise{ + {Role: "total", SourceIndex: 0, Value: intPtr(12)}, + {Role: "phase", SourceIndex: 1, Value: intPtr(8)}, + }, Result: intPtr(4)} + if err := ValidateWorkCheckpoint(candidate); err != nil { t.Fatal(err) } + ``` +- [ ] **Step 2: 跑 RED。** `cd modules/core && go test ./... -run 'TestWorkCheckpointEvidence' -count=1`;预期新类型/校验缺失。 +- [ ] **Step 3: 实现 Core 类型与结构校验。** 仅增加上述候选字段及 `ValidateWorkCheckpoint` 的有限模式检查;TS 权威镜像同步,不增加状态枚举或前端行为。 +- [ ] **Step 4: 跑 GREEN。** `cd modules/core && go test ./... -count=1`;检查 `git diff --check` 和 Go/TS 字段名一致。 +- [ ] **Step 5: 独立提交 Core。** 主题行英文祈使,body 写 `Why:/Evidence:/Impact:/Refs:`;Dev 指针留到 Task 4 统一绑定。 + +### Task 2:Backend 纯依据判定与保守降级 + +**Files:** Create `modules/backend/internal/runmgr/material_evidence.go`、`material_evidence_test.go`;Backend 内嵌 `deps/agent-system-core` 指向 Task 1 精确 SHA。组件同步若涉及权限/公开契约取舍,暂停并交人工决定。 + +**Interfaces:** `normalizeMaterialEvidence(core.WorkCheckpointCandidate, materialSourceRegistry) (core.WorkCheckpointCandidate, []string, error)` 不写库、不调用模型;调用方先对全部 `sources` 执行 `registry.validate` 与持久原件校验,函数只使用已提供前缀和候选中的同条来源短引。`parseCountPremise(quote, role, value) bool` 只识别受支持数量模式。`difference` 由宿主重新计算 result;`unresolved_relation` 用 `remaining=total-phase`、`low=max(0,dependent-phase)`、`high=min(dependent,remaining)`,`low!=high` 时把对应 finding 改为 `needs_user`、清空模型肯定 text/impact、填入宿主文字与真实来源;`low==high` 时也只发布宿主生成的、附前提的确定数量关系。前提数值超出总量安全降级,绝不输出伪算式。 + +- [ ] **Step 1: 写 RED。** 用 r0.2 A01 的三份首段构造来源注册表及候选:错误 `phase-list-gap` 不得保存“剩余4项包含3项”;12−8=4可保留为带“推算”的判断;明确原句“第二期包含3项依赖外部接口”可展示原文;0–3的未定交集转待确认。另覆盖 `12项/8万元`、同句两个阶段数字、来源重复、无 Origin、截断块、旧 parserRevision 和陌生数词。 + + ```go + normalized, notes, err := normalizeMaterialEvidence(candidate, sources) + if err != nil { t.Fatal(err) } + if normalized.Findings[0].Kind != "needs_user" || strings.Contains(normalized.Findings[0].Text, "其中包含3项") { + t.Fatal("unsupported phase membership escaped", normalized.Findings[0]) + } + if len(notes) == 0 { t.Fatal("model was not told that the claim became pending") } + ``` +- [ ] **Step 2: 跑 RED。** `cd modules/backend && go test ./internal/runmgr -run 'TestMaterialEvidence|TestMaterialSetBounds' -count=1`;预期未实现或错误断言失败。 +- [ ] **Step 3: 实现最小纯函数。** 原文直接关系优先保留 quote 的字面展示;仅支持 `total/phase/dependent` 的有限角色和一致单位。自由文本中 `已决定/已批准/其中/包含/属于/剩余/第一期/第二期` 与数量或归属共现、却无可验证依据时,产出通用待确认句,不原样回显风险词句;不认识的表达也走保守路径。 +- [ ] **Step 4: 跑 GREEN 与现有审批边界。** `go test ./internal/runmgr -run 'TestMaterialEvidence|TestMaterialSetBounds|TestMaterialApprovalClaims|TestMaterialSource' -count=1`;测试未触发的普通分析仍带“待核对”,否定/疑问审批表达不误拒。 +- [ ] **Step 5: 自审并提交 Backend 独立提交。** 记录识别语言的有限范围及未知降级成本,不声称通用语义正确性。 + +### Task 3:一次提交、提示与预算兼容 + +**Files:** Modify `modules/backend/internal/runmgr/task_checkpoint.go`、`task_checkpoint_test.go`、`material.go`、`material_sources.go`、`material_sources_test.go`;modify `modules/backend/internal/runtime/work_checkpoint.go`、`work_checkpoint_test.go`。不改已经落盘的 r0.2 样本、数据库表或 SSE 契约。 + +**Interfaces:** `PublishWorkCheckpoint` 先结构校验和**全部**来源合法性,再调用 Task 2 纯函数归一化与审批/全字段风险守卫,最后沿现有 `CommitTaskSources` 做一次 CAS。Backend 对所有模型文本和结果文本都做守卫;有明确原文的 `material_record` 沿现有宿主引用路径显示。有效来源但不唯一的关系提交 `needs_user`;来源伪造、hash/版本漂移、JSON未知字段或越权依旧整个检查点失败。`runtime.WorkCheckpointSink.PublishWorkCheckpoint` 从 `error` 改为 `(string,error)`,`runCtx` 在 CAS 成功后才返回简短归一化回执;`publishCheckpoint` 将回执附在既有成功消息,供正常下一轮模型看见,不新增 provider 调用。处理配方从 `material-recipe.v8` 到 `material-recipe.v9`。 + +- [ ] **Step 1: 写 RED。** `task_checkpoint_test.go` 用 SQLite 断言“有效差额+未定关系”同批一次提交、无伪造肯定句;同批再加非法 sourceId 后整个批次零提交且已保存旧成果不丢;幂等同 ID 不重复计数,更正旧 finding 保留历史。`work_checkpoint_test.go` 的假 stream 断言工具成功消息说明“转待确认”,仍只有既有最终答复请求;focus/remaining/nextStep 注入无依据决定句会被中性化。 + + ```go + before := card.Task.Revision + receipt, err := ctx.PublishWorkCheckpoint(mixedCandidate) + if err != nil { t.Fatal(err) } + if !strings.Contains(receipt, "待确认") { t.Fatal(receipt) } + after, _ := store.GetCard(owner, card.ID) + if after.Task.Revision != before+1 || after.Task.Findings[len(after.Task.Findings)-1].Kind != "needs_user" { + t.Fatal("mixed batch was not normalized atomically") + } + ``` +- [ ] **Step 2: 跑 RED。** `cd modules/backend && go test ./internal/runmgr ./internal/runtime -run 'TestWorkCheckpointEvidence|TestMaterialFinalReserve' -count=1`。 +- [ ] **Step 3: 连接校验与工具说明。** 更新严格 JSON schema 的可选 evidence;将 `WorkCheckpointSink`、`runCtx` 与现有单测调用点一起迁移到 `(string,error)`,使同一 checkpointId 幂等重发也能返回同一安全说明。材料任务提示只说明三种依据及未知归属要转待确认,不重复整份来源索引;工具结果回报归一化原因但不向默认UI暴露 sourceId。完整来源/阶段状态仍由宿主唯一拥有。 +- [ ] **Step 4: 跑 GREEN 与失败恢复。** `go test ./internal/runmgr ./internal/runtime -count=1`,加 `go test -tags live ./internal/server -run 'TestMechanismSourceExtractionAfterSourceOnce|TestEvaluationStops' -count=1`;离线比对 A01 输入与工具 schema 字符数/估算 token,若最终预留明显不足,停下来重新评审预算策略,不能私自提高额度。 +- [ ] **Step 5: 提交 Backend。** 精确记录 Core gitlink、新配方版本、RED/GREEN、正常/拒绝路径,不把假 stream 称为模型质量证明。 + +### Task 4:组合、真实数据库与既有阅读界面 + +**Files:** Update generated `modules/frontend/src/contracts.ts`、`modules/ui/src/contracts.ts` only through `make contracts`;add/adjust `modules/ui/test/progressive-cards.test.tsx` and `modules/frontend/test/work-state.test.ts` if the current behavior is not already covered;update Dev component gitlinks and task card。任何新视觉令牌、页面、store 所有权或公开 TaskSnapshot 字段需求须回设计评审。 + +- [ ] **Step 1: 同步后的组件核对。** 复查 Task 1 的权威 main 同步记录和 Core/Backend 分支来源;组件各自与最新权威 main 核对,解决不改变设计的冲突。当前主干已含项目成员相关 Backend/Frontend/Documents 变化,不能用旧 r0.2 组合声称兼容。 +- [ ] **Step 2: 类型镜像 RED→GREEN。** 固定 Core SHA 后先运行 `make contracts`,用 `git diff --exit-code` 检查生成镜像与 Core 类型一致;该命令预期在两个镜像尚未提交时指出差异,随后按生成结果提交。UI 断言材料记载优先、待核对判断限量、`needs_user` 在“需要留意”默认可见;Frontend 断言刷新/迟到来源响应不抢文件/焦点,旧 seq 丢弃。`cd modules/ui && npm test && npm run typecheck`、`cd modules/frontend && npm test && npm run build`。 +- [ ] **Step 3: PostgreSQL 与 SQLite。** Backend 全量 `go test ./... -count=1`;用任务自有新数据目录/端口启动隔离 PostgreSQL,`AGENT_PG_TEST_DSN` 仅指向该测试库,运行 `go test ./internal/runmgr ./internal/cardsvc ./internal/store -count=1` 并停止任务进程;来源CAS、混合批次和预算路径须在两档一致。输出经 `make evidence` 记录。 +- [ ] **Step 4: Dev 组合与真实浏览器。** 组件都提交后推进 gitlink,干净 HEAD 上用 `CMD='make test && make build' make evidence` 捕获完整输出并导入任务卡。启动带 PID/二进制 SHA/数据目录/组件 SHA manifest 的新任务自有 mechanism 实例;`PLAYWRIGHT_BASE_URL`、`WORK_CHECKPOINT_HTTP_MANIFEST` 均从本轮 manifest 验证后传入 `e2e/work-checkpoints.playwright.ts`。用真实 HTTP 验证待确认卡、来源跳转、版本变化、刷新和后台不抢阅读;仅停止本任务进程,保留 trace。 +- [ ] **Step 5: 独立代码复审与草稿 PR。** 审阅者用准确 Core/Backend/Frontend/UI/Docs/Dev SHA 重放五类 Review Focus;修复 Important 后按影响补测。六个 PR 继续草稿,推送当前 HEAD,核验 Dev `test/test-pg/svc-realcombo`、Backend `vet-and-test`、UI `test` 的受信 runner、SHA、所有必需步骤;Core/Frontend/Documents 无独立工作流时由 Dev 组合 CI 覆盖其 gitlink。真实模型仍 `NOT_RUN`,不合入。 + +### Task 5:真实模型质量出口(另行资源审批) + +**Files:** 只在受控评测入口确有缺口时改 `modules/backend/eval/live/work_checkpoints_test.go`/`internal/server/work_checkpoint_eval_test.go`;结果写 Dev 任务卡与 Documents 核验报告。旧 r0.2 A01 的原始 exit0、46,154 Usage 和语义 FAIL 不改写。 + +- [ ] **Step 1: 准备可审查、一次性的资源提案。** 固定新 Dev/Backend/UI/Frontend/Docs SHA、同一 A01 材料哈希、受保护路由来源(不输出 key)、新结果文件名、最多 run 数、单 run 与整批**返回 Usage 目标**、输出上限/300秒、缺 Usage/检查点拒绝/超额停止规则;明确在途调用可越过目标,无法凭源码承诺货币硬上限。 +- [ ] **Step 2: 等待用户明确批准。** 没有新授权则 `NOT_RUN`,六 PR 草稿且不合入;r0.2 的“授权”只覆盖已经执行的一次,不能复用标签或重跑。 +- [ ] **Step 3: 获批后只运行一次。** 全新隔离数据/manifest/输出,启动前重验 PID、二进制哈希、路由权限、零既有 run;逐调用核对实际 paid run ID、工具次数、Usage、持久阶段窗口、每条原文与逻辑归属、最终正文;任何工具拒绝后不再发 provider 请求。结果与失败用 `make evidence` 原样保存,不补写或重试。 +- [ ] **Step 4: 独立 REPLAY 与交付裁决。** 两个实质阶段、数字/决定/归属语义、完整最终答复及停止边界全部 PASS 后提交阶段 6/7 产物供人工评审;人工通过后才进入 `policy/delivery.md` 的重新同步、独立代码复审、精确 HEAD CI 和智能体 PR 合入。任一 FAIL 保留真实证据与草稿,修订设计/资源后再单独评审。部署不在本计划范围;合入成功后按已授权的任务卡归档和安全工作区回收规则收尾。 diff --git a/docs/proposals/2026-10-14-fast-slow-u3-r04-reliable-first-checkpoint-design.md b/docs/proposals/2026-10-14-fast-slow-u3-r04-reliable-first-checkpoint-design.md new file mode 100644 index 0000000..009ad92 --- /dev/null +++ b/docs/proposals/2026-10-14-fast-slow-u3-r04-reliable-first-checkpoint-design.md @@ -0,0 +1,52 @@ +# U3 r0.4:可靠的首条阶段发现与失败收口 + +状态:**书面设计待人工评审**。用户于 2026-10-14(Asia/Shanghai)认可“先发布一条有来源的短事实,再按需检索,并补足异常诊断与失败展示”的设计方向;这只授权写本文,不授权实施代码、再次调用付费模型、降低验收条件或合入。关联 [U2/U3 计划](2026-10-13-fast-slow-u2-u3-plan.md)、[r0.3 设计](2026-10-13-fast-slow-u3-r03-evidence-backed-judgments-design.md)及[核验记录](../reports/2026-10-13-fast-slow-u2-u3-validation.md)。 + +## 目标、边界与实际失败 + +普通用户上传材料后,应先在对话中获得简短即时反馈,再在同一张工作卡上及时看到有原文依据的阶段发现、仍需核对的问题和最终整理结果;失败时应知道已保存了什么、接下来能做什么。沿用三栏工作台、任务卡、现有“材料记载/待核对的判断/待确认”层级和来源展开;不新增默认命令、脚本、模型过程页或确认按钮。 + +r0.3 固定 Dev `e31443a8ff1f099f9b2ef320c295b0ddcdaf503d`、Backend `5f834544b0b808cb16214a36725f2ae3cb90bfd4` 的唯一获批 A01 已执行,**质量 FAIL**。快通道及时给出正确的三份材料概览;慢通道先作三次无命中的 `knowledge_search`,第一次 `publish_work_checkpoint` 的参数停在 `"findings": `,没有一条可用阶段发现或最终整理正文。两次付费 run、四次 provider 调用共返回 37,134 Usage tokens;检查点拒绝后没有再发 provider 请求,隔离实例已停止。原件和逐调用、SQLite、SELF 收据在受限归档 `/home/weichen/.local/share/agent-system-evidence/fast-slow-u3-r03-a01-20261014/`,清单哈希在 `archive-manifest.json`。旧 r0.2 的语义 FAIL 和本次 r0.3 的执行 FAIL 分别保留,不相互改判。 + +只读重放确认三份附件确已入本次 SQLite 索引;实际“文件名+多个中文关键词”查询在 FTS 和整串 LIKE 兜底均零命中,单独文件名各命中一条。另用合成 SSE 证明:当前 Agent-Native 解析器在 `finish_reason=tool_calls` 却缺 `[DONE]` 时仍可能放行半截工具参数。**本次未保存服务商原始 SSE,不能断定该机制就是 A01 参数截断的来源。**本次生成的失败通知还在无可用整理结果时要求交付物选择;评测关闭了通知模型回合,因此不能声称浏览器实际发出了选择卡。 + +## 方案取舍 + +| 路线 | 能解决的事 | 不足 | 结论 | +|---|---|---|---| +| 原样再跑同一候选 | 检查一次是否偶发 | 已批准额度耗尽;即使通过,也不修复已证实的流终态和失败通知缺口 | 不选 | +| 工具调用出错后自动请求模型修补 | 可能挽救一次格式错误 | 增加付费回合和延迟,可能让来源错误绕过停止规则,违背本轮“无额外模型调用”边界 | 不选 | +| **首条短事实优先+材料范围内的检索兜底+流终态守卫+状态感知通知** | 同时改善阶段可见性、来源检索和失败诚实性;保持模型调用边界 | 涉及 Agent-Native 与 Backend 的跨组件验证,模型是否遵循顺序仍须真实样本判定 | 推荐 | + +## 用户可见行为 + +当已内联的材料含有可直接引用的实质事实,慢通道第一轮先尝试发布 **一条** `reading` 阶段 `material_record`:短句、原文连续引文、当前材料的 sourceId 与 blockIndex;先落到现有任务卡,再做可选检索和跨文档核对。它不是“已收到文件”或工作过程,也不得把日期、预算审批、分期归属等推断装成材料原句。第二条及之后只发布新增的、不同的核对结果;r0.3 的数字、归属、决定状态及来源校验原样保留。材料无可用原文时不强凑首条;模型未遵循顺序时如实记录,不能由宿主捏造一条发现来满足计数。 + +仅在已内联片段不足以完成当前工作时,慢通道调用现有 `knowledge_search`。材料子任务应先用**精确文件名**定位被截断的附件;如果模型仍把唯一附件文件名和关键词混成一个查询且常规检索无命中,宿主只在本任务已登记的附件集合中做一次同工具调用内的精确文件名兜底。兜底结果必须同时匹配 owner、project、附件 artifactId/ref 和本次输入版本;可从当前任务的解析快照证明版本,无法证明时就保持无命中。重名或多个匹配不猜,索引/原件版本不一致不返回旧内容。它不扩大通用知识检索的可见范围,兜底本身也不增加 provider 回合;返回内容仍按现有工具结果大小限制处理,检查点引用仍经原件与连续短引校验。首条检查点改为优先发布可能改变既有工具驱动回合的实际时序和总数,但不引入独立摘要、审核或自动修复模型;仍受每 run 的轮数、工具数、时间及 Usage 边界约束,必须在真实样本中逐调用核对。 + +任务失败时,工作卡保留已原子提交的旧阶段发现,并明确标注“整理未完成”和未完成范围;若从未保存阶段发现,就明确说没有可用整理结果。此时对话只给出简短恢复说明,**不**从不存在的“建议后续处理”生成交付物选择卡,也不使用默认四选项兜底。只有成功终态且有可用整理结论与实际建议时,才允许既有 `confirm_choice` 流程;成功但没有可用建议时请用户说明下一步目标,不伪造建议。通知和终轮兜底应读取宿主终态/成果可用性,不以“材料整理已结束”字面串代替成功判断。 + +## 组件职责与安全契约 + +**Agent-Native 流边界。** OpenAI 兼容 SSE 的工具调用在终态完整性得到证明前不进入执行层:需要受支持的正常结束原因、完整终止信号和每个工具调用的非空 ID/名称、合法且为对象的 JSON 参数。缺 `[DONE]`、参数不完整、流解码失败或结束原因表明截断时,返回**不可自动重试**的失败;已经返回的 Usage 仍纳入本次 run 和整批预算,后续 provider 请求被阻断。具体事件形状须在实施计划中与现有 `consume`/runner 的 Usage 累加契约一起固定,不得只丢弃 tool call 后把空正文误报为成功。普通无工具文本、合法多片工具调用与现有供应商兼容路径不能因守卫回退。仅保存诊断元数据(run ID、调用序号、结束原因、是否完整终止、参数长度/哈希与 JSON 有效性、Usage 是否存在),不记录原始 SSE、密钥或额外完整提示词。 + +**Backend 材料任务。** 在 `buildMaterialTaskFor` 的 r0.3 来源语义守卫之上,收敛首批发布指令,先一条短事实、再核对差异;材料配方从 v10 递增到 v11,防止旧完成任务以新规则复用。材料范围内的检索兜底沿 `runCtx.Retrieve`/工具授权链执行,不接受客户端指定 artifactId 作为权限证明。`publish_work_checkpoint` 的来源登记、原件 hash/解析版本、CAS 一次提交、幂等与更正历史保持不变。失败通知从持久终态和可用成果判断;无成果时不触发 `confirm_choice` 或其 stop 兜底,不静默重复派发整理任务。 + +**Frontend/UI。** 先复用现有卡片状态和失败文案、来源展开、刷新和文件预览;只有实际浏览器验证证明现有表述无法解释“部分成果已保存/没有成果”时,才在同一张卡上做最小文案调整,仍消费 `DESIGN.md` 与 UI 令牌、`@ui` 原语。异步卡片与对话仍各有单一状态所有者,按 seq 丢弃过期事件,不抢文件、输入焦点和阅读位置。没有新的页面、弹窗或模型内部日志。 + +Core 的可选 evidence 契约和 TaskSnapshot 持久字段不变;无数据库迁移、生产模型路由或预算放宽。流守卫若落在独立的 Agent-Native 仓,将在现有六个草稿 PR 之外增加该组件的 PR,并由 Backend 更新其固定 gitlink、Dev 再固定 Backend;不能只改 Backend 的嵌套检出后声明已交付。Documents 与 Dev 的记录也各按本仓任务分支提交、推送和组合验证;设计审批不等于合入授权或已通过模型验收。 + +## 失败恢复、验证与交付门槛 + +零付费先证明失败和正常路径: + +1. 合成流覆盖 `tool_calls` 有结束原因却无 `[DONE]`、有 `[DONE]` 却参数不完整、`length` 截断、合法多片参数、正常纯文本;逐例断言是否执行工具、错误是否不可自动重试、已返回 Usage 是否保留。不得把坏 JSON 自动补全后调用工具。 +2. SQLite/PostgreSQL 的材料检索覆盖本次三条“文件名+关键词”原查询、单文件名、重名、跨 owner/project、artifactId 不匹配和输入版本漂移;断言兜底只在本任务唯一附件内命中,同一次工具调用内完成且不泄露其他资料。 +3. 假流和真实 HTTP 覆盖首条有用 `reading` 检查点先于可选检索、第二批不同 `crosscheck` 发现、无实质原文时不凑数、非法来源/数字关系整批拒绝、旧成果保留。失败通知和终轮兜底均不得在无可用建议时发选择卡;成功且有建议仍走原确认链。 +4. 浏览器覆盖运行中阶段卡、失败零成果、失败有部分成果、成功交付、刷新和来源版本变化;默认界面只给用户行动所需信息,后台不抢阅读。执行组件测试、Dev `make test && make build`、独立代码复审及当前 HEAD 平台 CI,收据与 SHA 分别标 SELF/REPLAY/GATE。 + +真实模型质量仍使用同一 A01 合成材料:至少两个真正有用、终态前可感知的不同阶段成果,完整最终正文,六月计划与七月评审意见、50 万元预算及审批状态、总 12/第一期 8/接口依赖 3 项的关系与验收缺项均准确;无法证明接口项属于哪一期时标待确认。逐调用核对实际 run ID、工具数、返回 Usage 与停止规则。**新付费样本必须在本设计及实施计划分别获批、零付费候选固定后单独提交资源提案;不沿用 r0.3 已用完的授权。**任何质量失败或安全门禁失败,相关 PR 保持草稿,不合入或部署。 + +## 已知风险与待评审取舍 + +首条短事实能减小工具参数负担,但不能保证模型一定按顺序调用,亦不能证明本次 A01 的半截参数来自服务商而非流适配。要求完整流终态可能暴露某供应商的非标准结束行为;若发现这种事实,须先提出受限、可重放的等价终态定义,经评审后处理,不静默放宽守卫。材料精确文件名兜底可能取回比关键词检索更多的同附件文本,必须有既有结果大小与来源限制。若这些取舍不可接受,需要重新评估增加模型纠错回合或人工恢复的成本;当前版本不预授权两者。 diff --git a/docs/proposals/2026-10-14-fast-slow-u3-r04-reliable-first-checkpoint-implementation-plan.md b/docs/proposals/2026-10-14-fast-slow-u3-r04-reliable-first-checkpoint-implementation-plan.md new file mode 100644 index 0000000..23c4fa5 --- /dev/null +++ b/docs/proposals/2026-10-14-fast-slow-u3-r04-reliable-first-checkpoint-implementation-plan.md @@ -0,0 +1,203 @@ +# U3 r0.4 Reliable First Checkpoint Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 上传材料后先产生一条真实、有来源的阶段事实;材料检索、模型工具流和失败通知均按可复核边界工作,使慢通道能够逐步帮助普通用户而不展示内部过程。 + +**Architecture:** Agent-Native 在流终态阻断不完整工具调用并保留已返回 Usage;Backend 从本任务附件快照提供受限检索兜底,收敛材料任务的首批发布顺序,并以实际终态和可用建议决定是否发交付物选择卡。沿用 r0.3 的 TaskSnapshot、来源与语义守卫、同一张卡和现有 UI 原语。 + +**Tech Stack:** Go 1.27、SQLite/PostgreSQL、Agent-Native SSE/runtime、Backend runmgr/runtime、React/Vite/Playwright、Dev `make test`/`make build` 与 Gitea CI。 + +**Spec:** [已批准的 r0.4 书面设计](2026-10-14-fast-slow-u3-r04-reliable-first-checkpoint-design.md),Documents `7b21e6d495e99e15c458d608db47c517b2fdc599`;[r0.3 A01 失败记录](../reports/2026-10-13-fast-slow-u2-u3-validation.md)。本计划版本仍待阶段 5 人工批准;设计批准不授权代码或新付费模型请求。 + +## Global Constraints + +- 保留 r0.3 的来源原文连续短引、owner/project、artifact hash/解析版本、数字/分期/决定状态守卫和 CAS;旧结果不追溯改写。Core 候选契约、TaskSnapshot 持久字段、数据库表和生产模型路由不变。 +- 只沿现有三栏、同一张任务卡和“材料记载/待核对的判断/待确认”分层呈现;默认不展示模型调用、命令或原始 SSE。UI 变更先读 Dev `DESIGN.md`,只用 `@ui` 令牌与原语,事件按 seq 丢弃过期项。 +- 不增加独立摘要、审核或自动修复模型;自然工具回合仍受每 run 的轮数、工具数、300 秒及返回 Usage 边界约束。工具拒绝、缺 Usage 或额度耗尽后不得追加 provider 请求。新付费 A01 必须单独审批,不沿用 r0.3 的额度或标签。 +- 旧 r0.2 语义 FAIL 与 r0.3 执行 FAIL 保留。新 A01 仍须两个真正有用、终态前可感知的不同阶段成果和完整准确正文;来源字面匹配、单测、假流或绿色 CI 均不能代替这项真实模型质量出口。 +- Agent-Native、Backend、Documents、Dev 各在自己的任务分支提交;不要直接编辑 Backend 内嵌的 detached Agent-Native 后把本地文件当已交付。新 Agent-Native PR 是现有六个草稿 PR 的依赖,固定指针和合入顺序为 Agent-Native → Backend → Dev。 +- 启动/恢复、`make evidence`、`task-sync`、Review、当前 HEAD CI、阶段 6/7 人工结论和安全回收遵循 Dev `AGENTS.md` 与 `policy/delivery.md`。本计划只申请零付费实施与验证;不得从计划批准推导新模型资源或部署授权。 + +## Review Focus + +1. **看似正常的 `tool_calls` 结束原因但没有 `[DONE]`:** 不得执行任何工具,返回不可自动重试的失败,已返回 Usage 仍计入预算。Task 1 的 `TestParseSSEToolFinishWithoutDone` 和 `TestRunTerminalToolFailureKeepsUsage` 钉住。 +2. **同名附件或其他 owner/project 的知识命中:** 文件名兜底必须拒绝歧义和越界,不将其他人的材料带入当前任务。Task 2 的 `TestMaterialAttachmentFallbackScope` 钉住。 +3. **本次附件注册后原件被替换:** 同 artifactId 的旧解析快照和索引不得继续供模型读取。Task 2 的 `TestMaterialAttachmentFallbackVersionDrift` 钉住。 +4. **后台失败但旧阶段发现已保存:** 卡保留已验证发现并明确未完成,不出现“全部完成”或交付物选择卡。Task 4 的 `TestFailedMaterialNoticeKeepsPartialFindingsWithoutChoice` 钉住。 +5. **后台成功但没有实际建议项:** 不生成默认四选项,也不触发弱模型选择卡兜底;让用户自由说明下一步。Task 4 的 `TestMaterialNoticeWithoutSuggestionsDoesNotNudge` 钉住。 + +## 文件与交付顺序 + +| 单元 | 主要文件(均相对于对应组件仓根目录) | 单一职责与可独立验证的产物 | +|---|---|---| +| Agent-Native 流终态 | `runtime/llm/{types,sse,sse_test}.go`、`runtime/{loop,loop_test}.go` | 完整工具调用才可执行;错误保留 Usage、不可自动重试。单独 Agent-Native PR。 | +| Backend 材料检索 | `internal/runmgr/{runmgr,material_retrieve,material_retrieve_test}.go` | 仅本次材料子任务从唯一附件快照兜底,拒绝歧义、越权和版本漂移。 | +| Backend 首条发布 | `internal/runmgr/{material,material_test}.go`、`internal/runtime/{work_checkpoint,work_checkpoint_test}.go` | v11 配方和一条短事实优先;不伪造发现、不增加独立模型回合。 | +| Backend 失败交互 | `internal/runmgr/{runmgr,material_test}.go`、`internal/runtime/{verify,llm_direct,material_flow_test}.go` | 成功且有实际建议才发选择卡;失败和无建议不强制选择。 | +| Backend 评测与组合 | `internal/server/{work_checkpoint_eval_test,work_checkpoint_eval_budget_test}.go`、`eval/live/work_checkpoints_test.go`、`modules/frontend/e2e/work-checkpoints.playwright.ts`(从 Dev 根目录) | 诊断元数据、预算停止、实际 HTTP/浏览器和固定组合证据。 | + +以上单元紧扣同一材料整理链路,接口按表格顺序依赖;暂不拆成互不关联的新项目。UI 仅在实际浏览器证明现有失败文案不足时修改 Frontend/UI,届时在同一任务卡记录影响并重跑各自设计守护;不预设新增组件。 + +### Task 1: Agent-Native 完整工具终态与 Usage + +**Files:** Agent-Native `runtime/llm/types.go`、`runtime/llm/sse.go:53-180`、`runtime/llm/sse_test.go:65-155`、`runtime/loop.go:320-360`、`runtime/loop_test.go`。 + +**Interfaces:** 扩展内部 `llm.Event` 的 `StreamComplete bool`;对不完整工具调用发一个含 `Usage`、`StopReason=StopError`、非空 `Err` 且无 `ToolCalls` 的 `KindMessageEnd`。`runtime.Run` 计入该终态已返回 Usage 后以不可重试错误结束;Backend 既有逐调用 wrapper 仍能从 `KindMessageEnd` 计量。普通文本和合法完整工具调用保持原行为。 + +- [ ] **Step 1: 建 Agent-Native 隔离分支并写 RED。** 从其权威 `origin/main` 建 `feature/fast-slow-u3-r04-stream-guard`(不要在 Backend 的 detached gitlink 上写);在 `sse_test.go` 增加两例:`tool_calls` 结束但无 `[DONE]`,及有 `[DONE]` 但参数停在 `"findings": `。使用与既有 `TestParseSSEToolCallIncrements` 相同的 SSE 输入写法,断言: + +```go +if end.Kind != KindMessageEnd || end.Message.StopReason != StopError || + len(end.Message.ToolCalls) != 0 || end.Err == nil || end.Retryable || + end.Usage.TotalTokens != 17 { + t.Fatalf("unsafe incomplete terminal: %#v", end) +} +``` + +同时在 `loop_test.go` 用假 stream 发上述错误终态(Usage 17),工具处理器计数必须为 0,`res.Err != nil`、`res.Usage.TotalTokens == 17`、没有第二次 provider 调用。 + +- [ ] **Step 2: 重放 RED。** 在 Agent-Native 根运行 `GOWORK=off go test ./runtime/llm ./runtime -run 'TestParseSSEToolFinishWithoutDone|TestParseSSEInvalidToolArguments|TestRunTerminalToolFailureKeepsUsage' -count=1`;记录预期失败的原始 SELF 收据,不能把旧 `TestParseSSEUncleanEndDropsToolCall` 当作已覆盖 `finish_reason=tool_calls` 的情况。 + +- [ ] **Step 3: 最小 GREEN。** 在 `emitParsed` 拼完工具片段后要求 `sawDone`、受支持结束原因、每个非空 ID/名称及可解为 JSON 对象的参数;不完整时构造错误终态,不修补参数。实现形状: + +```go +func terminalToolFailure(content string, usage Usage, reason string, complete bool, why string) Event { + err := &StreamError{Message: why, Retryable: false} + return Event{Kind: KindMessageEnd, Message: Message{Role: RoleAssistant, + Content: content, StopReason: StopError}, Usage: usage, + FinishReason: reason, StreamComplete: complete, Err: err, + ErrorMessage: why, Retryable: false} +} +``` + +`runtime.Run` 在现有 Usage 累加之后、工具执行与 stop-hook 之前,遇 `end.Err != nil` 即返回 `Result{StopReason: llm.StopError, Usage: usage, Err: end.Err, Retryable: false}`;更新与新语义相冲突的旧 `length`/断流测试预期。`KindToolCallDelta` 可以作为过程事件,但不得据此执行工具。 + +- [ ] **Step 4: 跑 GREEN 与正常回归。** 运行定向测试、`GOWORK=off go vet ./...`、`GOWORK=off go test -count=1 ./...`,另断言合法多片工具调用和无工具正常文本仍 PASS。用 `make evidence` 保存完整 SELF 输出;审阅返回 Usage 的终态错误不会被上层误报为成功,也不会触发 LLM 自动重试。 + +- [ ] **Step 5: 提交并开草稿 PR。** 仅提交本任务 Agent-Native 文件;英文祈使句主题、`Why/Evidence/Impact/Refs` body。推送该组件分支并记录准确 SHA 和草稿 PR,不修改其 `main` 或发布版本。 + +### Task 2: Backend 只读、限域的附件检索兜底 + +**Files:** Backend `internal/runmgr/runmgr.go:966-1018`;新建 `internal/runmgr/material_retrieve.go`、`internal/runmgr/material_retrieve_test.go`;必要时复用 `task_projection.go:358-370` 的原件 hash 检查。 + +**Interfaces:** `func (c *runCtx) materialAttachmentFallback(q core.RetrievalQuery) ([]core.RetrievalHit, error)` 仅在 `c.subAgent == materialSubagentID`、正常检索成功但零命中时调用;消耗 `c.attachments`(`startBackgroundSlot` 已从受信 `bgSpawnOpts.documents` 固定),不得从客户端 query 中取 artifactId 作权限。返回的每条 hit 的 `Doc.Ref` 必须等于当前任务唯一附件 artifactId。 + +- [ ] **Step 1: 写 RED 与版本负例。** 用 `newCardSpawnEnv`、`materialDocs()` 及真实 SQLite knowledge/artifact fixture,让本次 A01 的 `需求.txt 上线 预算 验收 接口` 在当前常规路径零命中,而兜底应找到当前任务的 `需求.txt`;重复文件名、另一 owner/project、同 ID 原件内容修改和 `Task.Inputs` 中 hash/解析版本不匹配时,兜底必须零命中或返回不含旧正文的明确错误。核心断言: + +```go +if len(got) != 1 || got[0].Doc.Ref != string(docs[0].Meta.Origin.ArtifactID) || + got[0].Doc.ProjectID != rc.run.ProjectID || + got[0].Doc.Owner != rc.run.Owner { + t.Fatal("material fallback crossed the task boundary") +} +``` + +- [ ] **Step 2: 重放 RED。** `go test ./internal/runmgr -run 'TestMaterialAttachmentFallbackScope|TestMaterialAttachmentFallbackVersionDrift|TestMaterialAttachmentFallbackQuery' -count=1`,记录每例失败原因,不用未授权共享知识充当正例。 + +- [ ] **Step 3: 实现受限兜底。** 从 `q.Text` 识别**恰好一个**当前附件文件名;核对该 origin artifactId/hash/解析版本与 `card.Task.Inputs`;经 `GetArtifact(owner,id)`、`ArtifactContent(owner,id)` 和项目 ID/sha256 再读确认后,从本次 `ParsedDocument` 快照构造有限 `RetrievalHit`(`Source=upload`、`Ref=artifactId`)。若查询有其余词,优先返回包含这些词的有界原文片段;仅文件名时返回尚未内联的部分或当前文档的有限原文,最多遵守现有 `MaxToolResultChars`。在 `runCtx.Retrieve` 的正常结果为零、错误为 nil 时调用: + +```go +if err == nil && len(hits) == 0 && c.subAgent == materialSubagentID { + return c.materialAttachmentFallback(q) +} +return hits, err +``` + +不得改变通用检索的 FTS/LIKE、共享授权或其他子代理结果;重名和版本证明失败时 fail closed,不猜测最相近文件。 + +- [ ] **Step 4: 跑 GREEN、权限与存储回归。** 运行上述三例、`go test ./internal/runmgr ./internal/store -count=1`,再在任务自有 PostgreSQL 上重放材料兜底与权限负例;相同文档多段、中文关键词和超长结果必须保持有限输出。记录被测 Backend SHA、数据库实例及 SELF 收据。 + +- [ ] **Step 5: 提交 Backend 检索单元。** 只提交本任务文件并核对 `git diff --check`;此时仍未改变阶段提示或失败交互,单元测试可独立审阅。 + +### Task 3: 首条有来源事实优先,配方 v11 + +**Files:** Backend `internal/runmgr/material.go:88-132,286-303`、`internal/runmgr/material_sources.go:144`、`internal/runmgr/material_test.go`、`internal/runtime/work_checkpoint_test.go`。 + +**Interfaces:** 不改 `WorkCheckpointCandidate` 或 `publish_work_checkpoint` JSON 契约。`buildMaterialTaskFor` 在已有实质内联片段时要求首个 `reading` 工具调用只含一条 `material_record`、当前 `sourceId/blockIndex/quote`;第二阶段沿 r0.3 evidence 守卫产生新的差异/待确认。材料输入修订从 `material-recipe.v10` 改为 `material-recipe.v11`,旧完成卡只读保留。 + +- [ ] **Step 1: 写 RED。** `material_test.go` 用三份完整内联 A01 和一份空/不可读材料分别断言:前者任务指令明确“首条一项短事实、先于检索”;后者不要求凑发现。`work_checkpoint_test.go` 的假流先发布原文事实、再可选检索、后发布不同的 crosscheck 条目,核对运行中卡片 seq、两批不同 findingKey/来源及最终正文;再用无效 sourceId 断言整批零提交。关键检查形状: + +```go +if first.Task.CheckpointSeq != 1 || second.Task.CheckpointSeq != 2 || + first.Task.Findings[0].Kind != "material_record" || + second.Task.Findings[1].Kind != "needs_user" { + t.Fatal("reading and crosscheck did not remain distinct") +} +``` + +- [ ] **Step 2: 重放 RED。** `go test ./internal/runmgr ./internal/runtime -run 'TestMaterialFirstFindingBeforeOptionalSearch|TestMaterialNoForcedFindingWithoutSource|TestWorkCheckpointTwoUsefulPhases' -count=1`。如果假流只证明宿主接受顺序、无法证明真实模型会遵循,明确在任务卡标“机制 PASS / 模型 NOT_RUN”。 + +- [ ] **Step 3: 最小 GREEN。** 把材料任务的首条指令放在长篇最终正文要求之前,避免模型先写完整计划或做已提供材料的冗余检索;提示只要求一条已提供原文短引,不复制全部来源索引或增加新工具。实现形状: + +```go +task = "先从已提供的实质原文发布一条 reading 材料记载:只写一项,附当前 sourceId、blockIndex 和连续短引;发布成功后再按需检索、核对差异和形成完整正文。\n\n" + task +``` + +空文本或只有处理警告时不追加强制首条要求;将配方常量更新到 v11。Host 不凭规则自行合成内容,模型未按顺序调用时如实失败或继续原有受控流程。 + +- [ ] **Step 4: 跑 GREEN 与预算体积检查。** 运行前述定向测试和 Backend `go test ./... -count=1`;离线比对 v10/v11 任务提示及工具 schema UTF-8 字节和粗估 token 增量,不能将粗估当真实 Usage。补测旧 r0.3 数字、决定、来源及 CAS 回归。 + +- [ ] **Step 5: 提交 Backend 首条发布单元。** 仅提交提示、配方与相关测试,记录“优先顺序不等于模型质量保证”的限制。 + +### Task 4: 失败通知按终态与建议可用性分流 + +**Files:** Backend `internal/runmgr/runmgr.go:2254-2400`、`internal/runmgr/material_test.go:198-230`、`internal/runtime/verify.go:34-44`、`internal/runtime/llm_direct.go:148-161,634-648`、`internal/runtime/material_flow_test.go:146-225`。 + +**Interfaces:** `materialChoiceReady(bgNotice, core.ActivityCard) bool` 仅在 `material && Status==completed`、任务卡确有可用最终正文且正文“建议后续处理”包含 3–4 条带依据的真实选项时为真。只有该路径加入新的内部 `materialChoiceReadyMarker` 并开启 stop 兜底;失败、取消、零成果及成功但无建议时只投递状态/恢复说明,保留已经持久化的旧发现,不出现默认四选项。 + +- [ ] **Step 1: 写 RED。** 扩展 `TestMaterialNoticeContract`:失败零成果、失败但卡已有一条合法 finding、取消、完成但无建议、完成且有 3 条建议。前四种系统提醒均不得含 `confirm_choice`/ready marker;第五种仍含两者。`material_flow_test.go` 加失败及无建议通知回合,断言不会产生 HITL 或 `materialNoticeBackstopReason`,成功有建议仍生成选择卡。 + +- [ ] **Step 2: 重放 RED。** `go test ./internal/runmgr ./internal/runtime -run 'TestMaterialNoticeContract|TestFailedMaterialNoticeKeepsPartialFindingsWithoutChoice|TestMaterialNoticeWithoutSuggestionsDoesNotNudge|TestMaterialNoticeChoiceChain' -count=1`;保存失败原件。 + +- [ ] **Step 3: 最小 GREEN。** 从 `bgNotice.payload.Status`、任务卡 `Task.OutcomeCompleteness`/结果和正文里的实际建议列表判断选择资格;只接受“建议后续处理”标题下 3–4 个非空选项,每项有材料依据文本。保守解析失败时提示用户自行说明目标,不生成虚构选项。`verify.go` 用与“已结束”不同的内部 ready marker;`llm_direct.go` 只按该 marker 设置 `st.materialNotice`。实现门槛示意: + +```go +func materialChoiceReady(n bgNotice, card core.ActivityCard) bool { + return n.material && n.payload.Status == core.RunCompleted && + card.Task != nil && card.Task.OutcomeCompleteness == "complete" && + len(parseMaterialSuggestions(n.payload.Text)) >= 3 +} +``` + +若当前 Task 的实际完成枚举或结果落点不同,以 `core.TaskSnapshot` 的现有定义修正该判断并在同一测试证明;不要硬编码“状态:completed”字符串。通知正文和终轮兜底保持一致,失败时不再要求 `delegate_task` 基于不存在的建议启动工作。 + +- [ ] **Step 4: 跑 GREEN。** 重跑定向测试、`go test ./internal/runmgr ./internal/runtime ./internal/cardsvc -count=1` 和正常完成链;检查旧阶段发现/失败终态的持久事件没有被新通知写成成功。所有结果由 `make evidence` 写入任务卡。 + +- [ ] **Step 5: 提交 Backend 失败交互单元。** 只提交相关文件和测试,通知文字使用普通用户能理解的状态,不输出 tool/JSON/Usage 细节。 + +### Task 5: 评测诊断与停止规则零付费重放 + +**Files:** Backend `internal/server/work_checkpoint_eval_test.go:347-455`、`internal/server/work_checkpoint_eval_budget_test.go:201-300`、`eval/live/work_checkpoints_test.go:146-295`;Agent-Native `runtime/llm/types.go` 提供 Task 1 的 `StreamComplete`。 + +**Interfaces:** 评测逐调用记录增加 `finishReason`、`streamComplete`、`toolArgumentBytes`、`toolArgumentsValid` 和 `usagePresent`;原有 run ID、调用数、reported Usage、输出上限保留。诊断字段不含 API key 或原始 SSE;`KindMessageEnd` 的错误终态仍计入 Usage,任一检查点拒绝或缺 Usage 后零后继 provider 请求。 + +- [ ] **Step 1: 写 RED。** 用本地 `httptest` SSE 服务分别发送“合法工具调用”“有 `tool_calls`、已返回 Usage、缺 `[DONE]`”“有 `[DONE]` 但参数半截”“无 Usage”四种结果;每次从全新隔离 SQLite 和 0 个既有 run 开始。断言坏结果后第二次请求没有到达服务端、记录 Usage 如实、`toolArgumentsValid=false`、结果卡为失败。不得调用外部模型。 + +- [ ] **Step 2: 重放 RED。** 在 Backend 根运行 `go test -tags live ./internal/server -run 'TestEvaluationStopsAfterMalformedToolTerminal|TestEvaluationKeepsUsageOnTerminalFailure' -count=1`;在 `eval/live` 用 `go test -tags live ./eval/live -run 'TestFollowupA01KeepsItsOwnOneShotResult|TestPersistedRunningCheckpointMaxDoesNotMissShortWindow' -count=1` 保留既有零付费结果名与窗口回归。`TestWorkCheckpoints` 必须保持 `NOT_RUN`,此步不提供模型实例或路由。 + +- [ ] **Step 3: 最小 GREEN。** 从 Task 1 的终态字段和每个 `ToolCall.Arguments` 的 `json.Valid` 生成诊断布尔值;错误终态的 `Usage` 必须先进入 `reportedTokens` 与 provider record,再向 runner 发出非重试失败。评测 wrapper 增加批次级 `terminalFault`,遇此错误后与 `checkpointRejected`/`usageUnknown` 一样拒绝所有后继 provider 请求;即使直接再次调用 stream,也只返回本地错误。不要把失败伪装成正常 `message_end` 或用下一轮模型修补参数。 + +- [ ] **Step 4: 跑 GREEN 与上限。** 运行上述 live-tag 假 provider 集及 `go test ./internal/server -count=1`;`eval/live` 只用上述 `-tags live -run` 选择零付费用例,避免无 manifest 时误执行 `TestWorkCheckpoints`。核对每 run 30,000 配置、8轮/12工具/300秒、单次输出4,000、整批返回 Usage 停止目标的既有逻辑未变。对失败和正常路径分别保存 SELF 收据。 + +- [ ] **Step 5: 提交 Backend 评测单元。** 记录新诊断只证明机制,不能把旧 r0.3 A01 或未运行的 r0.4 模型质量改判。 + +### Task 6: 固定组合、真实浏览器、复审与资源出口 + +**Files:** Dev `modules/backend`、`modules/documents` gitlink、`.task/feature-fast-slow-u2-u3-plan.md`、`PROJECT.md`;Backend `deps/agent-native` gitlink;视需要 Frontend `e2e/work-checkpoints.playwright.ts`(若 UI 确需文字调整,再列明 Frontend/UI 文件和设计守护)。 + +**Interfaces:** 对外的 TaskSnapshot、卡片事件 seq 和来源跳转保持现有契约。各组件分支自己的 commit/PR 是唯一交付对象;Dev 组合 pin 明确 Core、Backend、Frontend、UI、Documents、嵌套 Agent-Native 的 SHA。 + +- [ ] **Step 1: 固定依赖与 RED/GREEN 组合。** 先把 Agent-Native 草稿 PR 的准确 SHA 固定进 Backend gitlink,在 Backend 跑 `GOWORK=off go test ./...`;提交/推送 Backend。Dev 再固定 Backend 与已批准计划 Documents SHA,跑 `RECEIPT=/tmp/fast-slow-u3-r04-20261014-combo-implementation.json CMD='make test && make build' make evidence`(该路径必须尚不存在;若已占用,选新的带序号绝对路径,绝不覆盖),导入原始收据并提交/推送 Dev gitlink。任何 pin 漂移、活动 Git 锁或其他工作区未保存内容均停止,不强删锁或覆盖。 + +- [ ] **Step 2: PG 与 HTTP/浏览器。** 启动**本任务独有** PostgreSQL 和 HTTP 假流实例;manifest 记录 PID、二进制 SHA、Dev/Backend/Agent-Native SHA 与数据目录,测试前验证绑定。PG 重放 runmgr/cardsvc/store;`PLAYWRIGHT_BASE_URL` 指向受验 manifest 的本地地址后运行 `e2e/work-checkpoints.playwright.ts`,覆盖两次 running 阶段、零成果失败、部分成果失败、成功建议选择、刷新和来源版本变化、后台不抢文件/焦点。只停止任务自有进程并保留 trace。 + +- [ ] **Step 3: 独立 Review 与正式固定。** 在请求审核前、验收前及合入前分别按 Dev `AGENTS.md` 用 `CMD='make task-sync' make evidence` 同步最新权威 main;同步改变 base/head 或组件时按影响重跑组合、PG、浏览器和代码复审。独立审阅者对 Agent-Native 流边界、检索权限/版本、成功/失败通知、预算停止、UI 状态投影和计划范围作最小重放;不能把作者 SELF 升格为 REPLAY。Dev 当前 HEAD 的 `test`、`test-pg`、`svc-realcombo` 与 Agent-Native/Backend/适用 UI 的必需 CI 必须核对实际 SHA、runner 和所有步骤。 + +- [ ] **Step 4: 阶段 6 与模型资源分开审阅。** 将零付费候选、独立 Review、精确 HEAD CI 和实际浏览器结果交用户确认阶段 6;只有候选固定且用户另行批准一次性资源提案后,才能用原 A01 材料、受保护路由、全新 0-run 数据目录和独立输出名运行真实模型。提案必须列明最多付费 run、单 run token/轮/工具/时间、批次返回 Usage 目标、输出 cap、在途超额风险、停止规则及不自动重试;**本计划不预填或授权新的用量数字**。 + +- [ ] **Step 5: 依据真实结果决策。** 只读重放 SQLite、逐调用 Usage、运行中卡片窗口、每条短引及逻辑归属、完整正文;任何 FAIL 保留所有相关 PR 草稿,不自动重跑或合入。质量 PASS 后仍须阶段 7 人工验收,随后按 Dev 策略完成 Agent-Native → Backend → 其他组件 → Dev 的独立 Review、准确 HEAD CI、串行 PR 合入、合入后内容核验、任务卡闭合和安全回收;未获阶段结论时写 NOT_RUN,不能提前标完成。 + +## 计划自检与停止条件 + +每个实现单元的 RED、GREEN、独立审阅、失败原件和版本都进入同一 Dev 任务主记录;Documents 设计/计划仅作引用,不复制第二份状态。局部代码修复若只改变等价实现,在已批准阶段内按影响补测;若改变工具公开契约、权限、模型调用数/预算、阶段验收条件或组件职责,回到设计/计划人工评审。真实模型样本一旦缺 Usage、检查点拒绝、预算停、输出不完整或语义错误,停止后续请求,保留原件并报告,不通过“再试一次”抹平失败。计划获批后才开始 Task 1;目前 Task 1–6 均 **NOT_RUN**。 diff --git a/docs/proposals/2026-10-14-fast-slow-u3-r05-terminal-evidence-and-fast-feedback-design.md b/docs/proposals/2026-10-14-fast-slow-u3-r05-terminal-evidence-and-fast-feedback-design.md new file mode 100644 index 0000000..5079430 --- /dev/null +++ b/docs/proposals/2026-10-14-fast-slow-u3-r05-terminal-evidence-and-fast-feedback-design.md @@ -0,0 +1,61 @@ +# U3 r0.5:安全终态证据与可信快通道反馈 + +状态:**书面设计已获人工评审通过**。批准对象为 Documents `0d473fe866703eab32f715ddf12d1b25913b822b`、Dev `c7ff687c1de6911b43163433bc19e4e55df24eed`;用户于 2026-10-14(Asia/Shanghai)答复“批准设计,继续实施计划”。此前用户认可的方向是“保持不完整工具流的安全拒绝,增加无原始内容或凭据的终态/传输诊断与零付费重放;快通道数字和分期归属缺少依据时标待确认”。这两次答复只授权写设计及详细实施计划,**不授权产品代码、付费模型请求、合入或部署**。负责人 weichen;实施候选与验收主记录为 Dev `feature/fast-slow-u2-u3-plan` 任务卡,历史结果见[核验报告](../reports/2026-10-13-fast-slow-u2-u3-validation.md),前版见 [r0.4 设计](2026-10-14-fast-slow-u3-r04-reliable-first-checkpoint-design.md)和[实施计划](2026-10-14-fast-slow-u3-r04-reliable-first-checkpoint-implementation-plan.md)。 + +## 目标与已知事实 + +用户上传材料后应立即知道“已收到、正在做什么、何处看结果”;阶段发现和最终整理仍由同一张工作卡逐级呈现,并且数量、分期、审批状态与来源一致。非专业用户默认只看简短状态、有用发现和待确认问题,不看 SSE、脚本或 token 细节。沿用左侧上下文、中间对话、右侧工作/文件,不增加页面或独立模型回合。 + +r0.4 唯一获批 A01 固定在 Dev `d38dbdc396b7b8c6a9e34142ea8e6018abbed1fc`、Backend `37e2cb7adb6202902345d38bc591e12be7aa79c0`、Agent-Native `a5e3c7c817166865d75b764337b15ca7d505d131`。原始样本和独立只读评估(原件路径见[核验报告](../reports/2026-10-13-fast-slow-u2-u3-validation.md))确认:两个付费 run、三次调用、29,062 已返回 Usage tokens;工具回合有 `finishReason=tool_calls`、合法 JSON 参数片段和 Usage,但解析器未观察到 `[DONE]`,所以没有工具执行、运行中检查点或最终整理正文。三次终态的 `streamComplete` 均为 false,前两次纯文本仍被当作正常结束。**没有保存原始 SSE,无法区分服务端未发结束标记、连接中途结束或适配器遗漏;不能把“JSON 可解析”当成完整工具调用的证明。** + +快通道问题中的“剩余 4 项(含 3 项依赖接口)”也没有材料依据:原文只给总 12、第一期 8、总量中接口依赖 3,未给依赖项与分期的交集。问号不撤销括号内的肯定关系。r0.3 的结构化检查点守卫没有覆盖前台自由文本;r0.4 `materialStartedNote` 仍要求模型即时给“初步判断、需要澄清的问题”。这条未经来源校验的可见路径必须收敛。 + +原候选还漏记失败子 run 的 7,148 tokens。Backend `0637547cc5640d51e2d67b65ab07b184269f8371` 已在 r0.4 授权诊断范围内完成零付费修复并通过独立复审(原件 `/tmp/fast-slow-u3-r04-20261014/usage-fix-final-review.md`)及准确 HEAD CI;该修复晚于样本,不改写原结果,也不证明流终态或快通道质量已通过。八个相关 PR 仍是草稿,r0.4 质量出口为 **FAIL**。 + +## 方案取舍 + +| 路线 | 收益 | 代价与风险 | 取舍 | +|---|---|---|---| +| 仅修改提示词,要求模型“谨慎” | 改动小 | 前台自由文本已出现无依据归属;提示不能构成发布门禁,也不能解释缺 `[DONE]` | 不选 | +| **保留严格工具门禁,补不含内容的终态诊断;自动材料入口用宿主确认反馈,来源判断进任务卡** | 不执行不完整工具;即时反馈不依赖模型推断;零新增模型调用 | 暂不使缺 `[DONE]` 的工具请求成功;复杂材料的首条实质发现仍取决于慢通道 | **推荐** | +| 将“干净 EOF+结束原因+合法 JSON”直接视作 `[DONE]`,或自动让模型补发工具参数 | 可能减少一次失败 | 缺失尾帧也可能是截断;无本次原始流可证等价,补发增加付费回合并违反停止条件 | 不选;未来如有证据须另审 | + +本修订先补“为什么终止”和“哪些话可以立即显示”的可靠边界。它不是宣布供应商终态已兼容,也不以更宽松的模型质量标准换取通过。 + +## 用户可见行为与快通道边界 + +系统**已确定性启动或复用**材料整理任务时,立即由宿主在原对话给一条短确认,并保持同一张任务卡:例如“已收到 3 份材料,正在核对;有依据的发现会陆续出现在‘材料整理’中,尚不能确认的关系会明确标注”。材料数量、文件名及任务状态只能来自当前授权的附件快照与任务卡,不能由模型猜测。任务排队、已有活动任务、失败启动和已完成复用分别读实际状态,不把“已启动”发给启动失败的任务,不重派同一批材料。 + +对于这个**自动材料入口的首个可见快通道回复**,模型自由文本不作为已核对的材料结论直接流给用户。若要显示实质数量、分期、决定或跨文件关系,必须走已有来源、原句、范围和归属校验后成为 `material_record`/`needs_user` 等阶段条目;在此之前,只给宿主确认与“待确认”状态。样本中的正确表达是“总量、首期和接口依赖数量分别有记载;依赖项属于哪一期待确认”,不能写“剩余 4 项含 3 项依赖接口”。不凭单独的问号、免责声明或“可能”放行前面的肯定关系。这个选择减少即时推断,但用户会更早得到真实进度,稍后通过右侧卡片查看带来源的内容。 + +方案优先覆盖 **系统预启动材料整理的首回合**:无文字上传及达到材料阈值的文字+材料。后者原用户工作重点已由 `buildMaterialTaskFor` 带给慢通道;短确认应说明稍后将结合该重点汇报,而非假称当场已经回答。文字+小材料仍走普通对话与既有 stop 兜底,不把它伪装成本方案已验收的路径;实施计划须列出这一路径的风险与回归,并评估是否需要后续独立范围。原始模型候选若被抑制,不得进入用户可见事件、会话持久转录或后续模型上下文。实现时若不能保证这一点,选择直接跳过该首回合模型请求,由宿主正常落一条确认消息;不得先流出不可信片段再撤回。省略首回合模型请求是少一次请求,不是新增模型摘要或审核调用;对预算和 A01 基线的影响仍须如实记录。 + +后台成果不因快通道收敛而降格:首条仍须真实、短、有连续原文来源的 `reading` 事实,第二批是不同且有用的 `crosscheck` 发现,最终有准确完整正文。未验证阶段不得显示虚构的进度百分比、ETA 或“全部完成”;失败沿用 r0.4 的零成果/部分成果分流,不生成无依据的交付物选择卡。 + +## 终态诊断与安全契约 + +Agent-Native 是 SSE 读取与工具执行边界的所有者。保留现有规则:工具调用只有在受支持的完成原因、完整终止标记及 ID/名称/对象 JSON 参数全部满足时才交给执行器;缺 `[DONE]`、读取错误、取消或参数不完整时继续安全失败,已返回的 Usage 仍计入预算,不因失败自动补发请求。文本回合当前在缺 `[DONE]` 时可正常结束的事实需要单列诊断,不能借此推断工具回合也安全。Backend 的受控评测 wrapper 仍在终态故障、检查点拒绝、缺 Usage 或额度边界后阻断后继 provider 请求;不改模型路由、轮数、工具数、时间与输出上限。 + +新增**有界、无内容**的每次调用终态收据,与既有 `runId`、调用序号和逐调用 Usage 关联。字段建议为:HTTP 状态与可判定的响应完成类型、读结果(正常 EOF/读错误/取消)、`[DONE]` 是否观察到、最后一个 SSE 事件是否完整 JSON、已观察到的 `finishReason`、是否含工具片段及片段数量、参数总字节数与 JSON 对象有效性、Usage 是否出现与 token 数、最终是否放行工具。只记录枚举、计数、布尔值及受限错误类别;**不保存原始 SSE、prompt、正文、推理文本、工具名/参数、API key 或请求头**。不可从无内容收据反推出尾帧原文;读取错误细节需脱敏并限长,日志与归档采用任务自有权限。模型失败时诊断与失败状态同一调用保存,不让观测失败触发重试。 + +`bufio.Scanner.Err()==nil` 的 EOF 仅说明读取器没有报告错误,不证明供应商发送了所有预期事件;完整 `finishReason`、Usage 和合法 JSON 也不能独立补足 `[DONE]`。诊断先回答“解析器看到了什么、底层读取如何结束、工具是否被执行”。若零付费重放显示解析器漏认合法终止形式,或未来经单独批准的受限真实试验取得足够的供应商终态证据,才提出**按路由受限的等价终态定义**及正反例,回到方案评审;r0.5 本身不放宽工具门禁。 + +Backend 只消费 Agent-Native 的终态分类和 Usage,保留 run/card/source 版本权威;Frontend/UI 只显示“整理中/未完成/待确认”等用户可行动状态,不展示诊断字段。核心 `TaskSnapshot`、公开工具契约、数据库结构、权限和服务配置保持现有版本。若需持久诊断,优先落在已受控的评测记录/运行诊断面,实施计划须说明清理期限与是否需要新增内部字段;不得以完整原始流作为默认遥测。 + +## 零付费验证与阶段出口 + +1. **传输/解析重放。** 用 `httptest` 和人工 SSE 帧覆盖有 `[DONE]`、正常 EOF 却无 `[DONE]`、半个 JSON 帧、结束原因与工具片段不一致、读错误、取消、缺 Usage、纯文本缺终止标记。逐一断言收据分类、工具执行次数、返回及持久 Usage、后继 provider 请求数。特别验证“干净 EOF+合法工具 JSON”仍为 **0 次工具执行**。 +2. **快通道投影。** 对附件仅发送、文字+长材料预启动、排队、活动任务复用、启动失败、已完成复用分别用真实 HTTP/浏览器核对首条确认、卡片与会话转录。以 A01 的 12/8/3 和跨文档改写变体作反例,断言首回合任何可见文字均不包含未经证明的交集关系,明确标“待确认”;原用户文字仍在慢任务重点中,普通文字+小材料路径没有被误改。 +3. **渐进成果与失败。** 零付费假流核对第一条 `reading`、第二批不同 `crosscheck`、终态完整正文、零成果/部分成果失败,以及刷新、来源跳转和后台不抢阅读焦点;不能把宿主确认算成一个阶段发现。SQLite 与隔离 PostgreSQL 分别核对事件 seq、任务快照及已返回 Usage。 +4. **组合与审阅。** Agent-Native、Backend、适用的 Frontend/UI 和 Dev 固定准确 SHA 后做组件测试、`make test && make build`、真实浏览器、独立最小重放与准确 HEAD 平台 CI。源码、收据和独立审阅按 `SELF`/`REPLAY`/`GATE` 标识;没有运行的项目写 `NOT_RUN`。界面若确需修改,先读 Dev `DESIGN.md` 并只用 `@ui` 令牌、动效与折叠原语。 + +正式 A01 质量标准仍是至少两条**真正有用且终态前可感知**的不同阶段成果、完整准确最终正文、原文来源和数字/决定状态正确,逐调用核对实际 run ID、Usage、工具数和停止规则。宿主确认不计入两条;零付费重放只证明机制,不证明模型会完成任务。r0.4 的一次性资源已用尽;新的真实模型调用必须在本设计与详细实施计划分别获批、零付费候选固定和单独资源提案获批后才可运行。任何安全、语义或质量 FAIL 保持八个 PR 草稿,不进入阶段 7、合入或部署。 + +## 风险与待评审决定 + +- **诊断不等于修复供应商行为。** 当前原件缺原始 SSE,因此只能证明“未观察到 `[DONE]`”;本版保守失败可能继续导致零阶段成果。是否再做受限供应商探针或提出等价终态,必须依据新证据和单独资源审批,不在本设计中偷渡。 +- **快通道即时信息减少。** 宿主先确认真实状态,实质判断等来源核验后再上卡。若用户更看重未经核实的即时概览,需要另行决定风险,而不能把模型自由文本称为已核对结论。 +- **首回合跳过/隔离的实现选择。** 两者都不得让候选文本先进入事件或持久转录;实施计划应以最小零付费探针比较对已有会话、通知、计费、重入和用户文字重点的影响,推荐更小且可证明的路径。若探针显示需要改变公开消息契约或异步所有者,返回设计评审。 +- **范围边界。** 本版优先关闭预启动材料入口的快通道错误;普通文字+小附件问答仍需单列验证,不从 A01 推断全路径正确。若评审要求覆盖该路径,应在计划前扩展设计和验收矩阵。 + +请人工重点审阅:是否接受“先可信确认、实质判断稍后上卡”的体验取舍;是否同意缺 `[DONE]` 的工具流继续严格拒绝,并先以无内容诊断求证;以及短文字+小材料是否纳入同一增量。书面设计批准后才写详细实施计划,批准计划后才进行零付费代码实施;真实模型资源仍单独批准。 diff --git a/docs/proposals/2026-10-14-fast-slow-u3-r05-terminal-evidence-and-fast-feedback-implementation-plan.md b/docs/proposals/2026-10-14-fast-slow-u3-r05-terminal-evidence-and-fast-feedback-implementation-plan.md new file mode 100644 index 0000000..74f3026 --- /dev/null +++ b/docs/proposals/2026-10-14-fast-slow-u3-r05-terminal-evidence-and-fast-feedback-implementation-plan.md @@ -0,0 +1,206 @@ +# U3 r0.5 Safe Terminal Evidence and Fast Feedback Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 上传材料后立即给出可信的收件与工作状态,让实质结论经来源核对后逐步上同一张卡;为未见 `[DONE]` 的模型流保存无内容诊断,同时继续拒绝执行不完整工具调用。 + +**Architecture:** Agent-Native 的 OpenAI SSE 解析器为每次终态附加有界、无原文的读取观察;Backend 评测入口把它与实际 run ID、Usage 和停止决定一起落到受限记录。Backend 在系统预启动材料整理的首回合选择宿主确认 runner,不调用前台模型,因此不会先流出无法核对的自由文本;后台仍按 r0.4 的来源、阶段和失败规则运行。普通文字+小材料与已完成任务上的新提问继续原路径,并单独验证其范围限制。 + +**Tech Stack:** Go 1.27、Agent-Native SSE/runtime、Backend runmgr/受控评测、SQLite/隔离 PostgreSQL、React/Vite/Playwright、Dev `make test`/`make build` 与 Gitea CI。 + +**Spec:** [已批准的 r0.5 书面设计](2026-10-14-fast-slow-u3-r05-terminal-evidence-and-fast-feedback-design.md),Documents `0d473fe866703eab32f715ddf12d1b25913b822b`;[r0.4 A01 核验记录](../reports/2026-10-13-fast-slow-u2-u3-validation.md)。用户只批准**编写本计划**;计划本身待阶段 5 人工评审,不授权产品代码、付费模型、合入或部署。 + +## Global Constraints + +- r0.4 原始 A01(Dev `d38dbdc`,2 个付费 run/3 次调用/29,062 返回 Usage,0 个检查点)与受限归档原样保留,质量判定 **FAIL**。Backend `0637547` 的失败子 run Usage 修复也不追溯改写样本。 +- 缺 `[DONE]`、读取错误、取消、工具身份或对象 JSON 参数不完整时,工具执行次数必须为 0;合法 JSON、`finish_reason`、Usage 或正常 EOF 均不能单独替代完整终止标记。不新增自动重试、补参数、供应商切换或模型摘要调用。 +- 新终态诊断只保存枚举、布尔值、计数、HTTP 状态、实际 run ID/调用序号和 Usage;**不保存新的原始 SSE、prompt、正文、思考文本、工具名/参数、API key、请求头**。旧历史原件保持只读;新评测记录要移除现有 `messages`、`toolDeclarations`、`terminal` 的原始内容副本。 +- 保留 r0.3/r0.4 的 sourceId、owner/project、artifact hash/解析版本、原句、数字归属、CAS、seq、失败通知与建议选择条件。Core `TaskSnapshot`、HTTP 公开契约、数据库表、生产模型路由、预算与既有普通聊天入口不改。 +- 宿主确认不计入两个有用阶段成果。自动材料入口只有在系统确实预启动或复用活动任务时跳过前台模型;排队、运行、复用、启动失败与已完成复用按真实卡片状态分流。用户原文字重点继续进入慢任务;文字+小材料仍走原直连路径,不能用 A01 证明其质量。 +- 默认仍是三栏与一张任务卡。若实际浏览器证明必须修改 UI 代码,先读 Dev `DESIGN.md`,只消费 `@ui` 令牌、`mk-*` 动效和 Collapsible 原语;相关计划范围与组件 PR 需按影响重新评审。异步投影仍按 seq 丢弃过期事件,不抢文件、焦点和阅读位置。 +- 所有实施、真实 HTTP、PG、浏览器、审阅与 CI 都是**零付费**;只用 `httptest`/本地假流,不能运行 live `TestWorkCheckpoints` 或受保护模型路由。后续受限供应商探针或 A01 必须分别提交资源提案;本计划不预填、不授权任何 token/run 额度。 +- Dev `AGENTS.md` 的唯一 precheck 收据、`task-resume`、请求审核/验收/合入前 `task-sync`、`make evidence`、独立 REPLAY、准确 HEAD GATE 和任务工作区保护全部适用。计划实施需另获用户阶段 5 批准;质量 FAIL 时八个 PR 保持草稿。 + +## Review Focus + +| 容易伤害用户的输入/状态 | 所属任务的固定用例与预期 | +|---|---| +| `tool_calls` 有合法参数、Usage、正常 EOF,却没有 `[DONE]` | Task 1 `TestTerminalObservationCleanEOFWithoutDone`、Task 3 `TestEvaluationStopsAfterUnmarkedToolEOF`:记录 clean EOF,0 次工具执行、0 次后继请求。 | +| SSE 最后一帧半个 JSON,或 HTTP 读取错误/取消 | Task 1 `TestTerminalObservationMalformedAndReadError`:分类不同、已返回 Usage 不丢、原始内容不入诊断。 | +| 三个后台槽已满,材料排队;同批材料重发 | Task 2 `TestMaterialAckQueueAndReuse`:确认文字与卡片真实状态一致,只有一个材料子任务、前台 provider 0 次调用。 | +| 用户带文字+长材料,或文字+小材料 | Task 2 `TestMaterialAckPreservesUserFocusAndSmallPath`、Task 4 浏览器用例:前者重点进入慢任务且前台只确认,后者仍走原模型路径。 | +| prompt、工具参数或 provider 错误含敏感文本;诊断文件写入失败 | Task 3 `TestEvaluationTerminalRecordContainsNoPayload`、`TestEvaluationStopsWhenTerminalRecordUnavailable`:文件无哨兵原文,写入失败不放行工具终态或下一次请求。 | + +## 文件责任与依赖顺序 + +| 单元 | 文件(相对对应仓根) | 产物/消费者 | +|---|---|---| +| 1 Agent-Native 终态观察 | 修改 `runtime/llm/{types,sse,client,sse_test}.go`,新建 `runtime/llm/client_test.go` | `llm.Event.TerminalObservation`;Task 3 消费。Agent-Squad 与 Backend 的 Agent-Native gitlink随后固定同一 SHA。 | +| 2 Backend 可信快反馈 | `internal/runmgr/{material,material_ack,runmgr,material_test,material_ack_test}.go` | 内部 `materialIntakeDecision` 与 `materialAckRunner`;Task 4 的 HTTP/浏览器消费。 | +| 3 Backend 受控评测记录 | `internal/server/{work_checkpoint_eval_test,work_checkpoint_eval_budget_test}.go` | 内容最小化的 `provider-records.json` 与故障停止证据;Task 5 消费。 | +| 4 整条用户链 | Backend `internal/runmgr/material_notice_failure_test.go`、`internal/server/work_checkpoint_eval_test.go`;Frontend `e2e/work-checkpoints.playwright.ts` | 真实 HTTP、SQLite、隔离 PG、浏览器的收件→阶段→终态证据。Frontend 仅按需要改测试,不预设 UI 生产代码。 | +| 5 固定与评审 | Agent-Squad `deps/agent-native`;Backend `deps/agent-native`;Dev `deps/agent-native`、`deps/agent-squad`、`modules/backend`、可选 `modules/frontend`、任务卡与 Documents 指针 | 单一准确组件组合、独立复审、当前 HEAD CI、阶段 6 人工评审材料及资源决策。 | + +接口顺序:Task 1 的只读终态观察由 Task 3 记录;Task 2 的宿主确认由 Task 4 从真实浏览器观察;Task 1–4 的组件 SHA 进入 Task 5。Task 2 与 Task 3 可在各自包内分别验证,但 Backend 提交和 Dev 固定仍按上述依赖顺序完成。已有登记任务工作区与草稿 PR 继续使用,不从嵌套 detached gitlink 直接交付。 + +### Task 1: Agent-Native 有界终态观察,不放宽工具门禁 + +**Files:** 修改 `runtime/llm/types.go`、`runtime/llm/sse.go`、`runtime/llm/client.go`、`runtime/llm/sse_test.go`;新建 `runtime/llm/client_test.go`。**Interfaces:** 生产现有 `llm.Event` 的 `FinishReason`、`StreamComplete`、`Usage`、`HTTPStatus` 保留;新增只在终态/错误事件上出现的 `*TerminalObservation`,字段为 `ReadOutcome`(`done_marker|clean_eof|read_error|cancelled|invalid_frame|provider_error`)、`LastFrameJSONComplete`、`UsagePresent`、`ToolFragments`、`ToolArgumentBytes`(饱和计数)、`ToolArgumentsValid`。不包含原文、工具名或错误原句。Task 3 只消费这些标量。 + +- [ ] **Step 1: 写 RED。** 以现有 `TestParseSSEToolFinishWithoutDone`、`TestParseSSEReaderErrorAfterUsageKeepsUsage` 为底,新增 `TestTerminalObservationCleanEOFWithoutDone`:SSE 有合法工具 JSON、`finish_reason=tool_calls`、Usage 17,却在正常 EOF 结束;断言终态错误、`ReadOutcome=clean_eof`、`StreamComplete=false`、`ToolArgumentsValid=true`、`ToolFragments=1`、`ToolArgumentBytes>0`、`len(Message.ToolCalls)==0`。`TestTerminalObservationMalformedAndReadError` 表驱动比较半帧 JSON、注入读错误、取消;断言分类及返回 Usage。`client_test.go` 的本地 `httptest` 断言成功 HTTP 200 只作为标量传到终态,不保存 URL/请求头。 + +```go +if end.Err == nil || end.StreamComplete || len(end.Message.ToolCalls) != 0 || + end.TerminalObservation == nil || end.TerminalObservation.ReadOutcome != "clean_eof" || + end.Usage.TotalTokens != 17 { + t.Fatalf("unsafe or unclassified terminal: %#v", end) +} +``` + +- [ ] **Step 2: 记录 RED。** `GOWORK=off go test ./runtime/llm -run 'TestTerminalObservationCleanEOFWithoutDone|TestTerminalObservationMalformedAndReadError|TestClientTerminalObservationHTTPStatus' -count=1`;预计因新观察字段/分类缺失而失败,使用 Dev `make evidence` 保存原始 SELF 输出。不得用旧 r0.4 守卫测试的 PASS 代替新诊断测试。 +- [ ] **Step 3: 最小 GREEN。** 在 `sseEventReader`/`emitParsed` 区分 `[DONE]`、扫描器 clean EOF、`sc.Err()`、坏 JSON 和取消;仅维护布尔、枚举与计数,参数字节数饱和到 `4*1024*1024+1`。为诊断先计算已收参数的对象 JSON 有效性,**再**运行原有“缺 `[DONE]` 必拒绝”的工具释放门禁,不能因 `ToolArgumentsValid=true` 绕过它。创建终态 Event 时附观察;`client.run` 对成功响应复制 `resp.StatusCode` 到终态,不读取或记录头/body。现有 `toolFinishExecutable` 与“缺 `[DONE]` 不发 ToolCalls”的顺序一字不放宽;`KindError` 路径也要给出可判定的失败类别,不伪造正常终态。 + +```go +type TerminalObservation struct { + ReadOutcome string + LastFrameJSONComplete bool + UsagePresent bool + ToolFragments int + ToolArgumentBytes int + ToolArgumentsValid bool +} +// 仅 llm.Event 追加:TerminalObservation *TerminalObservation +``` + +- [ ] **Step 4: 跑 GREEN 与兼容。** 重跑 Step 2;另跑 `GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...`,重点复看合法多片工具、纯文本缺 `[DONE]` 的既有行为,以及 `runtime.Run` 的 Usage/不重试测试。测试诊断序列化后不含哨兵 prompt、工具名、参数原文。记录若当前 `client.go` 的 HTTP 状态只在错误事件有值,新增正常 200 不影响 fallback 选择。 +- [ ] **Step 5: 提交 Agent-Native。** 仅提交该组件文件;描述 `Event` 的加法式 Go 契约及 Agent-Squad/Backend 固定指针依赖,不宣布供应商行为已修复。推送现有草稿 PR,核对它的准确 HEAD CI。 + +### Task 2: Backend 预启动材料的宿主确认 + +**Files:** `internal/runmgr/material.go`、新建 `internal/runmgr/material_ack.go` 与 `material_ack_test.go`、`internal/runmgr/runmgr.go`、`internal/runmgr/material_test.go`。**Interfaces:** 内部 `materialIntakeDecision{Note string, State materialIntakeState, CardID core.ID}`;`State` 仅为 `none|queued|running|reused_running|completed|failed|unknown`。`materialAckRunner` 实现已有 `runtime.Runner`,只消费可信附件数、工作重点是否存在与卡片状态。`completed`/`none` 继续原 runner;`queued|running|reused_running|failed|unknown` 走宿主确认,不请求前台 provider。 + +- [ ] **Step 1: 写 RED。** 用 `newCardSpawnEnv` 和实际 `Store` 增加 `TestMaterialAckAttachmentOnlyDoesNotCallParentModel`:三附件无文字时先建任务卡,再在同一对话 run 保存一条不含“剩余4项含3项依赖接口”的宿主消息;前台 stream 调用数 **0**,慢任务数 **1**,宿主消息不增加 `checkpointSeq`。`TestMaterialAckQueueAndReuse` 将三个后台槽占满再发送,断言卡为 queued、确认文案不称“正在执行”;同批重发只复用一张卡,后台刚完成但内存活动键尚未释放时以卡片终态为准。`TestMaterialAckPreservesUserFocusAndSmallPath` 断言文字+长材料的原文重点在 `buildMaterialTaskFor`,文字+小材料仍调用原 runner;完成态重发仍可进入普通问答。`TestMaterialAckSkipsSquadEntrypoint` 将 `runtime.entry=squad`,断言系统预启动分支也不调用小队 runner。`TestMaterialAckFailureAndUnknown` 注入注册/读卡错误,断言不称已启动、不追加模型请求。 + +```go +if parentProviderCalls != 0 || materialChildren != 1 || card.Task.CheckpointSeq != 0 || + strings.Contains(visibleReply, "剩余 4 项(含 3 项依赖接口)") || + !strings.Contains(visibleReply, "待确认") { + t.Fatalf("first reply is not a truthful host acknowledgement") +} +``` + +- [ ] **Step 2: 记录 RED。** `GOWORK=off go test ./internal/runmgr -run 'TestMaterialAckAttachmentOnlyDoesNotCallParentModel|TestMaterialAckQueueAndReuse|TestMaterialAckPreservesUserFocusAndSmallPath|TestMaterialAckSkipsSquadEntrypoint|TestMaterialAckFailureAndUnknown' -count=1`;旧实现会调用前台 runner/缺状态分流,预计失败。原始 SELF 收据经 Dev `make evidence`,不要手抄输出。 +- [ ] **Step 3: 最小 GREEN。** 把 `materialIntake`、`materialExisting`、`materialSpawn` 的内部返回收敛为 `materialIntakeDecision`,更新同包调用和现有测试;从已注册的、同 owner/project/inputRevision 的卡片读取 queued/running/terminal 状态,读不到就 `unknown`,不靠中文 note 判断分支。`startClaimedRun` 在 `materialIntake` 后按 `State` 将 `runner` 替换为 `materialAckRunner`;该 runner 发一次既有 `EvRunPhase(Responding)`/`EvMessageDelta`,再用 `SetAssistantParts` 交给 `finishRun` 正常持久化。短文案只用可信附件数/状态,并明确未经核对的关系“待确认”;失败态提示可重新提出请求但不自动重试。原始模型候选不调用、不进入事件、转录或后续上下文。`none`/`completed` 保留原模型入口;`ensureMaterial` 的文字+小材料兜底继续原路径。 + +```go +type materialIntakeState string +const ( + materialNone materialIntakeState = "none" + materialQueued materialIntakeState = "queued" + materialRunning materialIntakeState = "running" + materialReusedRunning materialIntakeState = "reused_running" + materialCompleted materialIntakeState = "completed" + materialFailed materialIntakeState = "failed" + materialUnknown materialIntakeState = "unknown" +) +func (s materialIntakeState) needsAcknowledgement() bool { + return s == materialQueued || s == materialRunning || s == materialReusedRunning || + s == materialFailed || s == materialUnknown +} +decision := m.materialIntake(rc) +rc.materialNote = decision.Note +if decision.State.needsAcknowledgement() { + runner = materialAckRunner{decision: decision, attachmentCount: len(rc.attachments), hasUserFocus: messageText(rc.userMessage) != ""} +} +``` + +- [ ] **Step 4: 跑 GREEN 与并发。** 重跑 Step 2,再跑 `GOWORK=off go test -count=1 ./internal/runmgr ./internal/runtime ./internal/cardsvc` 与 `GOWORK=off go test -race -count=1 ./internal/runmgr -run 'TestMaterialAckQueueAndReuse|TestMaterialIntakeAttachmentOnlyLifecycle'`。核对授权关闭、无附件、任务注册失败、取消、重启后的活动卡去重、原已有完成结果、任务来源版本变化与后台 notice;宿主确认不能被通知当作最终整理正文或建议列表。 +- [ ] **Step 5: 提交 Backend 快反馈单元。** 只提交此任务相关文件,保留消息/卡片的现有 Core 契约;说明首回合少一次模型请求及其 Usage 可比性变化,不把普通小材料问答标成已整改。 + +### Task 3: 受控评测的无内容记录与停止规则 + +**Files:** `internal/server/work_checkpoint_eval_test.go`、`internal/server/work_checkpoint_eval_budget_test.go`。**Interfaces:** 消费 Task 1 `Event.TerminalObservation`、既有 `FinishReason`/`StreamComplete`/`Usage`/`HTTPStatus`;`provider-records.json` 保留 `runId`、调用号、输出上限、返回 Usage、结束原因及诊断标量,移除以后样本里的 `messages`、`toolDeclarations` 和原样 `terminal`。历史 r0.4 文件哈希不变。 + +- [ ] **Step 1: 写 RED。** `httptest` 分别返回带 `[DONE]` 的合法工具调用、正常 EOF 无 `[DONE]` 且 JSON/Usage 合法、半帧 JSON、读错误、缺 Usage。新增 `TestEvaluationRecordsContentFreeTerminal`:将哨兵 `PRIVATE_PROMPT_R05` 放入请求消息、`PRIVATE_ARGUMENT_R05` 放入工具参数,断言新 `provider-records.json` 不含两串和原始 `messages`/`toolDeclarations`/`terminal` 键,但有 run ID、调用号、`readOutcome`、`usagePresent`、`reportedTokens`。`TestEvaluationStopsAfterUnmarkedToolEOF` 与 `TestEvaluationStopsWhenTerminalRecordUnavailable` 分别断言无 `[DONE]` 和 0600 写入失败后第二次 provider 调用数仍为 **0**;已返回 Usage 必须保留。 + +```go +raw, err := os.ReadFile(filepath.Join(dir, "provider-records.json")) +if err != nil { t.Fatal(err) } +if bytes.Contains(raw, []byte("PRIVATE_PROMPT_R05")) || + bytes.Contains(raw, []byte("PRIVATE_ARGUMENT_R05")) || + bytes.Contains(raw, []byte(`"toolDeclarations"`)) { + t.Fatal("evaluation record contains model payload") +} +``` + +- [ ] **Step 2: 记录 RED。** `GOWORK=off go test -tags live ./internal/server -run 'TestEvaluationRecordsContentFreeTerminal|TestEvaluationStopsAfterUnmarkedToolEOF|TestEvaluationStopsWhenTerminalRecordUnavailable' -count=1`。只运行这些本地假 provider 测试;**不得**用无 `-run` 的 live-tag 命令误启动 `eval/live/TestWorkCheckpoints`。 +- [ ] **Step 3: 最小 GREEN。** 在 `evalProviderStreamWithProvider` 的“先关流、落 Usage/停止记录、再释放终态”顺序内生成只含标量的记录;`terminal==nil` 时使用有限的 `missing_terminal` 类别,错误信息仅记录枚举,不保存 `Err.Error()` 的可能敏感原文。`terminalFault`、`usageUnknown`、`checkpointRejected`、批次与单 run 上限继续在下次请求前拒绝。记录写入失败则终态不执行工具、下一次请求继续被本地拒绝;原始历史记录不迁移。 + +```go +type evalCallRecord struct { + RunID string `json:"runId"` + Call int `json:"call"` + OutputCap int `json:"outputCap"` + ReportedTokens int `json:"reportedTokens"` + FinishReason string `json:"finishReason"` + StreamComplete bool `json:"streamComplete"` + Observation *agentllm.TerminalObservation `json:"terminalObservation,omitempty"` + FailureClass string `json:"failureClass,omitempty"` +} +// 不再包含 messages、toolDeclarations 或整个 terminal Event。 +``` +- [ ] **Step 4: 跑 GREEN 与控制组。** 重跑 Step 2,再跑 `GOWORK=off go test -count=1 ./...`;仅用精确正则执行 `GOWORK=off go test -tags live ./internal/server ./eval/live -run 'TestEvaluationRecordsContentFreeTerminal|TestEvaluationStopsAfterUnmarkedToolEOF|TestEvaluationStopsWhenTerminalRecordUnavailable|TestFollowupA01KeepsItsOwnOneShotResult|TestFollowupA01UsesNewBoundedAuthorization' -count=1`。保存合法 `[DONE]` 工具调用成功、纯文本缺标记仍按旧行为且诊断清楚、坏工具终态失败三种记录,不能把诊断字段出现当作质量 PASS。 +- [ ] **Step 5: 提交 Backend 诊断单元。** 与 Task 2 合并前确认没有 `messages`、原始终态或工具参数在新的评测文件中,且 A01 标签/路由/模型预算没有变化。推送 Backend 草稿 PR 并核对准确 HEAD CI。 + +### Task 4: 用户链零付费组合重放 + +**Files:** Backend `internal/runmgr/material_notice_failure_test.go`、`internal/server/work_checkpoint_eval_test.go`;Frontend `e2e/work-checkpoints.playwright.ts`。**Interfaces:** 只通过现有 HTTP 消息、run、卡片、事件及 `TaskSnapshot` 观察 Task 2/3;不新增前端状态所有者或 API 字段。 + +- [ ] **Step 1: 写 RED。** 在 `e2e/work-checkpoints.playwright.ts` 的 `Scenario` 联合类型中增加 `fast-ack`,并在任务自有 HTTP 假流夹具加入“附件仅发送”“文字+长材料”“三个槽位已满”“同批重发”“启动失败”“已完成复用”“文字+小材料”场景;浏览器先断言宿主确认在原对话可见、任务卡在右侧同一入口、未出现无依据的 `剩余4项含3项依赖接口`、宿主确认不增加 finding/checkpoint。文字+小材料断言仍有原直连答复;后台失败零发现、部分成果、成功建议选择仍按 r0.4 规则。先让新增用例在旧前台模型路径失败,保存 SELF RED。 + +```ts +test('attachment-only material intake shows a safe first reply', async ({ page, request }) => { + const seed = await setup(request, 'fast-ack', false) + await open(page, seed) + await expect(page.getByText(/已收到.*份材料.*待确认/)).toBeVisible() + await expect(page.getByText(/剩余\s*4\s*项.*含\s*3\s*项依赖接口/)).toHaveCount(0) + const card = (await (await request.get(manifest.base + `/api/conversations/${seed.conv.id}/cards`)).json()).cards.find((c: any) => c.task) + expect(card.task.checkpointSeq).toBe(0) +}) +``` +- [ ] **Step 2: 完成受控机制夹具。** Backend 夹具在每个场景使用新的隔离 SQLite、固定三份合成文本与人工 SSE;父回合计数器记录 provider 调用数,子回合按场景给合法 `[DONE]`/阶段工具/失败终态。保留实际 HTTP 的 owner/project 与来源哈希核验;不把 fixture 中的 `reading`/`crosscheck` 当成真实模型遵循顺序的证明。 + +```go +// 每个 scenario 使用新 dataDir;stream 只连 httptest.Server。 +parentCalls := atomic.Int32{} +parentStream := func(ctx context.Context, model string, messages []agentllm.Message, + tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event { + parentCalls.Add(1) + out := make(chan agentllm.Event, 1) + out <- agentllm.Event{Kind: agentllm.KindMessageEnd, + Message: agentllm.Message{Role: agentllm.RoleAssistant, Content: "本地夹具答复", StopReason: agentllm.StopEnd}, + Usage: agentllm.Usage{PromptTokens: 2, CompletionTokens: 1, TotalTokens: 3}, StreamComplete: true} + close(out) + return out +} +// 自动材料入口完成后断言 parentCalls.Load()==0;文字+小材料对照为 1。 +``` +- [ ] **Step 3: 跑 GREEN 与视觉回归。** 启动本任务独有的 mechanism 二进制和数据目录,manifest 固定 PID、可执行文件 SHA、Dev/Backend/Agent-Native/Frontend SHA、模式与地址;先用 manifest 的 `pid` 核查对应 `/proc/` 可执行文件和目录归属,再将 manifest 的 `base` 设为 `PLAYWRIGHT_BASE_URL`、manifest 路径设为 `WORK_CHECKPOINT_HTTP_MANIFEST`,运行 `npx playwright test e2e/work-checkpoints.playwright.ts`。至少覆盖 1440px 桌面与 390px 窄屏:默认只见短确认,任务卡阶段发现、来源、失败和选择卡按现有层级展示,刷新和后台更新不抢文件、输入焦点或阅读位置;禁止新增命令/SSE 展示。只停止任务自有 PID,保留 trace/截图及 SELF 收据。 +- [ ] **Step 4: 隔离 PG 与完整 Backend。** 使用本任务独有的 PostgreSQL/Redis 实例和 DSN(不能连接既有服务),先验证实例 PID、数据根和数据库身份;设置 `AGENT_PG_TEST_DSN`、`AGENT_REDIS_TEST_ADDR` 后运行 `make test-backend-pg`,缺变量不得跳过。另运行 Backend `GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...`,以及纯文档/Frontend 受影响的正常路径。记录失败与修复版本;PG 和 SQLite 都核对 seq、宿主消息、来源卡和失败 Usage。 +- [ ] **Step 5: 提交实际测试增量。** Backend/Frontend 只提交本单元实际新增的夹具与浏览器测试;若需要改 Frontend/UI 生产组件才能表达正确状态,停止该部分,补设计与计划评审,再读 `DESIGN.md`、固定 `@ui` 令牌 PR 和设计守护测试。真实模型质量继续 `NOT_RUN`。 + +### Task 5: 固定跨仓候选、复审与资源出口 + +**Files:** Agent-Squad `deps/agent-native`、Backend `deps/agent-native`、Dev `deps/agent-native`/`deps/agent-squad`/`modules/backend`/可选 `modules/frontend`、`.task/feature-fast-slow-u2-u3-plan.md`、`PROJECT.md`、Documents 的核验报告。**Interfaces:** 公开 Core/HTTP/TaskSnapshot 不变;准确 gitlink 与运行 manifest 是唯一测试对象。 + +- [ ] **Step 1: 按依赖提交并固定。** Agent-Native 先提交并推送准确 SHA;Agent-Squad 在自己分支固定同一 SHA,完整 Go vet/test 后提交;Backend 固定嵌套 Agent-Native(及现有 Core)并完成 Task 2/3/4 的全量测试后推送;Frontend 如只改 e2e 则提交相应用例。Dev 最后暂存全部新 gitlink,先跑 `make test-pins`,再用 `CMD='make test && make build' make evidence` 保存组合;禁止用 detached 嵌套检出的未提交代码冒充可交付组件。 +- [ ] **Step 2: 记录最小重放集。** 任务卡列明 r0.5 需求/设计/用例/结果矩阵:`[DONE]` 与 EOF、半帧、读取错误、缺 Usage、queue/reuse/failed/completed、附件仅发送、文字+长/小材料、0/部分/两批成果、刷新与来源版本。每条标 `SELF`/`REPLAY`/`GATE`,旧 r0.4 A01 继续 FAIL;新真实模型 A01 为 `NOT_RUN`。文档补齐精确 SHA、原件保存路径和已知限制,不写未经验证的供应商终态原因。 +- [ ] **Step 3: 独立复审与当前 HEAD CI。** 请求审核前、验收前、将来合入前各用 `CMD='make task-sync' make evidence` 纳入最新权威 main;同步若改变代码、依赖、配置或 head,按影响重跑 Task 1–4 与组合。独立审阅者重放终态无内容记录、坏工具零执行、前台零 provider、队列/失败真实性、原文归属、Usage 与脚本权限,整改到无 Critical/Important。逐仓核验 Agent-Native、Agent-Squad、Backend、适用 Frontend 及 Dev 的准确 HEAD CI,Dev 的 `test`、`test-pg`、`svc-realcombo` 全部作业/步骤均须 GATE 成功;源外 RECEIPT 留固定后检查。 +- [ ] **Step 4: 提交阶段 6 人工评审。** 交付零付费候选、实际 HTTP/浏览器和 PG 的 SELF、独立 REPLAY、准确 HEAD GATE、未解决风险与八个草稿 PR 链接。用户仅评审 r0.5 实施候选;即使代码机制 PASS,仍不等于真实模型质量、阶段 7 或合入批准。没有人工阶段 6 结论,不进入资源决策。 +- [ ] **Step 5: 单独资源决策,不自动跑 A01。** 先判断无内容终态诊断是否仍不能证明缺 `[DONE]` 的工具流可安全执行。若不能,记录“工具仍保守失败”,向用户提交独立的、受限供应商终态探针或后续设计修订选择;**不得**直接复用 r0.4 A01 额度、放宽门禁或自动再跑。仅在后续必要设计/计划评审和单独资源提案获批、质量候选固定后,才按实际授权的 run/token/时间/输出/停止边界执行新真实模型对照。真实质量 PASS 后仍须阶段 7 人工验收,随后才按 Dev 策略做最终 Review、准确 HEAD CI、串行 PR 合入、合入核验与安全回收;质量 FAIL 则保持全部 PR 草稿。 + +## 计划自检与停止条件 + +- 本计划的终点是**可审阅的零付费实施候选**和阶段 6 材料;当前用户只授权编写计划,Task 1–5 的代码与测试均 `NOT_RUN`。不得将本计划的绿色文档 CI、未来单测或假流当成真实 A01 通过。 +- Task 1 的观察字段只增加无内容诊断,不更改 `toolFinishExecutable`;Task 2 只接管预启动首回合,保持用户重点与既有消息合同;Task 3 先持久 Usage/故障记录再放终态;Task 4 以真实 HTTP/浏览器检验用户看到的内容;Task 5 只固定实际提交的组件与证据。接口消费和责任边界按此顺序复核。 +- 任何需要接受无 `[DONE]` 的工具请求、增加模型调用、变更公开消息/TaskSnapshot 契约、数据库迁移、扩大权限或改变验收标准的发现,立即停止受影响实现,返回书面设计与计划人工评审;未受影响的零付费调查可继续。任何付费请求必须另有明确资源批准,失败不自动重跑。 diff --git a/docs/proposals/2026-10-14-fast-slow-u3-r06-transport-completeness-design.md b/docs/proposals/2026-10-14-fast-slow-u3-r06-transport-completeness-design.md new file mode 100644 index 0000000..b0cd4db --- /dev/null +++ b/docs/proposals/2026-10-14-fast-slow-u3-r06-transport-completeness-design.md @@ -0,0 +1,51 @@ +# U3 r0.6:当前路由的传输完成性证据设计 + +状态:**书面设计已获阶段 4 人工评审通过。** 用户先于 2026-10-14 的同会话答复“认可证据优先,写设计(推荐)”,随后审阅 Documents `54d39ef13034b2ce84cb84681566ea21311f79a0`、Dev `89125b8dda5f8f988a8a760ed64bf00827e1de5c` 的文稿及准确 HEAD CI,并明确答复“批准设计,继续实施计划”。该批准只允许编写详细实施计划;**不授权产品代码、额外付费请求、A01 复验、合入或部署。** 本次状态行仅记录评审结论,不改已批准设计的技术取舍。负责人 weichen;目标仍是让非专业用户在同一张任务卡上先看到可信收件反馈、再看到有来源的两批阶段成果和准确最终正文。既有[U2/U3 主计划](2026-10-13-fast-slow-u2-u3-plan.md)、[r0.5 设计](2026-10-14-fast-slow-u3-r05-terminal-evidence-and-fast-feedback-design.md)、[核验报告](../reports/2026-10-13-fast-slow-u2-u3-validation.md)及 Dev `feature/fast-slow-u2-u3-plan` 任务卡是本设计的上下文;本版只处理阻断后台工具流的终态归因,不重新设计界面或降低业务质量标准。 + +## 事实、问题与目标 + +r0.4 A01 在固定候选上有两次付费 run、三次 provider 调用,工具流返回 `tool_calls` 和 Usage,但客户端未见 `[DONE]`;工具安全拒绝,质量 FAIL。r0.5 随后保留严格门禁、增加无内容终态观察,并把自动材料入口首回合改成可信宿主确认。其零付费实施与阶段 6 已获人工确认。用户另行批准的**唯一**小型 MiniMax-M3 工具流探针在 Dev `0959e1c1566df9766878b2122eccc0ab42af10a3` 上返回 HTTP 200、`finishReason=tool_calls`、`ReadOutcome=clean_eof`、最后一帧已观察到的 JSON 完整、返回 Usage **513 tokens**,但 `StreamComplete=false`,工具放行/执行 **0/0**。一次性授权已消耗,不得补跑;见[作者判定](/tmp/fast-slow-u3-r05-20261014/probe-result-assessment.md)和[独立结果复核](/tmp/fast-slow-u3-r05-20261014/probe-result-independent-assessment.md)。本设计中的“当前路由”只指该探针 `SELF` 预检核对的 MiniMax-M3、`api.minimaxi.com/v1`、无 fallback,受保护路由文件 SHA-256 `2a92569d2416d74e8d07029219af88474cb6f860c2aa897a5ffdd10fcbe9b495`;身份漂移即重新评审,不复制或公开 API key。r0.5 真实 A01 仍 **NOT_RUN**,八个 PR 仍为草稿。 + +这一次短工具请求重现了“有结束原因和 Usage,但未见 `[DONE]`”的**客户端观察**,排除了“只在三材料长任务中才出现”作为必要条件;它没有原始 SSE、HTTP 尾部元数据或供应商侧记录,无法判断服务端未发送、传输途中结束,还是适配器漏认。`clean_eof` 表示读取器没有报告扫描错误;它不证明供应商已交付预期的全部 SSE 事件。当前 [MiniMax Chat Completions API 文档](https://platform.minimax.cn/docs/api-reference/text-chat-openai)列出流式、工具和 Usage 字段,但没有在这页给出该路由以字面 `[DONE]` 结束工具流的可核验契约;不能把 OpenAI 兼容接口的惯例当作该路由的事实。 + +本版要回答两个分开的判断:**HTTP 响应体在客户端边界如何结束?SSE 应用层是否有经证实的完整工具调用终态?** 成功标准是形成可重放、无内容的诊断与决策证据,减少原因的不确定性;本版不承诺 r0.6 代码一落地就让工具流成功,也不把一次小探针等同于 A01 质量验收。 + +## 方案取舍 + +| 路线 | 能回答什么 | 代价与结论 | +|---|---|---| +| **A. 当前路由上分层观察 HTTP 读取与 SSE 终态,先保守拒绝** | 区分已知长度、分块、HTTP/2、关闭分隔等响应体结束类型;定位解析器是否漏认本地合法终止形式 | **推荐。** 零付费重放先行;真实路由若仍缺应用层契约,保持拒绝,再单独评审兼容规则或替代路由 | +| B. 直接换成另一个终态已验证的模型路由 | 可能绕开当前路由的兼容性问题 | 需要新模型质量基线、路由配置与资源评审;当前证据不足以指定替代对象,作为 A 无法得到可信终态时的后备方向 | +| C. 将 `clean_eof + tool_calls + Usage + JSON` 直接等同 `[DONE]` | 可能放行这类响应 | **不采纳。** 已观察到的最后一帧完整不等于整条业务流完整;尾帧缺失仍可能留下看似合法的参数 | + +选择 A 不排斥后续 B。只有在明确证据与另行批准下,才能提出限路由的等价终态;本设计本身不定义一个可直接放行的 EOF 组合。 + +## 职责、数据流与隐私 + +Agent-Native 的 `Client.Stream` 已拿到 `http.Response`,`ParseSSE` 已拥有 `[DONE]`、`finishReason`、Usage、帧完整性与工具片段观察;**只有 Agent-Native** 可以在同一次调用中把两层事实关联成一条有界终态观察。Backend 只按现有 run ID/调用序号消费分类和 Usage,保留 r0.5 的拒绝后停止、预算和归档规则。Frontend/UI 沿用已实现的“整理中/未完成/待确认”、同一张任务卡、来源和失败展示;默认不显示协议字段、命令或 token。异步事件仍由原所有者按 seq 丢弃过期项,诊断记录不能成为另一个进度状态源。 + +建议在 Agent-Native 的内部终态观察中增加**有限枚举与饱和计数**,而非另存一份响应: + +- HTTP 层:状态码、协议大类 `http1|http2|other`、响应体定界类别 `known_length|chunked|http2_stream|close_delimited|unknown`、声明长度是否存在、客户端实际读取字节数的有界计数、是否到达 `io.EOF`、读取错误类别、是否发生自动解压。已知长度时可给出“读取字节数与声明长度相符/不符”;未知长度不得伪造相符结论。Go [`net/http.Response`](https://pkg.go.dev/net/http#Response) 暴露 `ContentLength`、`TransferEncoding`、协议版本及 `Uncompressed`,并说明 `Body` 在读取时流式返回;这些字段支持**客户端边界**的分类,不暴露供应商上游到代理之间的完整性。 +- SSE 层:沿用 `done_marker|clean_eof|read_error|cancelled|invalid_frame`、`StreamComplete`、受限 `finishReason`、Usage 是否存在及数量、最后一个**已观察**帧是否完整;只增加尾部事件的结构类别与有界计数,如 `done_literal|json_frame|empty_data|comment|unknown_non_json|partial_frame`,用于区分本地解析遗漏与真正未见标记。**不记录未知帧文字、尾部字节、原始 SSE 或其内容哈希。** +- 工具边界:分别记录“工具片段存在”“ID/名称齐全”“参数为 JSON 对象”“放行/实际执行次数”。现有 `ToolArgumentsValid` 只证明 JSON 对象可解析,不能偷换成 schema 或业务语义有效;本版不顺带新增工具 schema 校验器。缺 `[DONE]`、读取错误、取消、半帧、身份/参数不完整时仍为零放行、零执行。 + +所有字段只允许枚举、布尔和饱和计数;HTTP header/trailer 的名称与值、URL 查询、IP、请求 ID、证书细节、正文、思考、工具名及参数、prompt、API key、原始错误句均不入日志、评测记录或 UI。响应体计数只在流式读取时累计字节,不额外缓冲或复制模型内容。评测文件仍置于任务自有 0700 目录、0600 文件,保留期限沿现有受控评测清理规则;若需新增内部持久字段,实施计划须明确存储 owner、模式兼容与清理期限。公开 Core/HTTP/TaskSnapshot 合同和数据库表默认不变;发现必须改变时回到本设计重审。 + +`net/http` 可能自动解分块或解压,某些传输细节在 `Response.Body` 之上已不可见。实现计划应先用本地实验确定哪些字段能可靠观察;**不能观测就记 `unknown`**,不能为了满足一张表而声称看到了原始 HTTP 分块终止符或 HTTP/2 帧。响应体按协议完整读到 EOF,也只证明客户端收到的**响应体**结束,不能单独证明提供方本来就打算结束业务 SSE。真实路由的上游代理与供应商侧仍需外部契约或受控证据,不由客户端元数据代签。 + +## 决策规则与失败恢复 + +本版实现目标是**诊断,不放宽执行门禁**。终态首先按现有规则确定能否释放工具;新观察字段只给出原因与证据等级,不参与 `toolFinishExecutable` 的放行条件。非工具纯文本目前可以在缺 `[DONE]` 时按旧行为结束,但必须独立标注,不能把文本容忍路径借给工具。已返回 Usage 继续记账;诊断写入失败时保持原失败与停止决定,不让观察故障触发重试。取消、读错或多终态按保守失败处理;单次模型请求最多产生一条受限终态收据,Backend 不建立第二个状态所有者。 + +后续若要**提出**当前路由的等价终态规则,至少需要同时满足:① 可复核的供应商契约或该路由、模型、版本的授权外部证据,明确没有字面 `[DONE]` 时怎样表示业务完成;② 本地解析器能识别所约定的终止形式,并有正常、截断、半帧、缺 Usage、重定向/错误、取消的正反例;③ 客户端可区分响应体按协议结束与未知关闭,工具身份和参数完整性可验证,预算与停止门禁仍成立;④ 该规则只绑定经核验的路由身份,不对其他 OpenAI 兼容服务泛化。若还需声明 schema 校验,应在该轮方案中明定,不从当前 JSON 对象有效性推得。满足这些条件只进入**另一轮方案/计划评审**,不能自动改代码放行。若外部契约缺失、传输完整性未知或证据彼此矛盾,保持工具拒绝,并评估替代路由;不得因本次 513-token 探针成功返回 Usage 而补跑 A01。 + +## 零付费验证与阶段出口 + +详细实施计划获批前,本节只规定验收边界,不分派代码任务: + +1. 本地 `httptest`/TLS HTTP/2 假流覆盖有 `[DONE]`、无 `[DONE]` 的完整 JSON、已知长度与字节不符、合法 chunked、连接中断、HTTP/2 正常结束、关闭分隔未知、自动解压、半帧、尾部非 JSON、缺 Usage、重定向、取消。对每例核对 HTTP 与 SSE 分层分类、已返回 Usage、工具放行/执行次数和后继请求数;至少一组证明“响应体按协议结束但无应用终止契约”仍拒绝工具。 +2. 以哨兵正文、思考、工具名、参数、header/trailer、凭据与未知错误文字扫描新终态记录、日志和浏览器可见面;不得出现这些原文。计数边界、负值/溢出、未知枚举和多终态都须保守。正常 `[DONE]` 工具调用与 r0.5 快通道确认、排队/失败卡、来源跳转、刷新及按 seq 更新不得回退。 +3. 固定 Agent-Native 与消费它的组件 SHA 后,做组件测试、Dev 组合 `make test && make build`、适用的真实 HTTP/浏览器及隔离 PostgreSQL 回归、独立重放和准确 HEAD CI;`SELF`、`REPLAY`、`GATE` 分开标注。零付费测试只证明观测机制,不证明 MiniMax 真实流或 A01 质量。真实模型、供应商账单和业务成果均标 `NOT_RUN`,直至另获资源批准。 + +阶段 4 的用户结论已记录在文首;现进入**详细实施计划编写**,计划仍待阶段 5 人工评审。计划获批后才可零付费改代码和验证。任何新付费探针或 A01 都须在固定新候选、单独资源提案与明确批准后进行;r0.5 一次性授权不能沿用。真实 U3 质量仍要求两条在终态前可感知且有用、有来源的阶段成果、准确完整最终正文及全部调用 Usage/停止规则,宿主收件确认不计入两条。质量未通过时八个 PR 保持草稿,不合入或部署。 diff --git a/docs/proposals/2026-10-14-fast-slow-u3-r06-transport-evidence-implementation-plan.md b/docs/proposals/2026-10-14-fast-slow-u3-r06-transport-evidence-implementation-plan.md new file mode 100644 index 0000000..6cd0675 --- /dev/null +++ b/docs/proposals/2026-10-14-fast-slow-u3-r06-transport-evidence-implementation-plan.md @@ -0,0 +1,257 @@ +# U3 r0.6 Transport Completion Evidence Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 在当前 MiniMax-M3 工具流上留下可重放、无内容的 HTTP 响应体与 SSE 业务终态分层证据,同时保持缺 `[DONE]` 时工具零执行。 + +**Architecture:** Agent-Native 在现有 `Client.Stream → ParseSSE` 链路里流式计数响应体并归类最后的 SSE 结构,合成一条只含有限枚举、布尔和饱和计数的 `TerminalObservation`。Backend 受控评测白名单归一化该观察,与实际 run ID、调用号和 Usage 一起落到受限记录;普通用户界面仍只展示任务卡的真实状态与有来源成果。此版不把 HTTP EOF 变成工具放行条件,也不修改模型路由或加请求。 + +**Tech Stack:** Go 1.27、`net/http`/`httptest`、Agent-Native OpenAI 兼容 SSE、Backend 受控评测与 SQLite、任务自有 Playwright 机制实例、Dev 组合测试和 Gitea CI。 + +**Spec:** [已获阶段 4 人工批准的 r0.6 书面设计](2026-10-14-fast-slow-u3-r06-transport-completeness-design.md)。批准对象 Documents `54d39ef13034b2ce84cb84681566ea21311f79a0`、Dev `89125b8dda5f8f988a8a760ed64bf00827e1de5c`;用户答复“批准设计,继续实施计划”。本计划本身**待阶段 5 人工评审**;该答复只授权编写计划,不授权以下代码步骤、付费探针、A01、合入或部署。 + +## Global Constraints + +- 范围固定为已核验的 MiniMax-M3、`api.minimaxi.com/v1`、无 fallback 的路由身份(保护文件 SHA-256 `2a92569d2416d74e8d07029219af88474cb6f860c2aa897a5ffdd10fcbe9b495`)。代码观察可以复用在 OpenAI 兼容客户端,但**解释与未来兼容决策只针对该路由**;路由字节或模型改变须重新评审。不得把 API key、路由文件原文或供应商数据写进仓库与收据。 +- 保持 `toolFinishExecutable` 原有判据:缺 `[DONE]`、读错、取消、半帧、工具身份或 JSON 对象参数不完整时,不释放或执行工具;不新增自动重试、补参数、模型回退、摘要模型调用或额外 provider 回合。文本缺标记的既有容忍路径不得扩给工具。 +- 新观察只保存有限枚举、布尔和饱和计数。不得保存原始 SSE/尾部字节及其内容哈希、正文、思考、prompt、工具名/参数、HTTP header/trailer 名称或值、URL 查询、IP、请求 ID、证书细节、API key、原始错误句。诊断不进入普通用户 UI,异步状态仍由原所有者按 seq 处理。 +- Agent-Native 的终态事件是唯一诊断来源;Backend 只白名单消费并保留已返回 Usage、实际 run ID、停止边界。既有公开 Core/HTTP/TaskSnapshot 合同、数据库表、前端状态 owner、r0.5 宿主确认及来源判定不改。若证明必须改公开契约、权限、持久模式或 UI,停止受影响单元,返回设计评审;UI 修改前先读 Dev `DESIGN.md` 并使用 `@ui` 令牌/原语。 +- r0.5 唯一付费探针已消耗授权,结果 `incomplete_terminal`、返回 Usage 513、工具 0;r0.4 A01 仍 FAIL,r0.5 真实 A01 仍 `NOT_RUN`。本计划实施阶段只跑假流、真实本地 HTTP/浏览器与隔离 PG/CI,不运行 `TestWorkCheckpoints`、不使用 `WORK_CHECKPOINT_SERVER_MODE=model`、不读取保护路由 key。新的供应商试验和 A01 均须候选固定后另交资源提案并获明确批准。 +- 每个源代码单元先 RED、再最小 GREEN、再组件测试与提交;证据经 Dev `make evidence` 保存,例如 `RECEIPT=/tmp/fast-slow-u3-r06-task1-red.json CMD='GOWORK=off go -C deps/agent-native test ./runtime/llm -run TestClientBodyBoundary -count=1' make evidence`,来源为 SELF。每次换一个未使用的收据路径。独立审阅重跑为 REPLAY;准确 HEAD CI 为 GATE。正式用户验收与所有 PR 合入仍在后续阶段,八个 PR 维持草稿。 + +## Review Focus + +以下五类容易被“有 EOF/有 Usage”掩盖的输入必须由相应任务的测试直接覆盖: + +1. 声明长度大于实际响应体:`TestClientBodyBoundaryShortRead`(Task 1)应记读取错误或长度不符,仍零工具。 +2. 自动 gzip 解压使线上长度不可直接比:`TestClientBodyBoundaryUncompressed`(Task 1)应记长度关系 `unknown`,不能伪造匹配。 +3. 合法 `[DONE]` 在客户端未读到 HTTP EOF 前出现:`TestClientDoneDoesNotNeedBodyEOF`(Task 1)应维持原有合法工具终态,不把新观测变成额外放行门槛。 +4. Usage 后出现注释、空 `data:`、未知非 JSON 或半帧:`TestSSETailStructure`(Task 2)只记结构类别,不泄漏原文;缺 `[DONE]` 的工具仍零释放。 +5. 上游或测试注入未知枚举、负数、超上限计数:`TestEvaluationTransportObservationSanitized`(Task 3)应归 `unknown`/钳制计数,不让供应商可控文字入评测记录。 + +## File Ownership Map + +| 文件/组件 | 单一职责 | +|---|---| +| Agent-Native `runtime/llm/body_observation.go`、`client.go` | 只观察 HTTP 响应体读取与有限传输类别,不碰 SSE 内容或工具准入 | +| Agent-Native `runtime/llm/sse.go`、`types.go` | SSE 尾部结构、现有 `[DONE]` 门禁及一个终态观察合同 | +| Backend `internal/server/work_checkpoint_eval_test.go` | 在既有受控评测记录边界深拷贝并白名单化 Agent-Native 标量 | +| Agent-Squad/Backend/Dev gitlinks | 使各消费方固定同一已提交 Native SHA;Dev 验证真实组合 | +| Frontend 既有 Playwright 文件 | 只重放用户可见状态;本计划不修改前端生产代码 | + +--- + +### Task 1: Agent-Native 流式 HTTP 响应体观察 + +**Files:** +- Create: `deps/agent-native/runtime/llm/body_observation.go`(只计数和归类,不缓冲响应内容) +- Modify: `deps/agent-native/runtime/llm/types.go`(`TerminalObservation.BodyBoundary` 加法字段) +- Modify: `deps/agent-native/runtime/llm/client.go`(在既有响应上附观察,不改请求体/重定向/重试) +- Test: `deps/agent-native/runtime/llm/client_test.go` + +**Interfaces:** `TerminalObservation.BodyBoundary *BodyBoundaryObservation`;`BodyBoundaryObservation` 只包含 `ProtocolClass`(`http1|http2|other`)、`FramingClass`(`known_length|chunked|http2_stream|close_delimited|unknown`)、`DeclaredLengthKnown`、饱和的 `BytesRead`、`BytesSaturated`、`EOFSeen`、`LengthRelation`(`match|mismatch|unknown`)及 `Uncompressed`。`ParseSSE(ctx, io.Reader)` 的签名、`Event.StreamComplete` 和工具门禁不变;独立调用 `ParseSSE` 时 `BodyBoundary=nil`。 + +- [ ] **Step 1: 写 RED。** 在 `client_test.go` 增加上述三个 Review Focus 测试与 `TestClientBodyBoundaryChunkedAndHTTP2`。用 `httptest.NewServer` 显式设置正确/过长 `Content-Length`,用 `Flush` 生成 chunked,`httptest.NewUnstartedServer`+`EnableHTTP2`+`StartTLS` 生成 HTTP/2;gzip 用 `gzip.NewWriter` 返回压缩体。`TestClientDoneDoesNotNeedBodyEOF` 复用本文件已有的 `terminalRoundTripFunc`,返回含 `[DONE]`、但不要求随后读取到 `io.EOF` 的本地 `http.Response`,避免依赖真实网络调度。工具帧为本地固定 JSON;无 `[DONE]` 的用例必须同时断言 `len(end.Message.ToolCalls)==0`、返回 Usage 不丢、HTTPStatus 200。合法 `[DONE]` 用例必须保留当前可释放的 ToolCalls。测试只用本地假响应,不使用保护路由。 + +```go +// 关键断言形状;同一表驱动覆盖已知长度、chunked、HTTP/2、gzip、短读。 +if end.TerminalObservation == nil || end.TerminalObservation.BodyBoundary == nil { + t.Fatal("missing body boundary observation") +} +body := end.TerminalObservation.BodyBoundary +if body.FramingClass != wantFraming || + body.LengthRelation != wantLengthRelation || body.EOFSeen != wantEOF { + t.Fatalf("wrong body boundary: %+v", body) +} +if !wantDone && (end.StreamComplete || len(end.Message.ToolCalls) != 0) { + t.Fatalf("unfinished tool stream was released: %+v", end) +} +``` + +- [ ] **Step 2: 记录 RED。** 在 Dev 工作树运行 `GOWORK=off go -C deps/agent-native test ./runtime/llm -run '^(TestClientBodyBoundary.*|TestClientDoneDoesNotNeedBodyEOF)$' -count=1`,由 Dev `make evidence` 保存原始 SELF 失败;预期因 `BodyBoundary` 字段/分类尚不存在而编译失败,不改测试绕过。 +- [ ] **Step 3: 最小 GREEN。** 在 `types.go` 定义上面的导出结构;`body_observation.go` 用 `io.Reader` 包装 `resp.Body`,仅对 `Read` 返回的 `n` 累加,饱和上限 `32*1024*1024+1`,记录 `io.EOF`,绝不复制 `p[:n]`。以 `resp.ProtoMajor`、`ContentLength`、`TransferEncoding`、`Close` 和 `Uncompressed` 归类;已知长度只在未自动解压、未饱和且读到 EOF 时比较,其他情况记 `unknown`。`client.run` 对一次 `http.Client.Do` 的成功响应把计数器传给 `ParseSSE`,只在终态事件附上快照;HTTP 错误/请求前错误仍保留原错误分类。 + +```go +type BodyBoundaryObservation struct { + ProtocolClass, FramingClass string + DeclaredLengthKnown bool + BytesRead int64 + BytesSaturated, EOFSeen bool + LengthRelation string + Uncompressed bool +} +// 现有 TerminalObservation 追加:BodyBoundary *BodyBoundaryObservation `json:"bodyBoundary,omitempty"` +``` + +- [ ] **Step 4: 跑 GREEN 与边界。** 重跑 Step 2;再从 Dev 运行 `GOWORK=off go -C deps/agent-native vet ./runtime/llm` 与 `GOWORK=off go -C deps/agent-native test ./runtime/llm -count=1`。补一例关闭分隔/无法可靠判断的响应,必须为 `unknown`;检查取消、HTTP 4xx、重定向和读取失败都不产生虚假的 `EOFSeen=true`。`resp.Body.Close` 的结果不得被解释成业务流已完成。 +- [ ] **Step 5: 提交 Agent-Native Task 1。** 仅提交列出的结构、读取包装和测试,英文祈使句主题及 Why/Evidence/Impact/Refs 正文;推送现有 Agent-Native 草稿 PR #46,核对准确组件 HEAD 的 CI。此时 `BodyBoundary` 仍不影响工具执行。 + +### Task 2: Agent-Native SSE 尾部结构分类 + +**Files:** +- Modify: `deps/agent-native/runtime/llm/types.go`(`TailEventKind`、`TailEventCount`) +- Modify: `deps/agent-native/runtime/llm/sse.go`(在现有 reader/解析器内部统计结构,不存 data 内容) +- Test: `deps/agent-native/runtime/llm/sse_test.go` + +**Interfaces:** `TerminalObservation.TailEventKind` 为 `done_literal|json_frame|empty_data|comment|unknown_non_json|partial_frame|unknown`,`TailEventCount` 饱和到 `10000`。它们与 Task 1 的 `BodyBoundary` 并列,不能设置 `StreamComplete` 或绕过 `toolFinishExecutable`。`sseEventReader.next()` 可在内部增观察状态,但不改公开 `ParseSSE` 签名。 + +- [ ] **Step 1: 写 RED。** 新增 `TestSSETailStructure` 表驱动假流:合法 JSON+Usage 后分别接注释、空 `data:`、`data: NOT_DONE`、截断 `data: {`;另加 `[DONE]` 与纯文本无 `[DONE]` 正常对照。每例断言精确尾部枚举、计数范围、`ReadOutcome`、`StreamComplete` 和已返回 Usage;缺 `[DONE]` 或坏帧时工具调用零释放,合法 `[DONE]` 工具对照仍释放一条但本测试不执行工具。序列化观察不得含 `PRIVATE_TAIL_R06` 哨兵。`TestSSETailCountSaturates` 连续发超过 10000 个无内容注释并检查计数不溢出。 + +| 两个 JSON 帧后的尾部(纯文本例只有一个 JSON 帧) | `TailEventKind` | `TailEventCount` | `ReadOutcome` | +|---|---|---:|---| +| `[DONE]` | `done_literal` | 3 | `done_marker` | +| 直接 EOF | `json_frame` | 2 | `clean_eof` | +| 单独注释后 EOF | `comment` | 3 | `clean_eof` | +| 空 `data:` | `empty_data` | 3 | `invalid_frame` | +| `data: NOT_DONE` | `unknown_non_json` | 3 | `invalid_frame` | +| 截断 `data: {` | `partial_frame` | 3 | `invalid_frame` | +| 单个纯文本 JSON 后 EOF | `json_frame` | 1 | `clean_eof` | + +```go +got := end.TerminalObservation +if got == nil || got.TailEventKind != wantKind || got.TailEventCount != wantCount { + t.Fatalf("wrong tail observation: %+v", got) +} +raw, err := json.Marshal(got) +if err != nil || strings.Contains(string(raw), "PRIVATE_TAIL_R06") { + t.Fatalf("unsafe tail observation: %s err=%v", raw, err) +} +if wantToolRejection && len(end.Message.ToolCalls) != 0 { + t.Fatal("tail evidence released an incomplete tool") +} +``` + +- [ ] **Step 2: 记录 RED。** 从 Dev 运行 `GOWORK=off go -C deps/agent-native test ./runtime/llm -run '^(TestSSETailStructure|TestSSETailCountSaturates)$' -count=1`;用 Dev evidence 收据保存失败,预期新字段缺失。 +- [ ] **Step 3: 最小 GREEN。** `sseEventReader` 在完成一个结构事件时更新有限 `tailKind` 和饱和 `tailCount`;`emitParsed` 在成功 JSON、字面 `[DONE]`、空 data、非 JSON 与 JSON 末尾截断时归类。仅以 `json.SyntaxError` 的类别/结束位置区分 `partial_frame` 与其他 `unknown_non_json`,不能记录错误原句或帧字节。注释在 reader 中计数但继续保持原忽略行为;超出计数上限只饱和。`observation()` 把标量送到原终态事件,绝不修改 `sawDone` 的唯一设置点。 + +```go +const maxObservedTailEvents = 10000 +func noteTail(kind string, count *int, last *string) { + if *count < maxObservedTailEvents { *count = *count + 1 } + *last = kind +} +// sawDone 仍仅在 data == "[DONE]" 分支设为 true。 +``` + +- [ ] **Step 4: 跑 GREEN 与全量组件回归。** 重跑 Step 2,另外从 Dev 运行 `GOWORK=off go -C deps/agent-native vet ./...` 与 `GOWORK=off go -C deps/agent-native test -count=1 ./...`,保留现有多片工具参数、缺 `[DONE]`、读错、取消、纯文本与合法 `[DONE]` 正反例。检查单次流只发一个终态,且新增字段不带未知文本。 +- [ ] **Step 5: 提交并推送 Agent-Native Task 2。** 只提交本单元差异;当前 Agent-Native HEAD 成为 Backend/Agent-Squad 的唯一嵌套 pin,等待其准确 HEAD CI。若本地假流暴露解析器确实漏认合法结束形式,仅记录发现并回到方案评审,不能顺手放宽工具门禁。 + +### Task 3: Backend 无内容归一化与失败后停止 + +**Files:** +- Modify: `modules/backend/deps/agent-native`(固定 Task 2 Agent-Native SHA) +- Modify: `deps/agent-squad/deps/agent-native`、`modules/backend/deps/agent-squad`(先固定 Agent-Squad 的相同 Native SHA,再由 Backend 消费) +- Modify: `modules/backend/internal/server/work_checkpoint_eval_test.go`(`evalTerminalObservation` 白名单复制) +- Test: `modules/backend/internal/server/work_checkpoint_eval_budget_test.go`(现有 `live` tag、本地 fake provider) + +**Interfaces:** 消费 Task 1/2 的 `TerminalObservation.BodyBoundary`、`TailEventKind`、`TailEventCount`,只写入既有 `evalCallRecord.TerminalObservation`;`evalCallRecord` 的 run ID/调用号/Usage/failureClass 及 Backend 预算停止逻辑不变。必须深拷贝嵌套观察并将所有字符串缩至已定义枚举、负数钳为 0、过大计数钳至 Agent-Native 上限。没有公开 API、数据库或生产模型路由改动。 + +- [ ] **Step 1: 固定消费依赖。** 在 Agent-Squad 草稿分支将 `deps/agent-native` 固定到 Task 2 的提交,从 Dev 运行 `GOWORK=off go -C deps/agent-squad vet ./...`、`GOWORK=off go -C deps/agent-squad test -count=1 ./...`,提交并推送 PR #53。随后在 Backend 草稿分支将嵌套 `deps/agent-native` 与 `deps/agent-squad` 固定到这两个**已提交**的 SHA;用 `git rev-parse HEAD` 分别核对组件及嵌套指针,不以游离工作树代码作为依赖。Dev 的完整 `make test-pins` 留到 Task 4 暂存最新三条主 gitlink 后再运行。 +- [ ] **Step 2: 写 RED。** 在 `work_checkpoint_eval_budget_test.go` 新增 `TestEvaluationTransportObservationSanitized`:fake provider 传入 `TailEventKind="PRIVATE_TAIL_R06"`、`ProtocolClass="PRIVATE_HEADER_R06"`、负数/超上限计数和含正文的终态,同时给出 Usage 17;断言新 `provider-records.json` 只有归一后的 `unknown`、0/上限、run ID 和 Usage,不含哨兵、原始 `messages`/`terminal`。再加 `TestEvaluationTransportEvidenceStopsAfterUnmarkedToolEOF`:fake provider 仿照当前 Agent-Native 的失败终态发 `KindMessageEnd`、`StopReason=StopError`、非 nil `Err`、`FinishReason=tool_calls`、Usage17、`ReadOutcome=clean_eof`、`StreamComplete=false`、空 ToolCalls;第二次请求被拒,provider 调用数仍 1、落盘 Usage17、工具 0。沿用现有 `TestEvaluationStopsAfterMalformedToolTerminal` 的停止构造。 + +```go +clean := evalTerminalObservation(terminal.TerminalObservation) +if clean.TailEventKind != "unknown" || clean.BodyBoundary.ProtocolClass != "other" || + clean.TailEventCount < 0 || clean.BodyBoundary.BytesRead < 0 { + t.Fatalf("unbounded observation: %+v", clean) +} +if strings.Contains(string(recordJSON), "PRIVATE_TAIL_R06") || providerCalls.Load() != 1 { + t.Fatal("content leaked or a failed terminal caused another request") +} +``` + +- [ ] **Step 3: 记录 RED。** 只运行本地假 provider:从 Dev 执行 `GOWORK=off go -C modules/backend test -tags live ./internal/server -run '^(TestEvaluationTransportObservationSanitized|TestEvaluationTransportEvidenceStopsAfterUnmarkedToolEOF)$' -count=1`,用 Dev evidence 保存原始失败。**不得**运行宽泛 `go test -tags live ./...`、`TestServeWorkCheckpointEval` 的 `model` 模式或任何受保护路由。 +- [ ] **Step 4: 最小 GREEN。** `evalTerminalObservation` 从输入构造新 `TerminalObservation`,分别白名单 `ReadOutcome`/`TailEventKind`、BodyBoundary 的 `ProtocolClass`/`FramingClass`/`LengthRelation`,再复制布尔与钳制计数;未知协议类归 `other`,未知其他枚举归 `unknown`。不给 `evalCallRecord` 加原始 Event、header/trailer 或错误消息。先持久化 Usage 与失败记录,再按现有 gate 放行终态;记录失败仍阻止后继调用。 + +```go +func clampCount(value, limit int64) int64 { + if value < 0 { return 0 } + if value > limit { return limit } + return value +} +// 对 *observation.BodyBoundary 深拷贝后逐字段白名单,不保留输入指针。 +``` + +- [ ] **Step 5: 跑 GREEN 与控制组。** 重跑 Step 3,并从 Dev 运行 `GOWORK=off go -C modules/backend test -tags live ./internal/server -run '^(TestEvaluationRecordsContentFreeTerminal|TestEvaluationRedactsProviderControlledTerminalLabels|TestEvaluationStopsAfterMalformedToolTerminal|TestEvaluationKeepsUsageOnTerminalFailure|TestEvaluationStopsWhenProviderRecordUnavailable)$' -count=1`;再运行 Backend `GOWORK=off go -C modules/backend vet ./...`、`GOWORK=off go -C modules/backend test -count=1 ./...`。`go test -tags live` 的每条命令都必须有上述**精确锚定的 `-run`**,不触发真实模型样本。 +- [ ] **Step 6: 提交 Backend。** 将已固定的 Agent-Native/Squad gitlink 与归一化代码、测试一起提交到现有 Backend 草稿 PR #193;核对 Backend 真实 HEAD、Agent-Native/Squad 嵌套 pin 和组件 CI,保持两个消费方使用同一 Native SHA。 + +### Task 4: 组合重放、独立审阅与阶段 6 候选 + +**Files:** +- Modify: Dev `deps/agent-native`、`deps/agent-squad`、`modules/backend` gitlinks +- Modify: Dev `.task/feature-fast-slow-u2-u3-plan.md`、`PROJECT.md` 和 Documents `docs/reports/2026-10-13-fast-slow-u2-u3-validation.md`(只记录实际结果) +- Test without product edits: `modules/frontend/e2e/work-checkpoints.playwright.ts` 的现有宿主确认、失败和来源场景 + +**Interfaces:** 固定 Task 1–3 的精确 Agent-Native/Agent-Squad/Backend SHA;不变更 Frontend/UI/Core 的公开契约或代码。Dev、Backend 和 Squad 的嵌套 Agent-Native gitlink 必须同值,Backend 嵌套 Squad 指针与 Dev 同值;平台 PR 都保持草稿。任务自有 HTTP 机制实例必须 `WORK_CHECKPOINT_SERVER_MODE=mechanism`,不得接入业务实例或真实模型。 + +- [ ] **Step 1: 固定 pin 并跑组合。** Agent-Native → Agent-Squad/Backend → Dev 按依赖顺序提交并推送;Dev 暂存实际 gitlink 后用 evidence 分别运行 `make test-pins`、`make test`、`make build`。文档记录准确 SHA、原始退出码及来源,任何子仓未提交的工作树差异不能冒充 pin。若上游主干推进,按 Dev `make task-sync` 解决冲突并据影响重跑。 +- [ ] **Step 2: 建立任务自有机制实例。** `modules/frontend/e2e/work-checkpoints.playwright.ts` 与 `TestServeWorkCheckpointEval` 都要求数据目录名含 `fast-slow-u2-u3-`。先验证 `/tmp/fast-slow-u2-u3-r06-20261014` 不存在,创建 0700 目录;在 Dev 工作树执行下面的命令。`mode=mechanism` 使用代码内 fake stream;`env -u` 确保旧模型路由和 A01 标签不被继承。将启动命令与 PID 写入 SELF 收据,不把 server 日志中的模拟内容当成供应商证据。 + +```bash +R06_RUN_ROOT=/tmp/fast-slow-u2-u3-r06-20261014 +mkdir -m 0700 "$R06_RUN_ROOT" +GOWORK=off go -C modules/backend test -tags live -c -o "$R06_RUN_ROOT/server.test" ./internal/server +R06_DEV_SHA=$(git rev-parse HEAD) +R06_BACKEND_SHA=$(git -C modules/backend rev-parse HEAD) +env -u WORK_CHECKPOINT_ROUTE_FILE -u WORK_CHECKPOINT_EXTRA_A01 \ + WORK_CHECKPOINT_SERVER_MODE=mechanism \ + WORK_CHECKPOINT_SERVER_DATA="$R06_RUN_ROOT/mechanism-data" \ + WORK_CHECKPOINT_SERVER_MANIFEST="$R06_RUN_ROOT/manifest.json" \ + WORK_CHECKPOINT_MECHANISM_NOTICES=1 \ + WORK_CHECKPOINT_DEV_SHA="$R06_DEV_SHA" WORK_CHECKPOINT_BACKEND_SHA="$R06_BACKEND_SHA" \ + "$R06_RUN_ROOT/server.test" -test.run '^TestServeWorkCheckpointEval$' \ + >"$R06_RUN_ROOT/server.log" 2>&1 & +R06_SERVER_PID=$! +printf '%s\n' "$R06_SERVER_PID" >"$R06_RUN_ROOT/server.pid" +chmod 0600 "$R06_RUN_ROOT/server.pid" +``` + +- [ ] **Step 3: 验证实例身份并启动预览。** 从任务自有 `server.pid` 读取 `R06_SERVER_PID`;用 `python3 -c 'import json; print(json.load(open("/tmp/fast-slow-u2-u3-r06-20261014/manifest.json"))["base"])'` 取本地 API;核对 manifest PID、`binary` 与 `/proc/$R06_SERVER_PID/exe`、`binarySHA256` 与 `sha256sum`、Dev/Backend SHA 与当前 HEAD。另分别用 `git -C deps/agent-native rev-parse HEAD`、`git -C deps/agent-squad rev-parse HEAD` 及 Backend 嵌套 gitlink 对照 Dev pin;当前 manifest 不包含 Native/Squad 字段,不能声称它自己已核对这两个 SHA。确认 `127.0.0.1:4186` 未被占用后运行下面的 preview;若端口被占,停在本步骤并选一个新的任务自有端口,记录实际值,不连接已有服务。 + +```bash +R06_RUN_ROOT=/tmp/fast-slow-u2-u3-r06-20261014 +R06_SERVER_PID=$(cat "$R06_RUN_ROOT/server.pid") +for R06_WAIT in $(seq 1 80); do + test -s "$R06_RUN_ROOT/manifest.json" && break + sleep 0.25 +done +test -s "$R06_RUN_ROOT/manifest.json" +R06_API_BASE=$(python3 -c 'import json; print(json.load(open("/tmp/fast-slow-u2-u3-r06-20261014/manifest.json"))["base"])') +python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",4186)); s.close()' +PLAYWRIGHT_API_TARGET="$R06_API_BASE" npm --prefix modules/frontend run preview -- --host 127.0.0.1 --port 4186 --strictPort \ + >"$R06_RUN_ROOT/preview.log" 2>&1 & +R06_PREVIEW_PID=$! +printf '%s\n' "$R06_PREVIEW_PID" >"$R06_RUN_ROOT/preview.pid" +chmod 0600 "$R06_RUN_ROOT/preview.pid" +for R06_WAIT in $(seq 1 80); do + curl -fsS -o /dev/null http://127.0.0.1:4186/ && break + sleep 0.25 +done +curl -fsS -o /dev/null http://127.0.0.1:4186/ +``` + +- [ ] **Step 4: 跑浏览器并清理任务实例。** 在 Frontend 工作树运行下面的命令,核对可信首回合、零/部分失败、来源、刷新和 390px 窄屏;不改变现有 UI 文案、焦点或 seq 投影。使用 Dev `make evidence` 的源码外收据捕获完整输出,保存 trace;核对 `server.pid`、`preview.pid` 和 `/proc` 身份后,只停止这两个任务自有进程。 + +```bash +PLAYWRIGHT_BASE_URL=http://127.0.0.1:4186 \ + WORK_CHECKPOINT_HTTP_MANIFEST=/tmp/fast-slow-u2-u3-r06-20261014/manifest.json \ + npx playwright test e2e/work-checkpoints.playwright.ts --reporter=list +``` + +- [ ] **Step 5: 隔离 PG 与 CI。** Dev 必需 `test-pg` 作业在新容器中启动真实 PostgreSQL/Redis,执行 `make test-backend-pg` 和双进程生命周期;不得把 SQLite 或 SKIP 当成 PG。先核对 Agent-Native #46、Agent-Squad #53、Backend #193 以及 Dev #512 的准确 HEAD CI,再核对 Dev 的 `test`、`test-pg`、`svc-realcombo` 三项作业和全部步骤在**同一最新 Dev HEAD** 成功,记录 GATE 运行链接、runner 和 SHA。若本地另起隔离 PG,先查数据根/实例 PID 与 DSN 身份,不能连接现有 8788 实例。 +- [ ] **Step 6: 独立复审。** 未参与实现的审阅者对固定候选重跑 Task 1 的已知长度/gzip/DONE 对照、Task 2 的尾部结构、Task 3 的脱敏/停止最小集,先读命令×SHA×输出再读作者解释;审阅重点是正常 HTTP 结束不被写成 SSE 完成、未知类别不落原文、JSON 对象不被说成 schema 有效、缺 `[DONE]` 零工具且 Usage 保留。Critical/Important 闭合并在实际新 HEAD 补测之前,不提交阶段 6 结论。 +- [ ] **Step 7: 提交阶段 6 人工评审材料。** 任务卡与 Documents 核验报告映射 r0.6 设计条款 → 文件/测试 → SELF/REPLAY/GATE 证据,写明真实 MiniMax 再测和 r0.5 A01 `NOT_RUN`、r0.4 FAIL、当前路由原因仍可能 `unknown`。给用户审阅零付费机制候选;即使阶段 6 通过,仍须单独资源提案才可进行新的真实请求,再经阶段 7 人工验收。安全/质量失败时八个 PR 继续草稿,不合入或部署。 + +## Plan Self-Review And Stop Conditions + +| r0.6 设计条款 | 实施/验证归属 | +|---|---| +| HTTP 响应体协议、定界、读数、EOF 与未知处理 | Task 1 的 `BodyBoundary`、已知长度/gzip/chunked/HTTP2/DONE 对照 | +| SSE 尾部结构、不泄漏未知帧、缺 `[DONE]` 继续拒绝 | Task 2 的 `TailEventKind`、饱和计数和无标记反例 | +| 实际 run ID/Usage、有限白名单、失败后零后继请求 | Task 3 的归一化与 fake provider 拒绝测试 | +| 普通用户界面、来源、失败状态和 seq 不回退 | Task 4 的任务自有 HTTP/Playwright 与准确 HEAD 组合 | +| 供应商契约不足时不放宽、真实 A01 另审 | Global Constraints、Task 4 阶段 6 材料与以下停止条件 | + +Task 1 拥有 HTTP 读取边界,Task 2 拥有 SSE 结构,Task 3 拥有持久评测白名单,Task 4 只固定真实组合和用户链回归;没有第二个异步状态 owner、公开 schema 变更或额外模型回合。五项 Review Focus 都指向各任务的命名测试。 +- 发现 Go `net/http` 无法可靠暴露某种原始传输分隔方式时,记录 `unknown` 与零付费证据,不读取原始网络字节“补齐”;发现供应商另有终止形式时仅记录事实,**不**在本计划中放宽工具门禁。需要新的路由、公开字段、数据库迁移、权限、模型预算或 UI 变更时,停止受影响单元并返回设计/计划人工重审。 +- 本计划的交付终点是零付费、可审阅的阶段 6 候选。当前用户只批准**写计划**,Task 1–4 的代码、浏览器/PG 重放、真实模型对照和合入均 `NOT_RUN`。书面计划获阶段 5 人工批准后,按既有任务 worktree 同会话实施;任何真实 provider 请求仍须候选固定和单独资源批准。 diff --git a/docs/proposals/INDEX.md b/docs/proposals/INDEX.md index 7684538..baa5318 100644 --- a/docs/proposals/INDEX.md +++ b/docs/proposals/INDEX.md @@ -36,6 +36,12 @@ 运行卡呈现状态、已完成工作、当前发现与剩余项;四层阅读、阶段成果、来源、异常恢复及 U1–U4。 2026-10-12 方案设计已批准(阶段 1–4);首批仅 U1;[U1 实施计划 r1](2026-10-12-fast-slow-channel-display-u1-plan.md) 待阶段 5 批准;业务实施和运行验收 NOT_RUN。 +- [快慢通道 U2/U3 实施计划 r0.1](2026-10-13-fast-slow-u2-u3-plan.md):部分收口:阶段 5 已批准,零付费机制(含 r0.6)已合入;真实模型质量未解决(r0.4 A01 FAIL,r0.5/r0.6 A01 NOT_RUN),未部署。详见[核验报告](../reports/2026-10-13-fast-slow-u2-u3-validation.md)。 +- [U3 r0.5 安全终态证据与可信快通道反馈设计](2026-10-14-fast-slow-u3-r05-terminal-evidence-and-fast-feedback-design.md):书面设计、[详细实施计划](2026-10-14-fast-slow-u3-r05-terminal-evidence-and-fast-feedback-implementation-plan.md)及零付费阶段 6 已获批准;唯一另行授权的短工具流探针返回 513 Usage、`clean_eof`、缺 `[DONE]`,工具零执行且授权已用尽。A01 质量仍 NOT_RUN,详见[核验记录](../reports/2026-10-13-fast-slow-u2-u3-validation.md)。 +- [U3 r0.6 当前路由的传输完成性证据设计](2026-10-14-fast-slow-u3-r06-transport-completeness-design.md):书面设计与[详细实施计划](2026-10-14-fast-slow-u3-r06-transport-evidence-implementation-plan.md)已获批准,零付费实施通过阶段 6 并合入;唯一获批的一次性传输边界探针(472 Usage、HTTP/2 干净 EOF、缺 `[DONE]`、工具零执行)判定 INCONCLUSIVE,A01 不具备资格,未部署。 +- [U3 质量修订设计 r0.2](2026-10-13-fast-slow-u3-quality-revision.md):三处修订方向和书面设计均已批准,实施计划待人工评审;原样本质量及停止边界仍 FAIL,无新付费样本授权。 +- [U3 质量修订实施计划 r0.2](2026-10-13-fast-slow-u3-quality-implementation-plan.md):书面设计已批准,阶段 5 计划待人工评审;Task 1–5 为零付费实施与验证,Task 6 的真实模型样本另需明确资源授权。 + Generative UI 方向已收口(U1–U7 全部落地,2026-10-12):[使用说明与评测记录](2026-10-12-generative-ui-usage-and-evaluation.md)(final); 主记录 [r2](2026-10-10-generative-ui-plugin.md) 与 [U5 动作范围 r2](2026-10-11-generative-ui-u5-action-scope.md) 保留为过程权威。 diff --git a/docs/reports/2026-10-13-fast-slow-u2-u3-code-review.md b/docs/reports/2026-10-13-fast-slow-u2-u3-code-review.md new file mode 100644 index 0000000..4e2ab04 --- /dev/null +++ b/docs/reports/2026-10-13-fast-slow-u2-u3-code-review.md @@ -0,0 +1,135 @@ +> 独立审阅者 /root/fast_slow_final_review 的原件归属:`/tmp/fast-slow-u2-u3-20261013/review/`;原始重放日志在该目录。当前代码审阅PASS,模型质量FAIL;未复制评测材料或凭据。 + +# Independent fix re-review + +Reviewer `/root/fast_slow_final_review`, 2026-10-13. Read-only review; no source edits, deployment or paid model calls. Reviewed Backend `3d94c5df1a20bb6b2351a4cf353699bed77345a8..4a0418e211a260e89fe9ed79764138733ab7350c`, Frontend `c63f6c74707dc57fcdd1ed96e6a25cce32e7d61a..861c45edb5729a9ea1e08a1d53c5fa90cda15531`; Core/UI unchanged from original review. Root delivery identity supplied as d25bd8a plus evidence-only updates; final CI/merge identity remains the executor gate. + +## Previous findings and supplemental startup review + +- R1 deleted artifact indexing: CLOSED. Both lexical and vector cleanup calls restored after authoritative deletion. Independently replayed lexical deletion, and vector-row/retrieval deletion on isolated PG. +- R2 queued notice revalidation: CLOSED. Delivery validates task/attempt/conversation and uses current visibility-filtered title/summary before writing the system message and emitting results. Memory and persisted notice regressions pass. +- R3 same-name plugin: CLOSED. `subRun.localCheckpoint` records actual builtin registration; only that path intercepts the name. Plugin dispatch otherwise remains normal. Repro passes. +- R4 preview race: CLOSED. Async source open is owned by store and fenced by conversation epoch and explicit preview intent; later source opens, navigation, tab selection and close invalidate pending responses. Three added timing/navigation tests pass. +- Startup wiring: CLOSED for inspected/replayed paths. Recovery follows complete policy/CardSvc/budget/command/usage/PG event wiring and precedes public listen. Command consumption begins after recovery. Actual subprocess startup test confirms orphan queued Task is interrupted before HTTP readiness and disabled project mirroring stays disabled. +- Bound legacy child resume: CLOSED. Canonical task run IDs excluded, binding query failure stops automatic resume. Bound child test and existing unbound legacy/recovery-hold tests pass. +- SSE readiness: subscription now precedes readiness flush; consistent with avoiding a ready-before-subscribed gap. Source inspected; no separate new live-SSE replay claimed by this reviewer. +- Source catalog duplication: removed from the higher-level builder while retaining the original one; no source identity change. + +## Independent REPLAY + +Raw logs record exact command and object. These are reviewer REPLAY, not GATE. + +1. Backend SQLite `4a0418e211a260e89fe9ed79764138733ab7350c`: `go test ./internal/store ./internal/runmgr ./internal/runtime ./internal/app -run 'TestReviewDeletedArtifactMustLeaveKnowledge|TestReviewQueuedNoticeMustRevalidateSource|TestReviewCheckpointNameMustNotInterceptPlugin|TestTaskStartupReconcilesQueueBeforeServing|TestTaskBoundInterruptedChildDoesNotRespawn|TestResumeInterruptedChildrenRespawns|TestRecoveryHold' -count=1 -v` — exit 0, `backend-fixes.log`. +2. Same Backend, isolated PG `127.0.0.1:39334/fast_slow_test`, Redis `127.0.0.1:39335`: `go test ./internal/store ./internal/runmgr -run 'TestReviewDeletedArtifactMustLeaveKnowledge|TestDeletedArtifactClearsVectorRowsAndRetrieval|TestReviewQueuedNoticeMustRevalidateSource|TestTaskBoundInterruptedChildDoesNotRespawn|TestTaskReconcileOwnedQueueAndForeignLease' -count=1 -v` — exit 0, `backend-fixes-pg.log`. +3. Frontend `861c45edb5729a9ea1e08a1d53c5fa90cda15531`: `./node_modules/.bin/tsc --noEmit && ./node_modules/.bin/vitest run test/work-state.test.ts` — exit 0, `frontend-fixes.log`. + +## New finding from real evidence + +Important R5 — terminal card selects execution commentary instead of the final result. + +- `internal/runmgr/runmgr.go:1813` assembles `fullText := partsText(subParts)`. `partsText` at approximately line 2396 includes PartText with `Process=true`. `internal/runmgr/task_projection.go:340` then selects the first short paragraph from that combined text for the conclusion. +- Actual persisted candidate A01 card event 544/revision6 is completed, but conclusion says it will search knowledge, publish a phase result, then output the full analysis. These are earlier execution notes followed by a heading, not a result. The same text reaches the task-bound notice. +- Raw evidence: `candidate-A01-persisted-events.json`, event 544; original subagent assistant message `run_21039c462892` has two process text parts before its 2829-character final non-process result. +- Fix: separate final non-process answer text from the full execution transcript when building the completion payload; derive a faithful complete result paragraph or clearly neutral fallback from that final answer only. Preserve the process transcript for expanded reading. Test pre-tool notes + final answer, heading-prefixed final answer, and long numeric/date-containing conclusions without arbitrary truncation. + +## Declined to judge + +- Revised prompts/harness edits after Backend4a0418e: not part of this fixed review object; require their own affected review/replay. +- Candidate A02/A04 model quality: not yet present in the supplied evidence at this review time. +- Ten-second user readability and full real browser acceptance: not independently browser-replayed here. +- Normal notification-model response quality: controlled comparison suppresses it equally; this review checks notice content mechanics. +- Billing charges: provider token reports can be reconciled, actual monetary billing was not supplied or accessed. +- Existing services/deployment, final CI/platform admission and cleanup: outside this reviewer execution scope. + +## Verdict + +Original four blockers and startup defect are resolved on the named component SHAs. **Overall ready to merge: No.** R5 remains a user-visible correctness issue; real-model A01 acceptance also fails and cannot be relabeled as passing because quotes exist or mechanism tests pass. See `model-assessment.md` for independent claim-by-claim assessment and comparison limitations. + + +## Follow-up on Backend 3e330e3d37293556d6ca475e251748bd1a396cd4 + +Inspected the complete 4a0418e..3e330e3 delta. Canonical completion now builds its payload from non-Process parts while retaining all parts in the persisted transcript; empty final body gets an honest fallback. **R5 is CLOSED on this SHA.** Independent command `go test ./internal/runmgr -run 'TestTaskConclusionSkipsProcessText|TestTaskCheckpoint|TestMaterial' -count=1 -v` exited0; full output/object recorded in `conclusion-recipe-fix.log`. + +The new recipe explicitly distinguishes total/phase scope, inferred arithmetic/source fact, proposed/approved state, and unspecified numbering or unique claims. It requires natural-language visible labels and meaningful separate reading/crosscheck discoveries without inventing quantity. Recipe identity advances to v5, so stale v4 results are not reused. These are defensible changes addressing the observed failure; text inspection and mechanism tests do not establish model-quality success. + +Harness clears the Cards slice before each decode, preserving previously captured Task objects and preventing omitted fields from persisting into later responses. Remaining-case selection only accepts candidate A02,A04 and writes candidate-remaining.json. Original A01 remains untouched. Remaining allowance support is test-only; no production route/credential change reviewed. + +Updated code verdict: **No remaining identified code blocker on the inspected Backend3e330e3 / Frontend861c45e combination. Overall U3 acceptance and merge verdict remain pending/No because original A01 failed and revised A01 is not run; A02/A04 and final exact-head GATE results still require assessment.** This is not a claim that prompt refinement fixes the historical failed sample. + + +## Final minimal review — Backend ee65604d831caed768f6c798cda6122acb33f13d + +Reviewed the full f6b5360..ee65604 delta read-only; checkout was clean at verification. No paid model or credential use. + +- `material_record` admission now constructs Text only from host source labels and exact validated quotations, deduplicates identical label/quote lines, and clears model Impact/Assumptions. Source ownership, current-version witnesses, atomic projection and final64KiB validation remain in place. Raw candidate digest is computed before canonicalization, preserving same-ID/different-payload conflicts and idempotence. Interpretive kinds remain unchanged and do not acquire verified status. Recipev6 prevents reuse under the older recipe identity. +- The oversized-correction regression deliberately uses analysis text now, so canonicalization does not accidentally turn the old overflow test into an ordinary small-payload test. Correction history/fallback coverage remains meaningful. +- Evaluation wrapper now preserves any positive output cap below4000; it only supplies4000 when uncapped or above4000. The HTTP fixture confirms the actual request carries780 when production supplied780. This closes the fixture fidelity defect prospectively; it does not alter the interpretation of old A02 output obtained with the previous cap. + +Independent REPLAY: + +1. Isolated PostgreSQL: `go test ./internal/runmgr -run 'TestMaterialRecordCannotAddAnUnstatedApproval|TestTaskCheckpoint' -count=1 -v` — exit0; object and full raw output in `literal-record-pg.log`. +2. Local fake HTTP only: `go test ./internal/server -tags live -run '^TestEvaluationPreservesProductionOutputCap$' -count=1 -v` — exit0, subcases780→780,5000→4000,0→4000; raw output in `eval-cap-fix.log`. The live build tag does not imply a live-model request; this exact test uses httptest and fixture credentials only. + +Final code-review assessment on the inspected delta: **no remaining identified blocking code defect**. Original blockers, startup recovery, process-summary and evaluation-cap defects are closed on the reviewed sequence of SHAs. Remaining model limitations are explicitly retained: canonicalization prevents unsupported wording in newly admitted material_record findings, but it does not verify selected context, analysis/provisional/needs_user conclusions, final prose, or old persisted results. Previously observed stale nextStep text remains a readability limitation in the historical samples. + +**Overall U3 acceptance/merge verdict remains NOT PASSED / pending**: originalA01 failed; revisedA02 had semantic errors and cap-fidelity limitations; revisedA04 exhausted budget before final output. No additionalA01 run was executed by this reviewer or credited as evidence. A separately authorized new sample, if any, must be judged on its actual final recipe/cap and cannot retroactively rewrite the original failures. Final exact-head CI/platform admission remains the executor's gate. + + +## Terminal guidance follow-up — Backend 2a64432fc896e7a44d793fb8eb8b10c77fd5905d + +Inspected ee65604..2a64432. Normal completion/failure/cancellation through finishTaskProjection now replaces model NextStep with host-owned guidance; quota failure uses readable wording and precise execution exceptions are retained in Run.Error. No authority/schema/budget expansion. Independent `go test ./internal/runmgr -run 'TestFailedTaskDoesNotPromiseAutomaticContinuation|TestBackgroundCardLifecycle|TestTaskConclusionSkipsProcessText' -count=1 -v` exited0; `terminal-state-replay.log` contains exact object and full output. + +One same-scope omission remains on this SHA: `reconcileTaskProjections` at task_projection.go approximately297-309 separately sets interrupted but keeps copied NextStep. Orphan queued cards therefore still promise to start after capacity appears; interrupted running cards can still promise final output despite canonical automatic resume being disabled. Set host-owned stopped/request-again guidance on this recovery path as well and assert it in queue/startup recovery tests before claiming all terminal paths are covered. Reported to executor for this narrow correction. Original model acceptance status remains unchanged. + + +## Final recovery closure — Backend 6a2d85cbb9d1692dc2abf85ef59e3f5ff23748f0 + +Reviewed the narrow 2a64432..6a2d85c delta. `reconcileTaskProjections` now overwrites NextStep with host-owned stopped/request-again guidance. The existing recovery test asserts it, while retaining the foreign live PostgreSQL task check. **The terminal-recovery omission is CLOSED.** + +Independent PostgreSQL REPLAY: `go test ./internal/runmgr -run '^TestTaskReconcileOwnedQueueAndForeignLease$' -count=1 -v` — exit0 on exact Backend6a2d85c; full raw output in `final-recovery-nextstep.log`. No checkout edits, existing-service operations or paid calls were performed. + +The accompanying fake mechanism stream now chooses a real acceptance-condition quotation when available instead of supplying the same launch quotation for both material records. Frontend `4d1d64a..3087b0621cc1bf6c1ece39c5f4e148e24ef1cf59` changes only four browser expectation lines to match literal source text. No Frontend production behavior changes in that final delta. I inspected these changes; the executor's actual HTTP/browser replay was not rerun or represented as independent browser evidence here. + +**Final code-review verdict: PASS / no remaining identified blocking code findings for the reviewed U2/U3 change sequence**, ending Backend6a2d85c and Frontend3087b06, with unchanged Core6e38574 and UI3b34dfd. This is a code-review verdict, not overall delivery acceptance. The inherited login feature was reviewed only at the merge/startup boundary as recorded above. Root7fd5288/fresh-main4ad5b3c and Documents53a7d37 were supplied as delivery composition metadata; full root build/test/CI and planning closeout are executor gates, not independently asserted by this focused replay. + +**Overall U3 acceptance remains NOT PASSED / pending** on the six paid samples. OriginalA01 failure, revisedA02 semantic/fidelity limitations and revisedA04 budget-stopped final output remain documented without alteration. Additional failed-sample rerun approval has not been received or assumed by this reviewer. No code-review PASS should be used to state model acceptance, exact-head CI or merge approval is already complete. + + +## Post-disconnection recovery review — Backend e69865e11ba3f74887f92c2433b5ec37c881d0db / Core beafd7537a6770bd6888095b48926980fce487d8 + +Read-only review resumed at the current exact objects. Inspected zero-findings fix62a2ab4, upstream merge7855d82 and fixture adaptatione69865e. Checkout was clean; before/after object IDs stayed identical during all replays. + +The zero-accepted-findings failure copy is now truthful: only active findings count as available phase findings; absent active findings yield outcome none, explicit no-usable-findings conclusion, honest remaining work and a new-request suggestion without implying stored results. The exact budget exception remains in Run.Error. This closes the extraA01 zero-findings display finding prospectively; original failed sample remains unchanged. + +Merged coexistence is intact: Core retains TaskSnapshot/DocumentOrigin while adding SettingScope. Production app injects plugin schema, Cards, execution budgets, shared authorization, mirroring policy, command wiring and PG event wiring before recovery/public intake. SSE still subscribes before readiness, filters task source visibility on replay/live send, deduplicates seq, and now independently rechecks session validity on the upstream timer. No U2 task/source guard was lost in the merge. + +The isolated eval fixture now uses one settings service with trusted SetSystemOverride(config), shared by Manager/Server/plugin host. This is compatible with system-managed models/runtime.mock; it avoids silently stripped user overrides. It does not modify a real account setting or introduce a provider/credential change. Material intake thresholds and maxAttachmentChars remain normal effective scoped settings; original material revision logic consumes the effective cap. + +Independent REPLAY (all exit0, all exact before/after SHAs recorded): + +- `post-disconnect-sqlite.log`: Backend SQLite, `go test ./internal/runmgr ./internal/store ./internal/cardsvc ./internal/server ./internal/app ./internal/settings -run 'TestFailedTaskDoesNotPromiseAutomaticContinuation|TestTask|TestMaterial|TestCardsAtCursor|TestReviewQueuedNotice|TestSSE|TestMergeUserDottedKeyMaskAndNormalizeStored' -count=1 -v`. Covers zero findings, canonical queue/recovery, checkpoint/source authority, atomic card/event failure, visibility, production startup, session SSE invalidation and managed-setting scope behavior. +- `post-disconnect-pg.log`: same Backend, isolatedPG, `go test ./internal/runmgr ./internal/store ./internal/cardsvc -run 'TestFailedTaskDoesNotPromiseAutomaticContinuation|TestTask|TestMaterialRecord' -count=1 -v`. Covers transactional projection/source/recovery under PG. +- `post-disconnect-fixture.log`: same Backend, `go test ./internal/server -tags live -run '^(TestExtraA01LimitsActualRunIDs|TestEvaluationPreservesProductionOutputCap)$' -count=1 -v`. Fake streams/HTTP only, no paid models. +- `post-disconnect-core.log`: Corebeafd753, `go test ./... -count=1`. + +**Current code review: PASS for the inspected U2/U3 fixes and integration boundary; no remaining identified blocking code finding.** This does not certify all unrelated upstream settings/auth behavior, final root CI, deployment or overall model quality. **Overall U3 quality remains FAIL/not passed**, including the explicitly approved extraA01. Source can be prepared as a draft/reviewable candidate; no merge or additional paid sample is justified by these mechanism tests. Raw model evidence and all original failure assessments remain applicable as recorded. + + +## Code-review receipt — latest account hardening integration + +Reviewer `/root/fast_slow_final_review`; source level **REPLAY**, not GATE; date2026-10-13. Exact reviewed Backend **0bc4b0f794809e048744a4bf0c94a0835cc32665**, merge parents e69865e11ba3f74887f92c2433b5ec37c881d0db and cfb8308895d0b94765ba10ed0e5b9bf38b439414. Clean checkout, exact SHA unchanged before/after both independent runs. Machine-readable receipt with raw-log SHA256 identities: `account-merge-review-receipt.json`. + +Read the incoming app/server/accounts/auth/dialect/migration/SSE delta against U2/U3 integration boundaries. The production run-manager, task projection, task-source endpoint, source visibility filter and notice revalidation files have **no diff** from the previously reviewed candidate. App startup order remains full-wiring/PG-event preparation → reconciliation → pending notices → legacy-unbound resume → command consumer → public HTTP serving. Incoming app changes affect later CLI account/bootstrap/storage helpers, not that startup block. + +SSE now periodically rechecks API keys as well as sessions, keeping the original authenticated owner fixed. Existing source visibility filtering still executes for both replay and live card payloads; subscribe-before-readiness, seq dedup and end-of-stream behavior remain intact. Source preview still binds owner/conversation/project and expected content hash. Notice redelivery still revalidates card/task/attempt and current source visibility before creating a new message. The added bootstrap lock has a distinct namespace and does not replace task admission/channel locks. Migration29 adds only redemption-attempt storage; independent migration and task projection replays pass. + +Independent focused checks on exact0bc4b0f, both exit0: + +1. SQLite: `go test ./internal/server ./internal/cardsvc ./internal/runmgr ./internal/app ./internal/store -run 'TestSSE(SessionInvalidation|APIKeyRevocation)ClosesStream|TestTaskSource|TestTaskVisibility|TestReviewQueuedNoticeMustRevalidateSource|TestTaskStartupReconcilesQueueBeforeServing|TestTaskReconcileOwnedQueueAndForeignLease|TestTaskBoundInterruptedChildDoesNotRespawn|TestFailedTaskDoesNotPromiseAutomaticContinuation|TestTaskProjection|TestMigrationUpgradesExistingDatabase' -count=1 -v` — `account-merge-sqlite.log`. +2. IsolatedPG: `go test ./internal/server ./internal/cardsvc ./internal/runmgr ./internal/store -run 'TestTaskSource|TestTaskVisibility|TestReviewQueuedNoticeMustRevalidateSource|TestTaskReconcileOwnedQueueAndForeignLease|TestTaskProjection' -count=1 -v` — `account-merge-pg.log`. + +**Code-review receipt verdict: PASS for the U2/U3 change and this incoming integration boundary; no new blocking finding identified.** Review does not certify the entire separate account-hardening feature or all possible credential/replay timing behavior. No model calls, credentials, source edits, deployment or merge operations occurred in this review. + +**Overall U3 model acceptance remains FAIL, not merge-ready**: extraA01 has zero accepted checkpoints and no final child result; original model findings remain unchanged. Dev baseaa6364f, Corebeafd75 and FE0bbccbe are supplied composition metadata, not a substitute for the executor's final exact-root test/build and CI. This receipt supports a draft/reviewable candidate only while the quality gate remains failed. + +Declined in this final receipt: full independent audit of unrelated account lifecycle/CLI features; real-model revalidation (not authorized); monetary billing; final root/platform CI; production deployment. Existing original review declined-scope notes remain applicable where not superseded by later focused replays. diff --git a/docs/reports/2026-10-13-fast-slow-u2-u3-extra-a01-review.md b/docs/reports/2026-10-13-fast-slow-u2-u3-extra-a01-review.md new file mode 100644 index 0000000..35cbee2 --- /dev/null +++ b/docs/reports/2026-10-13-fast-slow-u2-u3-extra-a01-review.md @@ -0,0 +1,83 @@ +> 原件归属:独立审阅者 /root/fast_slow_final_review;原始记录在 `/tmp/fast-slow-u2-u3-20261013/review/`。以下为质量判定,未复制完整评测材料或凭据。 + +# Independent verification — explicitly approved extra A01 + +Reviewer `/root/fast_slow_final_review`, 2026-10-13. Read-only inspection and fake-provider tests only. No model calls, credentials, source edits or further sample retries. The original six-sample failures remain recorded. + +## Object, authorization and artifacts + +The parent records explicit user approval for exactly one additional A01, at most two paid execution runs, reported-token target60000 and no automatic retry. This reviewer did not infer further authorization from leftover budget. + +Sample manifest fixes Backend `31efafa4790843b39ea15c4c0cd99e1495d89199`, Dev `316b5a5a56c4cf1201003865448c613cbb2a12e6`; binary SHA256 `1ce5c7961b3544bfe337d4495f3bdded776d8b4afd71afcbe56a35bbfb6cd2d5`. 31efafa's delta from6a2d85c changes only live evaluation fixtures/consumer tests, not production behavior. Original evidence remains in `../model-output/candidate-extra-a01.json` and `../approved-extra-a01-data/provider-records.json`. + +## Paid run count and usage — independently reconciled + +| Actual RunID | Kind/state | Provider calls | Reported tokens | +|---|---|---:|---:| +| run_b05e2ef14df2 | conversation/completed | 2 | 10706+11013=21719 | +| run_8cc08e1fb3fe | subagent/failed | 2 | 9362+11122=20484 | +| run_05d9df58ee30 | conversation/completed, test-only notice | 0 | 0 | + +Exactly **two paid run IDs**, four real provider requests, **42203 reported tokens**. Provider total equals summed persisted Run.Usage, with usageUnknown=false. The two foreground requests match exact non-process persisted text; the two child requests match tool-call IDs. Mapping is saved in `extra-a01-call-run-map.json`. Provider fingerprint groups=3 is not a paid-run count. + +Every actual request's outputCap is4000; neither child call began with a remaining run allowance below4000. After its second request, reported20484 exceeded the20000 run threshold, so further child requests were denied. This sample does not exhibit the old fixture cap override. No final child answer was produced. Observed extra usage is below its separately approved60000 target by17797; that difference is not new-run authorization. + +Original batch211974 + separately authorized extra42203 = **254177 reported tokens across both authorizations**. Keep those two budgets distinct; the extra authorization does not rewrite the original240000 batch target. + +## Checkpoint rejection and source validation + +First tool call `call_0VotHi8NLwSGVP42wwmeLq2K`: + +- Valid JSON syntax, invalid candidate schema: findings contains unknown `$text`, source fields are misplaced at finding level, one source has no quote, another blockIndex is a string. +- Observed result: `json: unknown field "$text"` from strict decoding. It never reaches the publishing sink. +- This is a schema/model-output failure, not a source-store failure or fabricated success. + +Second tool call `call_H3g1poeMJTa4bBhzfBRu3S1r`: + +- Schema nesting/types repaired; same checkpoint ID is legal because the first attempt was rejected and not committed. +- Twelve source links in the candidate were independently matched to host source IDs and source paragraphs. Eleven quotes are literal substrings. One review quote stitches two source portions with inserted `……`: “评审意见要求上线七月以留出接口联调时间……外部接口的负责人尚未确认。” The supplied paragraph contains intervening budget text and no such ellipsis, so this is not a literal quote. +- Observed result: `checkpoint.source: quote not in provided block`. The rejection is correct. Details in `extra-a01-rejected-source-checks.json`. +- Manager validates all candidate sources before committing any finding. This later invalid quote rejects the entire publication, including earlier valid records; no partial leak occurs. Neither call consumes an accepted-checkpoint count. + +## Persistent transactionality and truthful state + +Read the isolated SQLite DB with mode=ro and extracted exact card events to `extra-a01-persisted-events.json`: + +| Seq | Revision | State | Accepted checkpoints/findings | +|---|---:|---|---| +| 1 | 1 | queued | 0/0 | +| 3 | 2 | running | 0/0 | +| 418 | 3 | running activity | 0/0 | +| 422 | 4 | running activity | 0/0 | +| 426 | 5 | failed | 0/0 | +| 429 | 6 | failed/reported | 0/0 | + +Registration05:09:53.281418215UTC; failed projection05:10:42.845780033UTC, approximately49.56s later. Rejected tool attempts only coexist with real activity updates; there is no findings publication in any persistent revision. Card outcomeCompleteness=none, executionState=failed, phase/focus state “执行已停止.” Exact run error is retained as code `run.subagent_failed`, message `budget_exceeded: run token budget exhausted`. Foreground remains completed. No malformed or stitched-quote finding was displayed as an accepted result. + +A truthful-copy edge remains on this sample's production SHA: conclusion says “已有发现已保留” and nextStep references existing findings even though there are none; remaining still says “待任务进展后更新” after execution stopped. Main state is honest, but these subordinate strings imply saved findings/future progression. Reported to executor for a host-owned zero-findings branch: state that no accepted phase result exists, clear obsolete process Remaining, and suggest inspecting materials/new request only when desired. Preserve this historical raw sample; do not rewrite it after correction. + +The material notice contains the correctly failed summary but retains generic deliverable-selection prompt text. Because the controlled notice model is disabled, no actual notification-model behavior is established by this sample. Do not claim it would correctly offer follow-up actions based on nonexistent results. + +## Visible answer quality + +There is no child final result or accepted checkpoint to grade as useful. The foreground provides factual file overviews and preserves missing approval/date/acceptance information, but repeats known unsupported interpretations: “分期vs一次性” implies the requirements demanded one-phase delivery; “剩余4项(含3项依赖接口)” assigns external dependencies to phase2 without evidence; “后续…需按会议版执行” chooses authority not supplied by the materials. It again duplicates the immediate answer after the stop-hook reminder. Questions later asking whether those assignments are correct do not remove earlier assertions. + +The semantic failures in rejected candidate text were not promoted to material records. Literal-source canonicalization is therefore not exercised by a successful actual publication in this extra sample. It remains mechanism-tested, not real-model demonstrated here. + +## Fixture-only code review and independent replay + +31efafa adds explicit authorizedA01 selection/output name, refuses to overwrite an existing completed sample file, and installs `limitExtraA01Runs` before the shared provider wrapper. The guard reads authoritative persisted run records in the isolated default-owner evaluation space and rejects a third run or query failure before forwarding a paid request. It deliberately does not rely on prompt fingerprints. The zero-token notice follows real work and makes no provider request. + +Independent REPLAY on exact31efafa before/after: `go test ./internal/server -tags live -run '^(TestExtraA01LimitsActualRunIDs|TestEvaluationPreservesProductionOutputCap)$' -count=1 -v` — exit0. Raw output `extra-a01-guard-replay.log`. These tests use fake streams/httptest, not paid models. The third-run guard and output-cap780 preservation both pass. + +## Incoming settings/SSE compatibility notes + +Read upstream Core0f94e4b and Backend741da4a changes without altering refs. Effective settings now merge persisted system scope and strip managed user/project overrides. Models and runtime.mock become system-managed; materialIntake, materialIntakeChars and context.maxAttachmentChars remain ordinary scoped values. U2 material hashing already consumes effective attachment cap. Current eval fixture's trusted model configuration must be seeded through system override/system scope after merge rather than now-rejected MergeUser. No route credential was read or printed. + +Merge must retain TaskSnapshot/DocumentOrigin while adding SettingDesc.Scope, and retain source visibility + subscribe-before-ready alongside upstream SSE session rechecks. These are compatibility observations, not a completed review/replay of the not-yet-merged composition. No broad audit of the upstream settings feature is claimed. + +## Verdict / declined scope + +**Extra A01: FAIL.** Zero accepted useful checkpoints, no child final answer, child ended on budget. Strict argument/source rejection, whole-publication consistency, run isolation/count and observed usage accounting behave as expected; those safety properties do not satisfy output quality acceptance. The two-run/60000 observed limit is met, not a reason for further retries. + +Overall U3 quality gate remains **not passed**. No more paid samples are authorized in this review. Declined: monetary billing (not provided); normal notification-model quality (suppressed); new merged component/runtime behavior (await exact composition); final platform CI/merge (executor gate). Retain the failure as concrete, reviewable evidence. diff --git a/docs/reports/2026-10-13-fast-slow-u2-u3-validation.md b/docs/reports/2026-10-13-fast-slow-u2-u3-validation.md new file mode 100644 index 0000000..6055e0b --- /dev/null +++ b/docs/reports/2026-10-13-fast-slow-u2-u3-validation.md @@ -0,0 +1,216 @@ +# 快慢通道 U2/U3 候选核验记录 + +2026-10-13 起;负责人 weichen,实施者本聊天 Codex;计划[ r0.1](../proposals/2026-10-13-fast-slow-u2-u3-plan.md),用户“确认,继续推进”授权原增量阶段5–10连续推进。**r0.2 语义质量、r0.3 与 r0.4 真实模型阶段成果质量均未通过;八个 PR 保持草稿,未合入、未部署。** r0.4 已获书面设计、实施计划和零付费阶段 6 评审;其一次性 A01 资源批准已经使用,结果见文末。 + +## 已实现的用户行为 + +同一张任务卡展示真实排队、工作、阶段发现、范围限制和结果;默认最多三项普通发现,关键问题与更正始终可见,依据和历史按需展开。材料记录仅展示核验后的原文和文件名;分析与推断保留类别及前提。来源点击进入现有文件预览,后台更新不抢当前文件、输入焦点和阅读位置。 + +任务、尝试、内容版本及执行权由宿主管理;卡、事件和水位原子提交;重复/旧帧/迟到预览被丢弃。重启中断失去执行者的任务,不从旧请求隐式创建新尝试。完成只引用最终正文,停止后的下一步明确要求用户重新提出请求。无新增默认命令/脚本展示、页面或模型摘要调用。 + +## 自检与独立重放 + +SELF原始命令输出由Dev的make evidence工具写入任务卡,不能升级为GATE。外部收据和报告保留于`/tmp/fast-slow-u2-u3-20261013/`。 + +| 对象及来源 | 检查 | 当前结论 | +|---|---|---| +| SELF,合并后候选Devad31203,Corebeafd75/Backend0bc4b0f/FE0bbccbe/UI3b34dfd/Docsdd3b9da | `make test && make build` | PASS:治理312、FE224、UI156、完整后端及构建;SELF收据`fast-slow-current-aa-combination.json`。模型质量仍FAIL。 | +| SELF,当前组合Devd06792c,Corebeafd75/Backende69865e/FE0bbccbe/UI3b34dfd/Docs1b296a1 | `make test && make build` | PASS:治理312、FE224、UI156,后端及构建通过;SELF原始收据`fast-slow-final-current-combination-serial.json`。当前模型质量仍FAIL。 | +| SELF,最终候选Dev7fd5288,Core6e38574/Backend6a2d85c/FE3087b06/UI3b34dfd/Docs53a7d37 | make test && make build | PASS;治理311、FE224、UI156及完整后端/构建;收据fast-slow-final-fixed-combination.json | +| SELF,Dev ed7f1c5,Core6e38574/Backendf6b5360/FE4d1d64a/UI3b34dfd/Docs59db26e | make test && make build | PASS;治理311,FE224,UI156;最后材料原文与评测上限修复是后续独立增量,不把旧SHA当新SHA | +| SELF,Backend ee65604/2a64432增量 | go test ./...;live真实HTTP夹具780/5000/0输出限额测试 | PASS;完整后端、防止原文添加批准状态、停止后继及精确错误记录的回归通过 | +| SELF,隔离PostgreSQL | runmgr/cardsvc/store;此前完整PG后端套件 | PASS;原子性、租约隔离、来源CAS及材料记录守卫通过 | +| SELF,实际HTTP浏览器 | 两项新来源/阅读用例、十一项既有导航/窄屏/字号/附件/确认用例 | PASS于先前固定候选;未当作模型质量证明 | +| REPLAY,/root/fast_slow_final_review | 独立源码审阅、SQLite/PG重跑、迟到预览、插件冲突、删除索引、通知、实际生产启动及终态正文 | 已整改问题闭合;最终核验对象/日志见下面复审报告 | +| GATE,Dev 7692503 / Backend e1c994c / UI 3b34dfd | 对应精确HEAD平台CI | PASS;Dev三项、Backend及UI各自要求的作业均在受信runner通过。后续v7评测入口及文档更新会形成新HEAD,旧CI不冒充新对象;U3出口FAIL,不提前合入 | + +最新来源预览、旧版本保护及后台不抢文件/焦点的两项实际HTTP/browser用例于Backend6a2d85c、FE3087b06通过,SELF收据fast-slow-browser-final-literal.json。 + +独立报告原件:`/tmp/fast-slow-u2-u3-20261013/review/fix-review.md`、`model-assessment.md`、`model-assessment-remaining.md`。真实模型数据、执行事件和逐调用Usage均保留,凭据不在报告或源码中。 + +代码复审归档:[独立复审记录](2026-10-13-fast-slow-u2-u3-code-review.md)。新增模型样本的逐项独立判断见[额外A01评估](2026-10-13-fast-slow-u2-u3-extra-a01-review.md)。其结论是代码PASS、模型出口仍未通过,未把代码与模型测试混为一个完成状态。 + +## 原六样本的真实模型结果 + +固定三份合成文本共3589字,覆盖六月/七月意见、50万元预算与审批、12项总需求/第一期8项、接口依赖及验收缺项;A04含不可读取的PDF。均使用相同MiniMax-M3路由、最多8轮/12工具、每run返回Usage阈值20000和300秒,无自动重试或回退。正常通知模型在对照中对称抑制;其回复质量不在本比较内。 + +| 批次/对象 | 结果 | reported tokens | +|---|---|---| +| baseline R0 Backend0f192a4,A01/A02/A04各一次 | 保留原输出;A02缺少后台整理,部分分析也有语义错误,不能充当准确标准答案 | 57517 | +| candidate Backend4a0418e,A01一次 | FAIL:仅一次落盘阶段发布;把总量/第一期当冲突,第二期剩余量误写8;最终卡含过程文字 | 48177 | +| revised Backend3e330e3,A02/A04各一次 | A02有两次发布但预算含义仍误读;A04保留两次发现及PDF限制,因预算耗尽失败、没有最终正文 | 106280 | +| 总计 | 六素材任务已用完;候选未通过质量出口 | 211974 | + +限制:旧A01观察记录因JSON复用Task指针失去历史快照代表性,原文件保留,实际只有一次检查点由独立数据库事件证实。旧评测错误覆盖了生产较小输出上限,A02最终完整答复不能证明生产预算内也能完成;已用真实HTTP夹具修复,原成功输出不追溯升级。Usage是返回计量,单次在途请求仍可越过阈值,不能声称严格计费上界。模型引用22/22匹配只证明出处,不证明分析正确。 + +## 剩余工作及停止边界 + +原计划要求一次额外A01的明确批准;该次与其后另行批准的v7试验均已执行,结果见下节。两次授权都已耗尽,不能自动追加样本。只有取得真正有用且语义准确的阶段成果、最终结果与可信Usage,才可继续合入与收口。 + +10秒可读性尚无代表性用户测量,标为未验证;既有服务/生产数据未变,U4与部署未启动。代码与原始证据保留在登记任务工作区,未回收未完成分支。 + +## 2026-10-13追加A01复验与会话恢复 + +用户明确批准的一次追加A01已执行:Backend31efafa(生产逻辑与6a2d85c相同,追加仅改变受控测试入口),两个付费run、四次provider调用,共42203 reported tokens。后台两次候选因非法字段及非原文拼接引用被严格拒绝,零接受的检查点/发现;达到run额度后失败。原件`/tmp/fast-slow-u2-u3-20261013/model-output/candidate-extra-a01.json`、`approved-extra-a01-data/provider-records.json`及独立`review/model-assessment-extra-a01.md`完整保留。原七个素材任务累计254177 reported tokens;U3质量出口仍FAIL,未自动重试、未合入、未部署。 + +零成果的失败提示已整改:明确尚无可用发现,剩余工作及下一步不再暗示保存了成果或仍自动处理;真实RED→GREEN收据zero-findings-red/green。合并上游系统设置/SSE会话检查后,测试夹具只在自身启动配置注入系统层,保持原有效模型配置,不修改任何既有服务。原模型失败不因这次整改追溯改为PASS。 + +用户看到的Codex会话错误经当前chat元数据确认:上一轮回答流中途断开(request ID10e3777f-9b5e-4fc5-9dd2-132405d8c669);当前chat恢复为active,源码及证据保留。该会话传输错误与应用模型验收失败分别记录。 + +下一步建议先缩小单次检查点的复杂度,并依据实际输入开销调整每run预算分配,形成可评审的修订方案和有限复验预算;来源合法性保持严格,不能通过接受省略/拼接引用降低门禁。新的付费样本及原验收条件调整需明确授权。 + +最新组合真实HTTP浏览器:Backende69865e/Corebeafd75/FE0bbccbe,两项来源预览和后台不抢焦点用例均PASS,SELF收据`fast-slow-browser-current-upstream.json`。首次组合检查遇到已自行消失的短暂Git索引锁,串行重跑后通过;未删除锁。 + +合并后的隔离PostgreSQL后端完整套件PASS(SELF `fast-slow-current-backend-pg.json`)。当前Backend0bc4b0f实际HTTP/browser两项PASS(SELF `fast-slow-browser-aa-final.json`)。独立REPLAY核对账户加固合并与任务来源/通知/恢复/SSE共存,见更新后的代码审阅归档。 + +后续代码候选仅把模型侧单次检查点声明收敛至1–3条和连续短引文,并将材料配方递增至v7;host来源校验、原JSON失败原件不变。该v7后续试验已另行获批并执行,质量结果见下节;它没有复用先前已耗尽的授权。 + +## 另行批准的v7后续A01:两次落盘,质量仍FAIL + +Backend7f8674f、Dev c1c1942 的隔离试验只执行一次A01:两个付费run,五次provider调用,共57997 returned Usage token;通知run零调用,所有单次输出上限4000,实际每run预算30000/8轮/12工具。原始结果为`model-output/candidate-followup-a01.json`,逐调用原件为`followup-a01-v7-live-data/provider-records.json`,命令原始SELF收据为`followup-a01-run-receipt.json`,均在`/tmp/fast-slow-u2-u3-20261013/`,旧额外A01结果保持原哈希。 + +数据库事件独立重放确认两次检查点确以running态提交:第一批3项,第二批累计6项;10处已接受引用均与来源原文及版本匹配。第二次提交只比失败终态早1.076毫秒,250毫秒轮询客户端漏读,原评测exit1所称“少于两个落盘成果”是观测假阴性。评测入口已增加对持久事件的只读核验并用夹具RED→GREEN验证;独立覆盖测试确认不会把其他会话或终态计入。**没有再次调用真实模型或追溯改写原始exit1**。详见`/tmp/fast-slow-u2-u3-20261013/review/model-assessment-followup-a01.md`及`persistence-counter-replay.log`。 + +业务质量依然FAIL:首批发现把需求文档中的预算与范围误写成“已批准决定”,材料并无批准依据;第二批虽有可追溯的分析,却几乎立刻进入失败终态,用户难以看到持续推进。后台run在报告用量越过30000阈值后失败,未生成最终正文;在途调用可使该run的实际返回Usage超过阈值。来源字面正确不等于语义正确,不能以两个落盘检查点放行U3。后续付费样本需要新的方案与明确授权;草稿PR保留,未合入、未部署。 + +本次还有一项**执行偏差**:后台第2次检查点候选因非原文引文被拒绝后,仍在同一run发起下一次付费provider请求,返回14231 tokens。虽然仍是两个付费run且本次总57997低于60000目标,这违反了计划第6节“非原文引用时停止”的独立条件;不能解释为“同run纠错就不算重试”而豁免。根因是原评测入口只守卫run数、Usage和超时,没有把检查点工具拒绝作为后续provider请求的停止信号。新增零付费RED→GREEN测试及评测入口阻断,**未重新运行真实模型**;原始偏差和质量FAIL保持不变。 + +## r0.2 零付费修订候选(验证进行中) + +用户在本会话批准[书面设计 r0.2](../proposals/2026-10-13-fast-slow-u3-quality-revision.md)与[实施计划 r0.2](../proposals/2026-10-13-fast-slow-u3-quality-implementation-plan.md)后,Backend `373f8a1`、UI `ccc43e6` 分别形成来源一次呈现、肯定批准断言宿主守卫、待核对判断分组和最终答复预算预留。Documents 夹具已适配内联来源;Dev 组合 SHA、实际 HTTP 浏览器、独立复审和新 HEAD CI 仍待固定。此段只记录零付费机制进展,不能将旧 v7 A01 结果升级为 r0.2 真实模型证据。 + +Backend 在该组件 SHA 上的 `go test ./... -count=1` 与材料预算路径 race 回归通过;UI `npm test && npm run typecheck` 为 17 文件、158 测试通过;Frontend 五项受影响的 store/阅读/布局/设计守护文件共 84 项测试通过。来源映射与审批否定/疑问回归在 Backend 组件测试中通过。旧 A01 返回 Usage 的离线算术重放会在原第三次请求前诚实停止,**不是**新版模型完成最终答复的证据。正式组合与浏览器验证完成后再补准确 Dev SHA 和 SELF 收据;新付费 A01 为 `NOT_RUN`,未获资源批准。 + +主干同步时四个组件 gitlink 冲突,Dev 首次 `make task-sync` 的 exit2 收据已在任务卡原样保留。Core、Frontend、UI、Backend 分别合入当时的 `origin/main`,Backend 内嵌 Core 同步至合并 SHA;合并后的 Core `go test ./...`、Backend `go test ./...`、UI 165 测试与类型检查、Frontend 224 测试与构建均通过。Dev 后续 `make task-sync` exit0 的 SELF 收据也已导入任务卡。当前浏览器固定对象为 Dev `85bed4d`、Core `c55ebbc`、Backend `627b6e4`、Frontend `329bdf5`、UI `cbbef50`、Documents `72e11c5`;正式组合测试仍待记录。 + +第一轮 HTTP 浏览器回归有一项失败:受控机制夹具将 `maxOutputTokens` 与 `llmRetries` 写成 Go 整数,`EffectiveSettings.GetNumber` 只接受 `float64`,预算预留误判并在一项检查点后结束。Backend `627b6e4` 将隔离评测配置改为有效数值类型,新增测试先 RED 后 GREEN,完整 Backend Go 套件通过;原失败实例与 trace 未覆盖。第二轮用全新数据目录与二进制 manifest 在 Dev `85bed4d` 运行,`SELF` 收据 `/tmp/fast-slow-u2-u3-r02c/browser-receipt.json` 经 `make evidence` 导入任务卡,真实 HTTP Playwright 两项均 PASS:刷新后来源导航、文件版本变化提示,以及后台不抢文件、焦点和视口。此结果证明机制链路,不证明模型语义质量;新付费 A01 仍 `NOT_RUN`。 + +正式组合在 Dev `8c1fc39`、Core `c55ebbc`、Backend `627b6e4`、Frontend `329bdf5`、UI `cbbef50`、Documents `2fdda63` 上执行 `make test && make build` exit0;完整 `SELF` 收据 `/tmp/fast-slow-u2-u3-r02-combination-20261013.json` 已经 `make evidence` 导入任务卡。另在全新、任务自有 PostgreSQL 数据目录与数据库上运行 Backend `internal/runmgr`、`internal/cardsvc`、`internal/store` 三包,均通过并停止测试实例;`SELF` 收据 `/tmp/fast-slow-u2-u3-r02c/postgres-receipt.json` 已导入。文档增补将形成新 Documents/Dev SHA,最终精确组合测试、独立代码复审与平台 CI 仍须对新 HEAD 核验。新真实模型对照仍 `NOT_RUN`,不合入。 + +Documents `4a70ba8` 固定后,Dev `1fa58cb` 又完成一轮 `make test && make build` exit0(任务卡 SELF 收据 `/tmp/fast-slow-u2-u3-r02-final-combination-20261013.json`)。最新主干 `d5428c5` 仅增 RDM 验收脚本与任务卡,Dev 同步无组件冲突;该合并后准确 HEAD 尚需新组合测试。 + +独立只读复审 `/root/u3_r02_final_review` 对 Dev `1fa58cb`、Backend `627b6e4`、UI `cbbef50` 判出三项 Important、无 Critical/Minor:否定及疑问审批表达误拒、同 ID 幂等检查点被重复计数、最终答复提醒改变受控评测 run 指纹。Backend `9126dae` 逐项添加失败回归再修复:`TestMaterialApprovalClaims`、`TestMaterialFinalReserveCountsDistinctPersistedCheckpointIDs`、`TestMaterialFinalReserveAfterTwoCheckpoints` 均 RED→GREEN;完整 Backend Go、定向 race 与 live-tag 夹具通过。复审未评判真实模型质量或现有实例部署;前者仍 `NOT_RUN` 且阻止合入,后者不在本轮范围。 + +修复后 Dev `2b72f19`、Backend `9126dae` 的任务自有新 HTTP 实例通过二进制/PID/组件 SHA manifest 检查;两项 Playwright 再次 PASS,SELF 收据 `/tmp/fast-slow-u2-u3-r02d/browser-receipt.json` 已导入任务卡。全新隔离 PostgreSQL 实例上的 runmgr/cardsvc/store 也再次 PASS 并停止,SELF 收据 `/tmp/fast-slow-u2-u3-r02d/postgres-receipt.json` 已导入。最终 Documents/Dev SHA、组合测试与当前 HEAD 平台 CI 尚待固定;新付费 A01 样本依旧 `NOT_RUN`。 + +## 2026-10-13 r0.2 已授权 A01:两次阶段成果和最终正文,语义仍 FAIL + +用户针对一次性资源提案 `/tmp/fast-slow-u2-u3-r02d/A01-resource-proposal.md` 明确答复“授权/批准这一次受控复验”。在 Dev `501ae35`、Backend `d89c72e`、Core `c55ebbc`、Frontend `329bdf5`、UI `cbbef50`、Documents `1a1c888` 上,仅运行同一 A01 一次;启动前任务自有 manifest 验证 PID、二进制 SHA、路由文件 0600/哈希、零既有 run,旧结果文件未覆盖。三份合成材料与 v7 A01 哈希一致。完整 SELF 原始收据经 `make evidence` 导入 Dev 任务卡:`/tmp/fast-slow-u2-u3-r02-model-20261013/{preflight-receipt,manifest-receipt,run-receipt,mechanism-receipt}.json`。测试入口 exit0 仅表示程序和机械条件通过,**不表示语义质量通过**。 + +只读重放核对实际两个非零 Usage run:前台 `run_f1a48f781f87` 两次 provider 调用、21,651 tokens;材料子 run `run_a666011bf941` 三次调用、24,503 tokens;总计五次、46,154 reported tokens,全部有 Usage、单次输出上限4,000。另有一个零 token 通知 run,不是第三个付费 run。`provider-records.json` 的 `modelRuns=3` 是三个提示指纹组,其中前台同一 run 被分成两组;不能当作三个付费 run。两次检查点工具结果均为 `ok`,未触发“拒绝后停止”场景;最终正文为2,135字符的非过程文本。 + +持久事件证实第一批三条材料记录在 UTC 14:21:03.473(conversation seq374)提交,第二批新增三条分析在14:21:16.504(seq385)提交,终态在14:21:37.987(seq413);两段可见窗口分别约13.031秒和21.483秒,API观察也捕捉到两次 running 态。11处已接受来源引用均为对应文件原文连续片段。这些机制结果比 v7 样本进步,但不能替代逐条语义审查。 + +独立只读审阅智能体 `/root/u3_r02_model_assessment` 重新读取 SQLite(mode=ro)、逐调用记录、最终卡和三份原文,判定**质量 FAIL**:第二批 `phase-list-gap` 分析称“12−8=剩余4项,其中包含3项依赖外部接口”,而原文只说总12项中有3项依赖接口、第一期8项,没有说明这3项的分期归属。该错误仍是最终卡的 active finding;同条末尾说“不能断言3项必属二期”并不能撤销前面的肯定句。最终摘要虽未重复错误,也不能使已展示的错误阶段成果合格。另有“六月已决定”的措辞比原文“上线六月”更强,应在下一轮方案中处理。**旧样本和本次原始结果均保留,六个 PR 保持草稿,不合入或部署,不自动再跑。** + +观察到两处零付费诊断缺口:评测 provider 以首条系统消息和首条用户消息作 run 指纹,前台第二次调用的用户消息变化,使同一实际 run 拆成两个组;实际调用数均未越过8,但这个分组不足以可靠证明未来每 run 的8次/300秒控制。子 run 的持久 Usage `toolCalls:0` 与两个成功检查点事件不一致,实际工具次数应按持久事件核对。修复这些记账/评测缺口不追溯改变本次 A01 的 FAIL 或授权用量。 + +原始对象 SHA-256:SQLite `8a824bee36991c7fdbdbcee04d6daaca1b4d1824b92b5d1eb47080f3ed5c5f06`;provider 逐调用 `fa35bc9dcd3458f7f37ce7ae3e111b1521b52f8740de096d61d0ef4c23ff546f`;A01 结果 `95aab04b25a3cebb71bbd55ae3f662dda6e3979eea2428e1bf262fd7dfdd6bf1`;SELF run 收据 `68da77d2ecf32a7f51e0b85277a062695f63b70ff142c20e3897ddd681145e8b`。原件位于 `/tmp/fast-slow-u2-u3-r02-model-20261013/` 与 `/tmp/fast-slow-u2-u3-20261013/model-output/candidate-r02-a01.json`。 + +上述两个诊断缺口随后在 Backend `062b6f5` 作**零付费整改**:provider 请求经宿主上下文携带真实 run ID,评测在缺 ID 时拒绝而非按可变提示分组;同一 run 的多次请求共用调用数/300秒起点,逐调用记录也写入 run ID。直连与子 run 的 `ToolCalls` 经用量累加进入持久 run.Usage。分组与记账测试均先 RED 后 GREEN,Backend 完整Go、定向 race 与 live-tag fake-provider 测试通过。此提交晚于并且**未参与**上面唯一一次付费 A01;原始 `modelRuns=3`、`toolCalls:0` 与质量FAIL不追溯更改。该新代码的真实模型质量出口仍 `NOT_RUN`,不能直接合入。 + +为防临时目录清理导致原始证据丢失,任务自有受限归档另存于 `/home/weichen/.local/share/agent-system-evidence/fast-slow-u3-r02-a01-20261013/`:18个文件逐项哈希验证、目录0700、文件0600,包含旧版本的测试二进制、SQLite、provider逐调用、结果、收据及三份合成材料;**未复制受保护路由或API key**。`archive-manifest.json` 记录来源路径、大小和SHA256。归档不提升SELF或REPLAY的可信等级,也不代替原始任务现场。 + +## r0.3 有依据的阶段判断:零付费候选 + +用户批准[书面设计 r0.3](../proposals/2026-10-13-fast-slow-u3-r03-evidence-backed-judgments-design.md)与[实施计划 r0.3](../proposals/2026-10-13-fast-slow-u3-r03-evidence-implementation-plan.md),仅授权零付费代码和验证。Core 候选增加可选结构化依据;Backend 对短引核验总量/首期/接口依赖数量,重新计算差额及交集可能范围。来源有效但归属不能确定时,宿主在同批一次 CAS 提交 `needs_user`,工具成功结果说明已转待确认;伪造来源、原件或任务固定输入的 hash/解析版本不一致仍整批拒绝。候选之外的 TaskSnapshot 持久字段、三栏布局和模型调用次数不变。有限语法识别不到的真实表达也会保守降级,不能宣称通用语义正确。 + +零付费 RED→GREEN 覆盖 r0.2 A01 错误归属、12−8=4仅为推算、原文明确关系、0–3 的未定交集、预算条目误作需求、旁路状态字段、同批原子提交、幂等与任务输入版本。Core `b474394`、Backend `62ad895`、Frontend `9d7681c`、UI `ac5b617`、Documents `f64fe11` 固定的 Dev `f7e55c3` 上,`make test && make build` exit0;完整 SELF 收据 `/tmp/fast-slow-u3-r03-20261013/combo-fixed-receipt.json` 已由 Dev `make evidence` 导入任务卡。此前 Dev `6734665` 的组合 exit2,因生成 TS helper 被导出使 Frontend knip 53 超过基线52;失败收据同样保留,修复后 Frontend knip 51≤52、UI 61≤61。任务自有 PostgreSQL 上 Backend runmgr/cardsvc/store 三包通过并已停止,SELF 收据 `/tmp/fast-slow-u3-r03-20261013/postgres-receipt.json`;材料工具说明及 schema 总增量为 877 UTF-8 字节,token 只是离线粗估,须以将来真实返回 Usage 判断预算。 + +为验证默认阅读,Backend `756679d` 的零付费机制夹具在第二批同时给出原文记录和无依据的接口分期肯定句,并附三个真实数量前提;浏览器断言先在旧夹具失败,再在 Dev `b10a218`/Frontend `006c815` 的全新任务自有 HTTP 实例两项 PASS:待确认问题可见、刷新与来源跳转/变更版本提示可用、后台重放不抢当前文件和输入焦点。SELF 收据 `/tmp/fast-slow-u3-r03-20261013/browser-c-receipt.json` 已导入 Dev 任务卡;测试实例和预览进程按记录 PID 停止,截图与 trace 保留。此项是**假流机制验证**,不等于付费真实模型 A01 的阶段、最终正文或 Usage 质量通过。新精确 Dev HEAD 的组合、独立代码复审和平台 GATE 仍待核验;六个 PR 保持草稿,未合入。r0.2 的真实语义 FAIL 不追溯改变,r0.3 新模型样本 `NOT_RUN`,资源仍须单独批准。 + +请求代码复审前 `make task-sync` 将权威 main `415f9ac` 纳入任务分支;Dev `1ce3c68` 的 `make test && make build` exit0,原始 SELF 收据 `/tmp/fast-slow-u3-r03-20261013/review-combo-receipt.json` 已导入任务卡。但独立只读审阅者 `/root/u3_r03_final_review` 找到三项 P1,**该版代码语义安全复审未通过**:只引“已批准”可截去原文“不等于”,A项目总数与B项目首期数字可被相减,无数字的“接口依赖工作均安排在后续批次”可从自由字段或任务状态原样展示。代码绿色、来源字面匹配和浏览器通过都不能放行这些错误。 + +Backend `9019f90` 为三项分别加入失败用例后作零付费修复:恢复短引所属完整可见句子并在否定/重复/过长语境保守处理;显式项目范围不一致时转待确认,未明确共同范围的差额改用条件句;无结构依据的普通分析只保留审慎表达,未知状态文案替换为中性进度。额外覆盖句尾标点后的相邻句不能混入直接关系。Backend 全量 Go 与 live-tag 假流停止测试通过;全新任务自有 PostgreSQL runmgr/cardsvc/store 三包 PASS(SELF `/tmp/fast-slow-u3-r03-20261013/postgres-review-receipt.json`),Dev `884dcd8` 的全新 HTTP 机制实例两项 Playwright PASS(SELF `/tmp/fast-slow-u3-r03-20261013/browser-d-receipt.json`);测试进程已停止。**修复版的独立复审、最终精确HEAD组合和GATE仍待核验,真实模型质量仍 NOT_RUN;旧 r0.2 FAIL 不变。** + +随后独立审阅持续用否定词截断、`项目A/项目B`、同句撤销/改数、无数字分期措辞和状态字段构造反例;每项按影响先RED再修复,旧版未通过的判断均保留在 Dev 任务卡。最终零付费机制候选 Backend `5f53f80`:材料记录展示经重新验证的完整可见句,截断且无句界转待确认;直接关系只允许一个完整来源;有限正向数量句拒绝未知数值/修订片段,唯一集合关系要求同名显式范围;任务进度文案全由宿主按阶段生成;无依据普通分析仅允许少数完整审慎句式,模型自填的影响与前提不照搬,其余待确认。新增提示不增加模型调用,处理配方从 v9 调至 **v10**,避免旧完成结果按新规则复用。这是计划内保守回退,代价是陌生自然语言更常进入待确认。 + +独立只读审阅者 `/root/u3_r03_final_review` 在 Backend `5f53f80` 重跑全量 Go 和无付费 live-tag 机制测试,复核上述反例后,结论为**代码机制 PASS,在批准的有限语义范围内未发现剩余 Critical/Important**;没有运行真实模型。Dev `24058c0` 固定该 Backend 的新隔离 PostgreSQL 三包通过(SELF `/tmp/fast-slow-u3-r03-20261013/postgres-v10-receipt.json`),任务自有 HTTP/Playwright 两项通过(SELF `/tmp/fast-slow-u3-r03-20261013/browser-e-receipt.json`),实例和预览均按 PID 停止。当前精确 Dev HEAD 的 `make test && make build`、Gitea CI 和 r0.3 真实模型质量仍待验证;即便机械门禁转绿,旧 r0.2 语义 FAIL 也不改判,六 PR 继续草稿,新的付费样本只在另行资源审批后运行。 + +验收前再次 `make task-sync` 将权威 main `57bcb17` 合入,五组件指针未变。Dev `6f91ca9` 的 `make test && make build` exit0,完整 SELF 收据 `/tmp/fast-slow-u3-r03-20261013/acceptance-combo-receipt.json` 已导入任务卡;结果仅证明该 SHA 的测试和构建。随后 Backend `5f83454` 只为未来试验增加独立 `approved-r03-20261013` 标签、`candidate-r03-a01` 输出名及既有30,000-token/单run守卫,标签回归先RED后GREEN;标签存在**不代表用户已批准付费调用**。此新增 Backend SHA 的 Dev组合/GATE仍待固定,r0.3 模型质量继续 `NOT_RUN`。 + +## 2026-10-14 r0.3 唯一获批 A01:零阶段成果,质量 FAIL + +用户明确批准一次性资源提案 `/tmp/fast-slow-u3-r03-20261013/A01-resource-proposal.md` 后,在固定 Dev `e31443a8ff1f099f9b2ef320c295b0ddcdaf503d` / Backend `5f834544b0b808cb16214a36725f2ae3cb90bfd4` 上只运行一次 A01。启动前重新核验六个干净组件 SHA、准确 HEAD 的 Dev/Backend/UI 平台 CI、三份合成材料哈希、受保护路由、空结果路径与独立零既有 run 实例。预检 SELF 收据 `/tmp/fast-slow-u2-u3-r03-model-20261013/preflight-launch-receipt.json` exit0;实例 PID、二进制哈希与 Dev/Backend SHA 均在 manifest 验证。模型路由凭据没有入仓或归档。 + +唯一运行的前台快通道给出三份材料即时概览;后台先调用三次 `knowledge_search`,均无命中,首次 `publish_work_checkpoint` 的参数在 `"findings": ` 截断,不是合法 JSON。检查点被拒后没有再发 provider 请求,后台任务和卡片失败,运行中保存的阶段发现为零、没有最终整理正文。评测 `TestWorkCheckpoints` exit1,原始 SELF 收据 `/tmp/fast-slow-u2-u3-r03-model-20261013/a01-run-receipt.json` 不改写。实际两个付费 run、四次调用,返回 Usage 按 run 为21,241/15,893,合计37,134 tokens;均有 Usage、单次输出上限4,000,资源在获批边界内。只读核对 SELF `/tmp/fast-slow-u2-u3-r03-model-20261013/assessment-v2-receipt.json` exit0;第一次评估脚本因 manifest JSON 字段大小写判断错误的 exit1 也保留,修正只重跑离线核对,没有重跑模型。 + +补充零付费调查确认三份上传材料确已入本次 SQLite 索引;实际“文件名+关键词”查询在当前 FTS/LIKE 路径均零命中,单文件名各命中一条(SELF `search-diagnosis-receipt.json`)。Agent-Native SSE 的合成流在 `finish_reason=tool_calls` 且缺 `[DONE]` 时仍放行半截工具调用(SELF `sse-repro-receipt.json`);**没有本次服务商原始 SSE,不能把这一机制缺口断言为 A01 截断的原因**。失败通知的系统文字在无可用结果时仍要求交付物选择;评测抑制了通知模型回合,不证明浏览器实际发卡。完整原始样本、SQLite、逐调用记录、工具脚本及受限报告在 `/home/weichen/.local/share/agent-system-evidence/fast-slow-u3-r03-a01-20261014/`,`archive-manifest.json` 校验每个副本;未复制受保护路由或 API key。 + +本次 **U3 模型质量 FAIL**,原有 r0.2 语义 FAIL 也不改判。六 PR 保持草稿,不进入阶段7验收、代码合入或部署;本次付费授权已耗尽,无自动补跑。用户先批准[ r0.4 方向](../proposals/2026-10-14-fast-slow-u3-r04-reliable-first-checkpoint-design.md)的书面设计撰写,随后批准 Documents `7b21e6d495e99e15c458d608db47c517b2fdc599` 的书面设计进入[详细实施计划](../proposals/2026-10-14-fast-slow-u3-r04-reliable-first-checkpoint-implementation-plan.md);该计划待阶段5人工评审,尚未授权代码修订或新付费样本。设计将首条短事实、材料范围检索、流终态/Usage 安全与失败状态选择卡一并处理,不降低原 A01 两条有用阶段成果及完整正文的验收标准。 + +## 2026-10-14 r0.4 一次性 A01:工具流终态失败,质量 FAIL + +用户先批准 r0.4 书面设计、详细计划与零付费实施,随后确认 Dev `aedcc7c` 阶段 6 通过,再单独批准一次性资源提案(原件归档):固定质量候选最多 2 个付费 run,每 run 配置 30,000 tokens/8 轮/12 工具/300 秒,单次输出最多 4,000 tokens,整批已返回 Usage 停止目标 60,000 tokens,不自动重试或换模型;本次不授权合入和部署。主干同步后,Dev `d38dbdc396b7b8c6a9e34142ea8e6018abbed1fc` 固定 Backend `37e2cb7adb6202902345d38bc591e12be7aa79c0`、Agent-Native `a5e3c7c817166865d75b764337b15ca7d505d131`。Dev 准确 HEAD 的 `test`、`test-pg`、`svc-realcombo` 平台运行 3075 和 Backend 准确 HEAD 平台运行 3074 均由受信 runner 完成;启动前零付费预检、路由文件权限和哈希、三份合成材料、唯一输出路径与 0-run 数据目录核对通过。原提案没有被数据目录修订覆盖;等价路径修订另存 `A01-resource-proposal-implementation-amendment.md`,仅将目录名补足夹具要求的任务前缀。 + +仅启动一次模型模式任务并运行一次 `TestWorkCheckpoints`。测试 exit1,报“少于两个已持久化的运行中检查点”;随即停止本任务实例,没有补跑。独立只读复审 `/tmp/fast-slow-u3-r04-20261014/A01-independent-assessment.md`(SHA-256 `2ac373ed66d47aaa9cdb699d3bf77eb8a515d46956ee0321b70cc0230d79b667`)核对原始 SQLite、逐调用记录、结果、收据、材料与已提交源码:实际 **2 个付费 run、3 次 provider 调用,返回 Usage 29,062 tokens**,均有 Usage;最后一次子 run 返回 7,148 tokens,`finishReason=tool_calls`、`streamComplete=false`、非重试错误 `tool call stream ended before [DONE]`,没有可执行工具调用。`toolArgumentsValid=true` 只表示收到的 JSON 可解析;原始 SSE 未保存,无法判断 `[DONE]` 未被观察到的原因,也不能确认被拒工具的名称。数据库中没有 `tool.call`/`tool.result` 或检查点事件,运行中卡片 `checkpointSeq=0`、发现 0 项,材料任务失败、完整最终正文缺失。失败通知没有虚构交付物选择。 + +快通道澄清问题还出现独立语义错误:`剩余 4 项(含 3 项依赖接口)` 把总 12 项中 3 项依赖接口擅自归入第一期 8 项之外的剩余 4 项;原材料未给出交集归属。问句形式不消除其中的肯定关系。故本次真实质量 **FAIL**,不进入阶段 7,八个 PR 不合入。r0.2/r0.3 失败结果保持原判。 + +另有一项记账缺陷:逐调用记录正确合计 29,062,但原 SQLite 的父 run 记 21,914、失败子 run 记 0,结果 JSON `reportedTokens=21,914`,少计已返回的 7,148 与其 cache-read 128。Agent-Native 错误终态把 Usage 放入 `Result.Usage`,Backend 子 runner 却只从未发出的 `message_end` 累计持久化 Usage。此项零付费修复及独立验证另按 Backend/Dev 新 SHA 记录;不会改写本次已保存的结果,也不能使质量由 FAIL 转 PASS。 + +原始试验及复审材料已复制到受限归档 `/home/weichen/.local/share/agent-system-evidence/fast-slow-u3-r04-a01-20261014/`:20 个文件,逐个校验原件与副本 SHA-256,目录 0700、文件 0600,`archive-manifest.json` SHA-256 `881991dcab17ad2539f8b24332f962e3505ab2f248662f58283fe08daa179077`;未复制受保护路由或 API key。原始数据仍在 `/tmp/fast-slow-u3-r04-model-20261014/`,归档和本地收据均不提升证据等级。 + +下一步需对工具流未见 `[DONE]` 的真实边界和快通道数字归属分别修订书面设计;先用零付费可重放数据建立安全终态判据、诊断和语义反例,不降低工具执行或来源门禁。任何新的付费样本需新的候选固定、阶段评审和单独资源批准;本次授权已用尽,不能沿用运行 3075 的 GATE 或自动再试。 + +## 2026-10-14 r0.5 零付费实施候选:安全首回合与无内容终态诊断 + +用户已分别批准 [r0.5 书面设计](../proposals/2026-10-14-fast-slow-u3-r05-terminal-evidence-and-fast-feedback-design.md)、[详细实施计划](../proposals/2026-10-14-fast-slow-u3-r05-terminal-evidence-and-fast-feedback-implementation-plan.md) 和同会话零付费实施。没有批准新供应商探针、付费 A01、合入或部署。r0.4 真实样本的 2 run/3 调用/29,062 返回 Usage、零检查点和质量 FAIL 保持原样。 + +当前组件候选:Agent-Native `6a1dcddb48d71844c6b66045c6dc1fff5fd53e93`,Agent-Squad `bfb0834dd622a37b5a05825308434de005f9710f`,Backend `bd41302310960417e665e5387e6fce1f5644ee58`,Frontend `cfabed8`。Dev 的这些 gitlink 已暂存,最终 Dev HEAD 尚待任务卡与本报告提交后固定。Agent-Native 只记录 `done_marker`、`clean_eof`、`read_error`、`cancelled`、`invalid_frame`、`provider_error`/`transport_error` 等无内容状态及有界计数;未见 `[DONE]` 的工具流依然不能执行。Backend 的预启动材料首回合直接持久化宿主确认:排队、运行、复用、失败和完成按卡片实际状态表述,数字/分期/归属未经核对一律待确认。文字+小材料与完成任务上的新文字提问保留普通路径。受控评测新记录不复制 prompt、正文、工具声明或完整 terminal,落真实 run ID、Usage 和诊断标量;旧 A01 原件不改写。 + +零付费 `SELF` 证据由 Dev `make evidence` 保存在任务卡:Agent-Native 终态测试 RED→GREEN 和全量 Go/race;Backend 的真实 Store 首回合测试与内容泄漏反例 RED→GREEN、全量 Go/race、选定本地 live-tag 假流;Squad 完整 Go vet/test。任务自有 HTTP 机制实例经 PID、可执行文件 SHA 和组件身份核对,十项 Playwright 均通过,收据 `/tmp/fast-slow-u3-r05-task4-browser-final.json`。可见性覆盖无文字上传立即确认、文字+长材料重点、小材料原模型入口、进行中/完成后复用同一任务卡、两条有来源阶段成果、零/部分失败不出无依据选择、来源版本变化、刷新和输入焦点;390px 窄屏截图保存在 `/tmp/fast-slow-u3-r05-20261014/browser-final/browser-mobile.png`。独立临时 PostgreSQL/Redis 数据根和数据库身份核验后,`make test-backend-pg` 全量通过且两实例停止,收据 `/tmp/fast-slow-u3-r05-task4-pg-first.json`。Frontend 269 项单测及构建通过;Dev 暂存组件指针上的 `make test-pins` 与 `make test && make build` 通过,收据分别为 `/tmp/fast-slow-u3-r05-task5-pins-staged.json`、`/tmp/fast-slow-u3-r05-task5-dev-combination.json`。第一次未暂存 gitlink 的 pin 校验及浏览器夹具三项失败原收据保留,不作为产品通过证据。 + +已核对的组件准确 HEAD `GATE`:Agent-Native run 3094、Agent-Squad run 3113、Backend run 3114,作业及步骤均成功。Frontend 与 Documents 没有独立 `.gitea/workflows`,其组件独立 GATE 为 N/A;Frontend 单测/构建由 `SELF` 记录并纳入 Dev 组合 CI。最终 Dev HEAD 的 CI、独立复审、阶段6人工结论尚待固定后核对。本节只证明安全机制和假流界面行为;**r0.5 真实模型质量 `NOT_RUN`**,不能由假流推断服务商会给完整 `[DONE]`、两条有用阶段成果或最终正文。八个 PR 保持草稿,任何下一次真实模型请求都须单独资源提案与批准。 + +独立只读复审在 Dev `b01ae4e`/Backend `bd41302` 找到两项 Important(原件 `/tmp/fast-slow-u3-r05-20261014/independent-review.md`):任意供应商 `finish_reason` 可原样进入受控记录;十项浏览器原 manifest 对应旧 Dev/Backend/Native/Squad 且未记 Frontend SHA,也未走真实 HTTP 排队/启动失败。无 Critical。Backend `8f5e32d` 先用哨兵测试复现泄漏,再在记录边界把结束原因和读取结果归入有限枚举,未知值统一 `unknown`,不改变工具执行门禁;Backend `c23239e` 与 Frontend `1544781` 的任务自有假流/Playwright 两项新用例先 RED 后 GREEN:真实 HTTP 排队展示“排队中”,注册失败时宿主确认未启动、没有任务卡与无依据判断。组件全量 Go vet/test、Frontend 269 单测与构建通过,原始 SELF 收据 `/tmp/fast-slow-u3-r05-review-labels-{red,green}.json`、`/tmp/fast-slow-u3-r05-review-browser-{behavior-red,green2}.json`、`/tmp/fast-slow-u3-r05-review-fix-component-suite.json` 均保留;独立报告针对修复前对象,不冒充修复后的二次 REPLAY。 + +修复组合 Dev `93100bf` 的任务自有浏览器脚本在启动前逐项要求 Dev gitlink 等于 Backend/Frontend/Native/Squad 的实际 HEAD,并要求 Backend 嵌套 Native/Squad 同值;manifest 记录五个精确 SHA、PID 与测试二进制哈希。该组合的 **12 项**真实 HTTP/Playwright 全部通过,覆盖新增排队/启动失败,SELF `/tmp/fast-slow-u3-r05-review-final-pin-browser.json`,实例和预览已停止。相同 Backend SHA 上全新隔离 PostgreSQL/Redis 经数据库身份核验后 `make test-backend-pg` 全量通过,SELF `/tmp/fast-slow-u3-r05-review-final-pin-pg.json`,实例已停止。此前十项浏览器结果只属于旧 manifest,不再作为修复组合的当前证据。最终 Documents/Dev 指针、当前 HEAD CI 和阶段6人工结论仍须另行固定核对;本次没有付费模型调用,真实质量仍 `NOT_RUN`。 + +## 2026-10-14 r0.5 阶段 6 与一次性终态探针 + +用户已确认 r0.5 零付费实施通过阶段 6,随后单独批准**一次**受控 MiniMax-M3 工具流终态探针;批准只包含固定 Dev `0959e1c1566df9766878b2122eccc0ab42af10a3`、受审 ELF `89e4800827288aaf81cd68fdd2a7823de75033de75838e223db5e95425af80e7` 上最多一个付费请求,配置输出上限 1024 tokens、120 秒、已返回 Usage 目标 3000 tokens,不执行工具、不重试或换模型;不包含 A01、合入或部署。原提案及候选补充记录分别在 `/tmp/fast-slow-u3-r05-20261014/terminal-probe-resource-proposal.md`(SHA-256 `a7e44752b43972da6797da2a67bb6e7b7be5a129683743183a44b5976b37d947`)与同目录 `terminal-probe-candidate-addendum.md`(`b3c832c37af62acbd964bfaf8bca39aa3eea6727bc858270f6bb602fbe7a6e73`);用户答复见 `terminal-probe-authorization-decision.md`(`3e234e50498df9b375c46ad82a34bbb63121384a22657f7a8f6e9a5f0f41e75f`)。受信 [Dev CI run 3136](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/actions/runs/3136) 的三项准确 HEAD 作业均成功;本地执行前 `SELF` 预检为 25 项 PASS。这些结果不替代供应商终态与业务质量验收。 + +唯一请求的私有终态收据 `/tmp/fast-slow-u3-r05-20261014/private-probe/run-001/terminal-receipt.json`(SHA-256 `6b013965579ccaf8462c059380e84bfd9aa446d3201031edbb0ff224ed958c4e`)记录 HTTP 200、`finishReason=tool_calls`、`ReadOutcome=clean_eof`、`StreamComplete=false`、最后一帧 JSON 完整;返回 Usage 为 prompt 425、completion 88、total **513 tokens**;工具片段 2 个,放行/执行工具均 **0**,分类 `incomplete_terminal`。已返回 Usage 低于目标,但不等于独立账单。一次性 claim 和 attempt marker 均存在、字节相同;运行命令 `SELF` 收据 `/tmp/fast-slow-u3-r05-20261014/probe-code/evidence-live-once.json` 的原命令 exit 1。**授权已用尽,不补跑。** + +[作者判定](/tmp/fast-slow-u3-r05-20261014/probe-result-assessment.md) SHA-256 `5145ce0da349174fe2719460a5e7896844808ad7481c827cea2d248119f2086f` 和[独立只读评估](/tmp/fast-slow-u3-r05-20261014/probe-result-independent-assessment.md) SHA-256 `f35d1c17fe3dd3013c741754db06acf7865a1eb03417ffd03a58badf25b409c3`一致判定:这次短工具请求也未让客户端看到 `[DONE]`;现有无内容记录无法区分服务端未发、传输中断与解析漏认。`clean_eof`、结束原因、Usage 和合法 JSON 均不足以放宽工具执行门禁。r0.4 A01 仍 **FAIL**,r0.5 真实 A01 仍 **NOT_RUN**;八个 PR 保持草稿,未合入或部署。后续先评审限于当前路由的传输完成性设计,再决定是否需要新的代码或受控资源试验。 + +## 2026-10-19 r0.6 零付费实施候选:传输完成性证据 + +用户批准 [r0.6 实施计划](../proposals/2026-10-14-fast-slow-u3-r06-transport-evidence-implementation-plan.md) 进入零付费实施。没有批准任何真实供应商请求、付费终态探针、A01、合入或部署;付费探针与 A01 须另交资源提案并经用户批准。 + +各组件分支先经 agent-exec 传输与各自 main 同步,以 `merge -s ours` 保留旧分支历史。同步后 Backend main #204 把后台材料结果交付到持久、非阻塞的工作卡,不再走前台通知 run 和 `confirm_choice` 卡;经核验的建议成为工作卡单选项,失败任务不给选择。 + +当前组件候选:Agent-Native #46 `28f3ea767447e089be546fcd54d0e721798f9e85`(Task 1/2 在 `b6a8433`),Agent-Squad #53 `11ff3a0a9c0124bb278440e82062685a7789ad94`,Backend #193 `f24a6108c0101efdd25040b402b41e2e82e71c91`(Task 3 在 `611cf1b`,复审修复 `f24a610`),Core #31 `0cf03446c458e1238a3d20a031becd4450d63228`,UI #51 `fadabceadeec210d52cda7e9cec71e988d2e4482`,Frontend #79 `6f8e7ec6046604785a9c1b18a04804545a030045`;Dev #512 在 `a7978078b05c52063b05ef32401234673d72b540`(任务卡提交 `c0f00b2`)固定这些 gitlink。Documents 指针待本节提交后固定。 + +| r0.6 设计条款 | 文件/测试 | 证据来源 | +|---|---|---| +| HTTP 响应体协议、定界、读数、EOF 与未知处理 | Agent-Native `BodyBoundary` 观察及已知长度/gzip/chunked/HTTP2/DONE 对照测试(`b6a8433`) | SELF(RED→GREEN 收据在 Dev 任务卡);REPLAY(独立复审重跑 Task 1 对照);GATE(Agent-Native 准确 HEAD `28f3ea7` CI 成功) | +| SSE 尾部结构、不泄漏未知帧、缺 `[DONE]` 继续拒绝 | Agent-Native `TailEventKind`、饱和计数与无标记反例(`b6a8433`) | 同上 | +| 实际 run ID/Usage、有限白名单、失败后零后继请求 | Backend `TestEvaluationTransportObservationSanitized`、`TestEvaluationTransportEvidenceStopsAfterUnmarkedToolEOF`(`611cf1b`;`f24a610` 把工具片段数与参数字节数钳到 Agent-Native 上限 10000 与 4 MiB+1) | SELF;REPLAY(修复复审重跑通过);GATE 仅 `611cf1b`/`9bcf1b5` 已核验,`f24a610` 待核验 | +| 普通用户界面、来源、失败状态和 seq 不回退 | Frontend `e2e/work-checkpoints.playwright.ts` 任务自有 HTTP 机制实例 12 项 | SELF(Dev `a797807` 组合、Backend `f24a610` 测试二进制、实例 `/tmp/fast-slow-u2-u3-r06-20261019-r3`,12/12 通过,收据 `/tmp/cleanup-20261018/fs/ev-dev512-r06-browser-r3.json`,实例与预览已停止) | +| 供应商契约不足时不放宽、真实 A01 另审 | 计划全局约束与停止条件 | 未发真实请求;资源提案另交 | + +工作卡对齐不在已批准计划内。计划原定本轮不改 Frontend/UI 代码;但主干同步后,Dev `e790fed` 的首次机制重放只通过 6/12:带 `work` 的卡片显示 main 的工作卡,缺任务发现、来源与限制,旧 e2e 仍等待已移除的通知 run 和 `.hitl-choice`。宿主选择可回退的方案,跟随 main 的工作卡:UI #51 在工作卡内渲染受信任务视图,保存结果与任务结论不同时仍显示完整结果;Frontend #79 固定该 UI,把针对已移除机制的断言换成工作卡等价断言(失败:无选项且只有发送 run;选择:恰好三条经核验建议,刷新后仍为三条),r0.5 的用户保证继续断言。**此 UI/Frontend 改动尚未获批**,作为阶段 6 的单独决策项交用户。 + +全新独立会话复审 Dev `bc69a66`(报告 `/tmp/fast-slow-u2-u3-r06-20261019/independent-review.md`):0 Critical、3 Important。I-1:有任务快照时工作卡只显示结论(首段、最多 1200 字符),隐藏其余结果;UI `fadabce` 以多段落测试先 RED 后 GREEN 修复。I-2:评测清洗未钳制工具片段数与参数字节数;Backend `f24a610` 修复。I-3:UI/Frontend 范围变化须阶段 6 明确签认(见上段)。另一全新独立会话复审修复后批准:REPLAY UI `fadabce` 的 `npx vitest run test/work-cards.test.tsx` 17 项通过、`npx tsc --noEmit` exit 0;Backend `f24a610` 的 `GOWORK=off go test -tags live ./internal/server -run TestEvaluationTransport -count=1` 通过、`go vet ./...` exit 0。 + +其他证据:Dev `a797807` 的 `make test-pins` 输出 `pins ok`(SELF)。Dev `e790fed` 本地 `make test` 有一项治理测试 `test_close_self_child_keeps_initiative_target_from_archive` 失败,在 main 检出上同样复现;`e790fed` 准确 HEAD 的 Dev CI(`test`、`test-pg`、`svc-realcombo`)成功,故记为本地环境非阻塞项。已核验准确 HEAD GATE 成功:Agent-Native `28f3ea7`、Agent-Squad `11ff3a0`、Backend `611cf1b` 与 `9bcf1b5`、Dev `e790fed`。UI `fadabce`、Frontend `6f8e7ec`、Backend `f24a610` 和最终 Dev HEAD 的 GATE **尚未核验**,因 Gitea HTTPS API 当前 TLS 握手失败;阶段 6 结论不得把这些写成已通过。 + +质量状态不变:r0.6 真实 MiniMax 复测 `NOT_RUN`,r0.5 A01 `NOT_RUN`,r0.4 A01 **FAIL** 维持;客户端看不到 `[DONE]` 的路由侧原因可能仍为 `unknown`。本节只证明零付费机制和假流界面行为。八个 PR 保持草稿,未合入或部署。下一步由用户对本候选及工作卡决策项作阶段 6 评审;任何真实请求须另交资源提案。 + +## 2026-10-19 r0.6 阶段 6 与一次性传输边界探针 + +用户已接受 r0.6 零付费候选(含 I-3 工作卡 UI/Frontend 范围变化)通过阶段 6,条件是准确 HEAD CI 成功;该条件现已满足:UI `fadabce` 运行 4104/4105、Backend `f24a610` 运行 4106/4107、Frontend `6f8e7ec` 运行 4108/4109、Dev `b1d40f6` 运行 4116,以及 Dev `93668915ed9dd83282bb37fa0b792dabc1571bc5` 的 [Dev CI run 4135](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/actions/runs/4135)(`test`、`test-pg`、`svc-realcombo` 均成功),均记为 **GATE**。上节"UI `fadabce`、Frontend `6f8e7ec`、Backend `f24a610` 和最终 Dev HEAD 的 GATE 尚未核验"一句由此取代。 + +用户随后只批准资源提案 `/home/weichen/.local/share/agent-system-evidence/fast-slow-u3-r06-20261019/resource-proposal.md`(SHA-256 `c0de0c45c5f30d01565ab57aaa996b018e393794abae06e1936684b78684c00b`)的阶段 1:最多一个 MiniMax-M3 请求,固定沿用 r0.5 天气工具 prompt,输出上限 1024 tokens、120 秒、已返回 Usage 目标 3000 tokens,不执行工具、不重试、不换模型;阶段 2 的 A01 不在批准范围内。用户决定记录为同目录 `probe-authorization-decision.md`(SHA-256 `370db44b516531924a68d572e91cfd121e55309ed8e38af8b33a4867e7670c8c`)。 + +探针由 agent-exec 执行者在私有仓 `probe-code`(HEAD `47c6b9a`)中自 r0.5 探针改写。全新独立复审发现一项阻断:`eof_unproven` 未限定于 `clean_eof`,HTTP/2 读错误会被误标;修复后由另一全新会话复审 PASS。二进制 SHA-256 `ec79d00669202588ad4635e9dcd18da196703a0b5f139bd3b165a94b9e408548`,固定 Dev `9366891` 与 Agent-Native `28f3ea7`。一名执行者自报曾带 `--execute-once`、但未设批准环境变量运行该二进制一次;它在第一道守卫即被拒,未读任何文件、未访问网络,当时私有目录与路由均不存在,无副作用。执行前 `SELF` 预检:prepare 24/24 PASS(`preflight-prepare.json`,SHA-256 `c22559f2cf4ca2364ede9f5fb9511bf0006cb0a1fa6614d9045da2aa5b2ad30d`),execute-ready 25/25 PASS(`preflight-execute-ready.json`,`ac0a96e37296abf3b4f4dfc141868b97e986d0e8b1ccf8296f10f28ed7b5c9c1`),providerRequests 0。路由文件只核元数据(0600、https `api.minimaxi.com` `/v1`、MiniMax-M3、密钥存在、无 fallback),其 SHA-256 绑定在批准记录中。 + +唯一请求(2026-10-19 08:55 UTC)的私有收据 `private-probe/run-001/terminal-receipt.json`(SHA-256 `21bd00d09b810bbc99b7be60de0f4cb3d50937ced8c7ee0c3f483b74badde4a1`)记录 HTTP 200、`finishReason=tool_calls`、`readOutcome=clean_eof`、`streamComplete=false`、最后一帧 JSON 完整、尾部 `json_frame`、SSE 结构事件 8 个;返回 Usage 为 prompt 425、completion 47、total **472 tokens**;工具片段 1 个、参数 28 字节且合法,放行/执行工具均 **0**。响应体边界为 `http2`/`http2_stream`,无声明长度,读取 2,844 字节,见到 EOF,长度关系 `unknown`,无透明解压;`failureClass=incomplete_terminal`、`diagnosis=eof_unproven`。一次性 claim 与 attempt marker 字节相同。**授权已用尽,不补跑。** + +作者判定 `probe-assessment.md`(SHA-256 `5b69fcbed8992f33cac6b2214b9eac5166c96c0adb8dc09f03f64fceea69a37c`)与全新只读独立评估(AGREE;纠正草稿三处措辞错误,结论不变)均适用判读表"framing 为 chunked/close_delimited/http2_stream 且长度 unknown"一行:只能确认 EOF,无法证明完整 → **INCONCLUSIVE**,不再发请求;阶段 2 A01 **不具备资格**(须 `done_marker`)。r0.5 与 r0.6 两次客户端都看到 `tool_calls`+Usage+完整末帧 JSON+clean EOF,且**均未观察到 `[DONE]`**;现有证据无法区分"供应商未发 `[DONE]`"与"中间一跳在 `[DONE]` 前结束流"(r0.5 未记录传输协议)。候选的保守规则(缺 `[DONE]` 即 `incomplete_terminal`,不放行工具)表现正确。若把 `tool_calls`+Usage+clean EOF 认作该供应商的完成信号,属规则变更,须另行设计并由用户决定。r0.4 A01 仍 **FAIL**,r0.5/r0.6 真实 A01 **NOT_RUN**;八个 PR 保持草稿,未合入或部署。 + +## 2026-10-19 部分收口 + +用户在 r0.6 一次性传输边界探针判定 INCONCLUSIVE 后选择部分收口:合入 r0.6 零付费机制(缺 `[DONE]` 即 `incomplete_terminal`、不放行工具的保守口径不变;阶段 6 已通过,各组件准确 HEAD CI 成功),组件 PR Agent-Native #46、Agent-Squad #53、Core #31、UI #51、Frontend #79 已合入,Backend #193、Documents #95、Dev #512 随本次收口合入。真实模型质量**未解决**:r0.4 A01 仍 **FAIL**,r0.5/r0.6 真实 A01 **NOT_RUN**,Task 7 模型质量验收未通过;是否把 `tool_calls`+Usage+干净 EOF 认作该供应商的完成信号属口径变更,留待后续独立任务设计与用户决策。本节取代前文“八个 PR 保持草稿、未合入”的表述;未部署。