状态: Draft
组件: Runtime Acceptance 链路(Completion Gate → Verification Gate → Decider → AcceptanceService),Stop Reason / Progress 评估,Plan 结构化校验,Todo 生命周期管理
日期: 2026-05-07
相关技术: ReAct Loop, Plan-Execute 阶段, Task Completion Signal, Verification Orchestrator, Anti-Stall, Accept Gate
1. 摘要
本 RFC 针对 NeoCode Runtime 验收决策链路的架构级缺陷提出重设计方案。当前设计的核心矛盾是:系统靠关键词推断任务类型来选择验收标准,而不是读模型在 plan 阶段自己声明的验收条件。这导致验收标准来源错误、TaskKind 推断不可靠、Decider 硬编码建议覆盖度低、以及防循环机制可被操纵。
本方案不是增加更多补丁覆盖症状,而是从以下五个层面重新设计:
- 验收标准来源:Plan.Verify 改为结构化
[]AcceptCheck,验收 Gate 直接映射,零推断
- 验收触发:引入完成声明检查,未声明完成不进入验收
- 验收结构:改为单一 Accept Gate,删除 Decider 决策权
- 执行与验收分离:验收阶段不重新执行命令,只检查执行期事实
- 防循环:删除 FinalIntercept,Progress 恢复硬终止,Exploration 不再 reset streak
2. 背景与问题
当前系统已经具备:
planning.go 的 plan 解析与 PlanArtifact 构建能力
verify/orchestrator.go 的 verifier 流水线能力
controlplane/progress.go 的 Progress 评估能力
task_completion 结构化信号的解析能力
但当前仍有几个关键问题:
2.1 Plan 定义了标准,验收不读标准
PlanArtifact 已包含 Verify []string——用户在 plan 阶段明确知道了"怎么算完成"并手动批准了计划。但验收逻辑从不读取 CurrentPlan.Verify,而是靠 inferTaskKindFromInput 从用户原始输入关键词推断任务类型,靠 Decider 基于 Facts 字段映射推断验证目标。
Plan 与验收是两个不连通的世界。
2.2 TaskKind 推断不可靠却被当作验收依据
InferTaskKind 基于关键词匹配("创建文件"→write,"看看"→read-only),置信度最高 0.9,大量 case 在 0.7。plan 阶段可能定义了复杂的验证标准(如"集成测试通过"),但验收时系统根据用户输入里的关键词判断这是个"chat"任务,结果 chat 的 verifier profile(几乎无验证)被应用。
2.3 Decider 的建议是硬编码规则,覆盖度低
decider/decide.go 的 RequiredNextActions 纯靠 switch-case 硬编码:pending_todo → 建议 todo_write,unverified_write → 建议 filesystem_read_file。模型实际需要 bash 跑 go test、需要调用子代理——Decider 一无所知。
2.4 验收与执行混为一谈
build/test/lint 在验收阶段由 verifier 重新执行。编译失败 → SoftBlock → 模型回执行阶段修复 → 再进入验收 → 再编译 → 再 SoftBlock。验收被当成了"带验证的执行循环"。
2.5 防循环机制可被操纵
finalInterceptStreak 可被 shouldPromotePendingFinalProgress 无限 reset——模型每轮读取不同文件即可续命。Progress 系统退化为纯 reminder,真正终止 Run 的是 FinalIntercept,但后者形同虚设。
3. 典型用户场景
场景 1:用户批准了 plan,但验收时系统不检查 plan 的标准
旧行为:
用户说"修复登录 bug",系统在 plan 阶段生成 plan,Verify 列表写了"go test ./auth/... 通过"。用户批准 plan 后进入 build/execute。模型写了修复代码但未跑测试,直接声明完成。验收时系统根据用户原始输入里的关键词"修复"推断为 workspace_write 任务,跑 verifier profile 发现 HasUnverifiedWrites=true 但文件存在,返回 AcceptanceAccepted。用户回来发现测试根本没跑过。
新行为:
plan 阶段 Verify 是 [{"kind": "command", "target": "go test ./auth/..."}]。build/execute 阶段模型声明完成,验收 Gate 读取 Plan.Verify 发现要求测试通过,检查执行期事实发现没有 go test 验证证据 → Failed,用户界面展示"任务交付不完整:plan 要求测试通过,但未检测到测试执行证据"。
场景 2:简单问答被推断为写入任务,验收过度严格
旧行为:
用户说"帮我看看这段代码什么意思"。InferTaskKind 匹配到"看看"关键词,但用户输入里同时含"代码",推断为 mixed 或 workspace_write。验收时应用写入任务的 verifier profile,要求 HasSuccessfulWorkspaceWrite 和验证证据。模型只是读了文件并回答了问题,没有写入 → AcceptanceContinue 循环,用户等不到回答。
新行为:
plan 阶段模型输出 [{"kind": "output_only"}]。验收 Gate 映射为 output_presence 检查项,只要 LastAssistantText 非空就通过。没有推断,没有误判。
场景 3:用户在界面上看着 agent 转了 20 轮,不知道为什么还不停止
旧行为:
用户说"修复登录 bug"。agent 开始修复,但不断读取不同文件寻找线索。用户在界面上看到一轮轮的"reading file"、"searching"、"reading file",转了 20 轮仍在继续。没有任何提示告诉用户发生了什么,也没有终止迹象。用户最终手动打断,不知道 agent 到底做了什么、进度怎样、什么时候会停。
新行为:
用户看到相同的任务。agent 连续 3 轮没有实质进展时,界面提示"已连续 3 轮无实质进展,若继续当前策略任务将被终止"。agent 在第 5 轮仍无写入或验证进展,Run 自动终止,界面展示"任务因长时间无实质进展而终止"。用户知道发生了什么,知道为什么终止,可以决定是否重新发起任务。
场景 4:用户看到"任务完成",实际上编译失败了
旧行为:
用户说"写个排序算法"。agent 写了代码后宣布完成,界面显示"任务完成"。用户拿着代码编译,发现编译失败,完全不知道 agent 为什么声称完成但代码根本跑不通。用户只能手动去读 agent 的长输出记录,找到错误信息,然后自己修复或重新发起任务。
新行为:
agent 在执行阶段自己调用 go build 发现编译失败后自行修复,修复通过后才声明完成。如果 agent 未修复就声明完成,验收时发现执行期没有编译通过的事实 → Failed,界面直接展示"编译失败(syntax error at line 12),任务未交付"。用户看到明确的失败原因,可以基于错误信息决定下一步。
4. 设计目标
本方案要求同时满足:
- 验收标准必须来自模型在 plan 阶段自己声明的条件,不是系统推断。
- 完成声明(
task_completion)是验收的必要条件。
- 执行与验收严格分离——验收不重新执行命令,只做一次性确认。
- 防循环机制不可被操纵——Exploration Progress 不再 reset streak。
- 删除无价值的抽象——Decider、TaskKind 推断、SoftBlock/HardBlock 区分。
- 验收只有两种结果:Accepted / Failed,没有 Continue。
5. 非目标
本 RFC 不处理:
- 不修改 model provider 协议或 tool schema。
- 不改动 plan 阶段的前端交互(只改
Verify 字段类型)。
- 不重新设计 todo 系统的状态机(只改生命周期边界和清理策略)。
- 不在验收阶段引入 LLM 作为判断者(保持确定性检查)。
- 不删除 PR 569 中合理的非 runtime 改动。
6. 核心设计
6.1 Plan.Verify 结构化:验收标准的唯一锚点
唯一的小改动:Verify 从自然语言 []string 改为结构化 []AcceptCheck。
type summaryCandidate struct {
Goal string `json:"goal"`
KeySteps []string `json:"key_steps"`
Constraints []string `json:"constraints"`
Verify []AcceptCheck `json:"verify"` // 从 []string 改为结构化
ActiveTodoIDs []string `json:"active_todo_ids"`
}
type AcceptCheck struct {
Kind string `json:"kind"` // "command", "file_exists", "output_only"
Target string `json:"target"`
}
简单问答的 plan:"verify": [{"kind": "output_only"}]
写入/修复的 plan:"verify": [{"kind": "command", "target": "go test ./..."}, {"kind": "file_exists", "target": "login.go"}]
验收阶段逐条映射为 Accept Gate 检查项,无需推断、无中间层。
这样设计的原因是:当前 Verify 是自然语言,系统无法直接消费,导致验收时只能靠 InferTaskKind 推断任务类型、靠 Decider 硬编码规则来猜验收标准。改为结构化后,模型在 plan 阶段直接声明"我要检查什么",验收阶段直接执行,人和系统读同一份数据。改动量极小(只加一个字段类型),但消除了整个推断层。
6.2 Accept Gate:Plan.Verify 直接映射
Plan.Verify kind |
检查函数 |
检查内容 |
"output_only" |
checkOutputPresence |
LastAssistantText 非空 |
"command" |
checkCommandSuccess |
执行期有对应命令的成功验证事实 |
"file_exists" |
checkFileExists |
目标文件存在 |
| (额外兜底) |
checkTodoConvergence |
存在 required todo 时全部终态 |
验收时遍历 Plan.Verify,逐条对应的检查函数执行。全部通过 → Accepted,任何一条失败 → Failed(生成完整失败报告)。
没有 Continue。没有 SoftBlock。验收是终态。
这样设计的原因是:当前 Completion Gate → Verification Gate → Decider 三层存在决策权冲突(Decider accepted 可被 Completion Gate 降级为 continue)。改为直接映射后,所有检查并行执行、结果聚合,不存在层级覆盖。Kind 和检查逻辑是固定的映射关系,不需要 Name、Required、闭包这些包装层。同时删除 Continue 结果是因为验收不是"过滤漏斗",而是"终态确认"——模型声明完成后,系统只做一次性 yes/no 判定。
6.3 完成声明是验收的必要条件
在 run.go 的验收触发点增加完成声明必要条件:
if !hasToolCalls {
completionSignaled, _ := maybeParseCompletionTurnOutput(turnOutput.assistant)
if !completionSignaled {
// 模型未声明完成,直接 continue,不触发任何验收逻辑
continueTurn(...)
continue
}
// 进入 Accept Gate
report := acceptgate.Evaluate(ctx, input)
}
这样设计的原因是:当前 !hasToolCalls 即触发验收,无论模型是否主动声明完成。这导致 chat/read-only 零门槛旁路——模型没有完成意愿,系统却启动完整验收链路。行业共识(Claude Code Ralph Loop、OpenCode、Devin)都是"完成 = 主观声明 + 客观验证",本设计把 task_completion 从"摆设"升级为"必要条件"。
6.4 执行与验收严格分离
执行阶段:模型写代码、调用工具、遇到错误自行修复。build/test/lint 由模型在执行阶段主动调用,结果记录在 ToolResult 中。
验收阶段:command_success 检查项只检查执行期是否留下了对应命令的成功验证事实,不重新执行命令。
- 模型没调用验证工具 → Failed("缺少验证证据")
- 验证工具在执行期返回失败且模型未修复 → Failed
这样设计的原因是:当前 verifier 在验收阶段重新执行 go build/go test,导致验收被当成了"带验证的执行循环"。行业共识(Devin、Copilot Workspace)把验证作为交付门槛,不是在验收阶段重新跑测试,而是检查执行阶段是否已经跑过且通过。分离后,编译失败应在执行阶段解决,验收阶段只确认"是否交付了正确的东西"。
6.5 删除 Decider 和 TaskKind
删除 Decider 包全部内容(decide.go、infer.go、types.go 中的决策类型)。
RequiredNextActions 是硬编码规则,只有 todo_write/filesystem_read_file/filesystem_glob 三种,覆盖度极低。
UserVisibleSummary 是固定模板("任务完成。"),信息量低。Accept Gate 的失败报告原生包含 user_visible_summary 字段,直接替代。
删除 InferTaskKind 和 DeriveEffectiveTaskKind。
- 推断基于关键词匹配,置信度低,且推断结果不用于任何验收决策。
- plan 阶段模型已通过
Verify 的 kind 字段声明了任务类型,不需要再推断。
这样设计的原因是:Decider 占据了架构的核心位置,但其建议生成是硬编码规则,无法覆盖真实任务的多样性(如需要 bash 跑测试、调用子代理等)。TaskKind 推断不稳定,却被当作验收依据。两者都是"系统自己猜标准"的产物,而 plan 的 Verify 已经让模型自己声明了标准,推断层变得多余。
6.6 Progress 硬终止,删除 FinalIntercept
删除:
finalInterceptStreak、pendingFinalProgress
mustUseToolAfterFinalContinue、noToolAfterFinalContinueStreak
lastAcceptanceBlockedReason、shouldPromotePendingFinalProgress
Progress 评估规则:
- Business Progress(写入+验证通过 / todo 终态变化)→ reset streak,任务在推进
- Exploration Progress(读文件、搜索)→ 不 reset streak,这不是实质进展。Exploration 有窗口上限(默认 3 轮),超过后 NoProgress 开始累计
- 无进展 → streak 单调递增
if next.NoProgressStreak >= input.NoProgressLimit { // 默认 5
next.ShouldTerminate = true
}
if next.RepeatCycleStreak >= input.RepeatCycleLimit { // 默认 3
next.ShouldTerminate = true
}
这样设计的原因是:当前 shouldPromotePendingFinalProgress 允许模型每轮读取不同文件就 reset finalInterceptStreak,导致死循环。Exploration 不是实质进展——读 10 个不同文件不代表任务在推进,只代表模型在找线索。只有 Business Progress(写入、验证通过、todo 收敛)才证明任务有交付推进,才应该 reset streak。
6.7 删除 SoftBlock / HardBlock,统一为 Pass/Fail
新架构只有两种 verifier 结果:Pass / Fail。
- 原
HardBlock 场景(文件缺失、零交付、内容不匹配)→ Fail
- 原
SoftBlock 场景(todo pending)→ 在验收前被拦截(Layer 0/1),不进入 Accept Gate
- 原
SoftBlock 场景(build/test 失败)→ 不应在验收阶段出现,执行阶段就应修复;若到验收阶段仍有失败证据 → Fail
这样设计的原因是:SoftBlock 被滥用于几乎所有"内容不正确"的场景(编译失败、测试失败、文件缺失全部返回 SoftBlock),本质上是把验收降级为"辅助提示系统"。行业共识(Devin CI fail = 未交付、Copilot Workspace test fail = PR 不 merge)是验证失败即终态。唯一合理的"可继续"场景是 todo pending,但这应在验收前被 Layer 0/1 拦截,不需要 SoftBlock 语义。
6.8 Run 边界与 Todo 生命周期
默认清空策略:回滚 PR 569 的"默认保留"语义,改为默认清空,只有明确续做意图才保留。
终止时统一清理:所有终止路径(Accepted / Failed / Incomplete / user_interrupt / budget_exceeded / max_turn_exceeded / fatal_error)统一清空 session.Todos。
删除 stale_todo_reset 提示:如果 todo 边界正确,stale todo 本不应大规模遗留。把"判断哪些 todo 属于上一个任务"推给模型是不可靠的。
这样设计的原因是:当前 Run 终止时从不清理 todo,加上 PR 569 把语义反转为"默认保留",导致旧 todo 大量遗留。stale_todo_reset 提示是症状治疗而非根因修复——让模型为系统的边界污染买单。
7. 与现有模块的关系
runtime
run.go:接入 task_completion 为验收必要条件;删除 FinalIntercept 字段;Progress 评估后硬终止;删除 Decider 调用
state.go:删除 finalInterceptStreak、pendingFinalProgress、mustUseToolAfterFinalContinue、noToolAfterFinalContinueStreak、lastAcceptanceBlockedReason
acceptance_service.go:删除多层合并逻辑,替换为单一 Accept Gate
turn_control.go:删除 shouldPromotePendingFinalProgress;删除 PR 569 的 HasUnverifiedWrites 捷径
todo_run_boundary.go:回滚 PR 569 语义反转
planning.go:Verify 从 []string 改为 []AcceptCheck
task_kind.go:删除 InferTaskKind 和 DeriveEffectiveTaskKind,清理文件或删除
controlplane
progress.go:恢复硬终止能力;删除提醒-纯提醒逻辑;Exploration Progress 增加窗口上限
completion.go:task_completion 信号纳入 CompletionState
verify
orchestrator.go:全量执行所有 verifier,删除短路
command_success.go:SoftBlock → Fail
todo_convergence.go:permission_wait/user_input_wait/external_resource_wait 从 HardBlock 移除,转为 Execute 阶段前置状态检查
git_diff.go:删除,零交付检测由 WorkspaceWriteVerifier(基于 HasSuccessfulWorkspaceWrite 和 fileSnapshot diff)替代
decider
- 删除
decide.go、infer.go、types.go 中的决策类型
- 删除
DecisionAccepted/DecisionFailed/DecisionContinue/DecisionIncomplete/DecisionBlocked
gateway
contracts.go 中引用 TaskKind 或 DecisionStatus 的类型需要删除或更新
- Plan 序列化:
PlanArtifact.Summary.Verify 类型变更影响 JSON 编解码和 RPC 传输,gateway 侧的反序列化需要兼容新旧两种格式
- 验收事件:
AcceptanceContinue 状态消失,新增的 Accept Gate 失败报告(含逐条检查结果)需要更新 gateway 的事件 payload 结构
tui
- Plan 审批界面:
Verify 改为结构化后,可逐条渲染检查项(图标 + kind + target),而非之前的自然语言文本
- 验收结果渲染:失败时不再显示"任务继续",而是展示逐条检查的完整报告(每个检查项、通过/失败、原因),TUI 需要对应的失败详情展示方式
- Progress Warning:新增的"连续 N 轮无实质进展"提示应以即时状态更新展示,不能只在日志里
session
PlanArtifact.Summary.Verify 类型变更影响 session 持久化的序列化/反序列化
- 已持久化的旧 session(
Verify 为 []string)需要向后兼容:反序列化时两种格式都接受,优先 []AcceptCheck,零值时回退读取旧 []string
config
- 删除
VerificationConfig.MaxNoProgress(与 RuntimeConfig.MaxNoProgressStreak 命名冲突)
- 删除
verifierGitDiff 常量及默认配置(Verifiers["git_diff"])
prompt assets
- 删除与 FinalIntercept/SoftBlock 绑定的旧提醒模板
- 删除
stale_todo_reset 提示模板
- 新增 Progress Warning 注入模板
8. 测试场景
- Plan.Verify 为
[{"kind": "output_only"}],模型声明完成且有输出 → Accepted。
- Plan.Verify 为
[{"kind": "command", "target": "go test ./..."}],执行期无测试验证事实 → Failed。
- Plan.Verify 为
[{"kind": "file_exists", "target": "login.go"}],文件不存在 → Failed。
- 模型无 tool calls 但未输出
task_completion → 不触发验收,直接 continue。
- 模型输出
task_completion 但仍有 tool calls → continue(工具优先)。
- Run 以 user_interrupt 终止 → session.Todos 被清空。
- Run 以 AcceptanceFailed 终止 → session.Todos 被清空。
- 模型连续 5 轮调用不同
filesystem_read_file(无 Business Progress)→ NoProgressStreak 达到 5,Run 终止为 Incomplete。
- 模型连续 3 轮调用相同
filesystem_read_file 读取相同文件 → RepeatCycleStreak 达到 3,Run 终止。
- 模型写入代码后未调用验证工具 →
command_success 检查失败 → Failed。
- Todo pending + build 失败同时存在 → Accept Gate 报告同时包含两者。
NoProgressStreak 达到 2 且存在 open required todo → 不注入 <stale_todo_reset>。
- 用户空输入触发新 Run → 旧 todo 被清空。
- 用户输入"继续修复" → 旧 todo 保留。
9. 假设与默认决策
- Plan 是 plan-execute 流的必经阶段,每个任务都有
PlanArtifact。
Verify 改为 []AcceptCheck 后,旧 plan(Verify 是 []string)需要向后兼容:读取时同时接受两种格式,[]AcceptCheck 优先。
MaxNoProgressStreak 默认值 5 和 MaxRepeatCycleStreak 默认值 3 在恢复硬终止后仍然适用。
- 删除
finalInterceptStreak 不会导致已有测试大面积失效——已有测试 TestAcceptanceContinueWithoutToolCallStopsAsIncomplete 将改为基于 NoProgressStreak 终止。
- 模型在收到
VerificationFail 后能正确理解任务终止(需在 prompt assets 中明确)。
- PR 569 中的合理前端改动(思考流展示、checkpoint 交互优化、文件变更去重)不在本次回滚范围内,仅回滚 runtime 验收链路的四个问题。
ExplorationWindow 默认值设为 3(即连续 3 轮 Exploration Progress 后,第 4 轮若仍无 Business Progress,开始累计 NoProgressStreak)。
WorkspaceWriteVerifier 依赖 toolExecutionSummary 中的 HasSuccessfulWorkspaceWrite 和 fileSnapshot diff 数据,这些数据在 toolexec.go 中已可靠采集。
10. 一句话结论
NeoCode 验收机制的问题不是"gate 有 bug",而是"验收标准来源错误"——系统靠关键词推断任务类型,而不是读模型在 plan 阶段自己声明的验收条件。新架构通过结构化 Plan.Verify([]AcceptCheck)让验收 Gate 直接映射检查项——模型说"output_only"就检查输出、说"command: go test"就检查测试证据,零推断,零中间层。同时通过 Progress 硬终止 替代可被操纵的 FinalIntercept,删除 Decider 决策权、TaskKind 推断、SoftBlock 消除架构冗余。
状态: Draft
组件: Runtime Acceptance 链路(Completion Gate → Verification Gate → Decider → AcceptanceService),Stop Reason / Progress 评估,Plan 结构化校验,Todo 生命周期管理
日期: 2026-05-07
相关技术: ReAct Loop, Plan-Execute 阶段, Task Completion Signal, Verification Orchestrator, Anti-Stall, Accept Gate
1. 摘要
本 RFC 针对 NeoCode Runtime 验收决策链路的架构级缺陷提出重设计方案。当前设计的核心矛盾是:系统靠关键词推断任务类型来选择验收标准,而不是读模型在 plan 阶段自己声明的验收条件。这导致验收标准来源错误、TaskKind 推断不可靠、Decider 硬编码建议覆盖度低、以及防循环机制可被操纵。
本方案不是增加更多补丁覆盖症状,而是从以下五个层面重新设计:
[]AcceptCheck,验收 Gate 直接映射,零推断2. 背景与问题
当前系统已经具备:
planning.go的 plan 解析与PlanArtifact构建能力verify/orchestrator.go的 verifier 流水线能力controlplane/progress.go的 Progress 评估能力task_completion结构化信号的解析能力但当前仍有几个关键问题:
2.1 Plan 定义了标准,验收不读标准
PlanArtifact已包含Verify []string——用户在 plan 阶段明确知道了"怎么算完成"并手动批准了计划。但验收逻辑从不读取CurrentPlan.Verify,而是靠inferTaskKindFromInput从用户原始输入关键词推断任务类型,靠 Decider 基于Facts字段映射推断验证目标。Plan 与验收是两个不连通的世界。
2.2 TaskKind 推断不可靠却被当作验收依据
InferTaskKind基于关键词匹配("创建文件"→write,"看看"→read-only),置信度最高 0.9,大量 case 在 0.7。plan 阶段可能定义了复杂的验证标准(如"集成测试通过"),但验收时系统根据用户输入里的关键词判断这是个"chat"任务,结果 chat 的 verifier profile(几乎无验证)被应用。2.3 Decider 的建议是硬编码规则,覆盖度低
decider/decide.go的RequiredNextActions纯靠 switch-case 硬编码:pending_todo→ 建议todo_write,unverified_write→ 建议filesystem_read_file。模型实际需要bash跑go test、需要调用子代理——Decider 一无所知。2.4 验收与执行混为一谈
build/test/lint 在验收阶段由 verifier 重新执行。编译失败 →
SoftBlock→ 模型回执行阶段修复 → 再进入验收 → 再编译 → 再SoftBlock。验收被当成了"带验证的执行循环"。2.5 防循环机制可被操纵
finalInterceptStreak可被shouldPromotePendingFinalProgress无限 reset——模型每轮读取不同文件即可续命。Progress 系统退化为纯 reminder,真正终止 Run 的是 FinalIntercept,但后者形同虚设。3. 典型用户场景
场景 1:用户批准了 plan,但验收时系统不检查 plan 的标准
旧行为:
用户说"修复登录 bug",系统在 plan 阶段生成 plan,Verify 列表写了"go test ./auth/... 通过"。用户批准 plan 后进入 build/execute。模型写了修复代码但未跑测试,直接声明完成。验收时系统根据用户原始输入里的关键词"修复"推断为
workspace_write任务,跑 verifier profile 发现HasUnverifiedWrites=true但文件存在,返回AcceptanceAccepted。用户回来发现测试根本没跑过。新行为:
plan 阶段
Verify是[{"kind": "command", "target": "go test ./auth/..."}]。build/execute 阶段模型声明完成,验收 Gate 读取 Plan.Verify 发现要求测试通过,检查执行期事实发现没有go test验证证据 →Failed,用户界面展示"任务交付不完整:plan 要求测试通过,但未检测到测试执行证据"。场景 2:简单问答被推断为写入任务,验收过度严格
旧行为:
用户说"帮我看看这段代码什么意思"。
InferTaskKind匹配到"看看"关键词,但用户输入里同时含"代码",推断为mixed或workspace_write。验收时应用写入任务的 verifier profile,要求HasSuccessfulWorkspaceWrite和验证证据。模型只是读了文件并回答了问题,没有写入 →AcceptanceContinue循环,用户等不到回答。新行为:
plan 阶段模型输出
[{"kind": "output_only"}]。验收 Gate 映射为output_presence检查项,只要LastAssistantText非空就通过。没有推断,没有误判。场景 3:用户在界面上看着 agent 转了 20 轮,不知道为什么还不停止
旧行为:
用户说"修复登录 bug"。agent 开始修复,但不断读取不同文件寻找线索。用户在界面上看到一轮轮的"reading file"、"searching"、"reading file",转了 20 轮仍在继续。没有任何提示告诉用户发生了什么,也没有终止迹象。用户最终手动打断,不知道 agent 到底做了什么、进度怎样、什么时候会停。
新行为:
用户看到相同的任务。agent 连续 3 轮没有实质进展时,界面提示"已连续 3 轮无实质进展,若继续当前策略任务将被终止"。agent 在第 5 轮仍无写入或验证进展,Run 自动终止,界面展示"任务因长时间无实质进展而终止"。用户知道发生了什么,知道为什么终止,可以决定是否重新发起任务。
场景 4:用户看到"任务完成",实际上编译失败了
旧行为:
用户说"写个排序算法"。agent 写了代码后宣布完成,界面显示"任务完成"。用户拿着代码编译,发现编译失败,完全不知道 agent 为什么声称完成但代码根本跑不通。用户只能手动去读 agent 的长输出记录,找到错误信息,然后自己修复或重新发起任务。
新行为:
agent 在执行阶段自己调用
go build发现编译失败后自行修复,修复通过后才声明完成。如果 agent 未修复就声明完成,验收时发现执行期没有编译通过的事实 →Failed,界面直接展示"编译失败(syntax error at line 12),任务未交付"。用户看到明确的失败原因,可以基于错误信息决定下一步。4. 设计目标
本方案要求同时满足:
task_completion)是验收的必要条件。5. 非目标
本 RFC 不处理:
Verify字段类型)。6. 核心设计
6.1 Plan.Verify 结构化:验收标准的唯一锚点
唯一的小改动:
Verify从自然语言[]string改为结构化[]AcceptCheck。简单问答的 plan:
"verify": [{"kind": "output_only"}]写入/修复的 plan:
"verify": [{"kind": "command", "target": "go test ./..."}, {"kind": "file_exists", "target": "login.go"}]验收阶段逐条映射为 Accept Gate 检查项,无需推断、无中间层。
这样设计的原因是:当前
Verify是自然语言,系统无法直接消费,导致验收时只能靠InferTaskKind推断任务类型、靠 Decider 硬编码规则来猜验收标准。改为结构化后,模型在 plan 阶段直接声明"我要检查什么",验收阶段直接执行,人和系统读同一份数据。改动量极小(只加一个字段类型),但消除了整个推断层。6.2 Accept Gate:Plan.Verify 直接映射
kind"output_only"checkOutputPresenceLastAssistantText非空"command"checkCommandSuccess"file_exists"checkFileExistscheckTodoConvergence验收时遍历
Plan.Verify,逐条对应的检查函数执行。全部通过 → Accepted,任何一条失败 → Failed(生成完整失败报告)。没有 Continue。没有 SoftBlock。验收是终态。
这样设计的原因是:当前 Completion Gate → Verification Gate → Decider 三层存在决策权冲突(Decider accepted 可被 Completion Gate 降级为 continue)。改为直接映射后,所有检查并行执行、结果聚合,不存在层级覆盖。
Kind和检查逻辑是固定的映射关系,不需要Name、Required、闭包这些包装层。同时删除 Continue 结果是因为验收不是"过滤漏斗",而是"终态确认"——模型声明完成后,系统只做一次性 yes/no 判定。6.3 完成声明是验收的必要条件
在
run.go的验收触发点增加完成声明必要条件:这样设计的原因是:当前
!hasToolCalls即触发验收,无论模型是否主动声明完成。这导致 chat/read-only 零门槛旁路——模型没有完成意愿,系统却启动完整验收链路。行业共识(Claude Code Ralph Loop、OpenCode、Devin)都是"完成 = 主观声明 + 客观验证",本设计把task_completion从"摆设"升级为"必要条件"。6.4 执行与验收严格分离
执行阶段:模型写代码、调用工具、遇到错误自行修复。build/test/lint 由模型在执行阶段主动调用,结果记录在
ToolResult中。验收阶段:
command_success检查项只检查执行期是否留下了对应命令的成功验证事实,不重新执行命令。这样设计的原因是:当前 verifier 在验收阶段重新执行
go build/go test,导致验收被当成了"带验证的执行循环"。行业共识(Devin、Copilot Workspace)把验证作为交付门槛,不是在验收阶段重新跑测试,而是检查执行阶段是否已经跑过且通过。分离后,编译失败应在执行阶段解决,验收阶段只确认"是否交付了正确的东西"。6.5 删除 Decider 和 TaskKind
删除 Decider 包全部内容(
decide.go、infer.go、types.go中的决策类型)。RequiredNextActions是硬编码规则,只有todo_write/filesystem_read_file/filesystem_glob三种,覆盖度极低。UserVisibleSummary是固定模板("任务完成。"),信息量低。Accept Gate 的失败报告原生包含user_visible_summary字段,直接替代。删除
InferTaskKind和DeriveEffectiveTaskKind。Verify的kind字段声明了任务类型,不需要再推断。这样设计的原因是:Decider 占据了架构的核心位置,但其建议生成是硬编码规则,无法覆盖真实任务的多样性(如需要
bash跑测试、调用子代理等)。TaskKind 推断不稳定,却被当作验收依据。两者都是"系统自己猜标准"的产物,而 plan 的Verify已经让模型自己声明了标准,推断层变得多余。6.6 Progress 硬终止,删除 FinalIntercept
删除:
finalInterceptStreak、pendingFinalProgressmustUseToolAfterFinalContinue、noToolAfterFinalContinueStreaklastAcceptanceBlockedReason、shouldPromotePendingFinalProgressProgress 评估规则:
这样设计的原因是:当前
shouldPromotePendingFinalProgress允许模型每轮读取不同文件就 resetfinalInterceptStreak,导致死循环。Exploration 不是实质进展——读 10 个不同文件不代表任务在推进,只代表模型在找线索。只有 Business Progress(写入、验证通过、todo 收敛)才证明任务有交付推进,才应该 reset streak。6.7 删除 SoftBlock / HardBlock,统一为 Pass/Fail
新架构只有两种 verifier 结果:
Pass/Fail。HardBlock场景(文件缺失、零交付、内容不匹配)→FailSoftBlock场景(todo pending)→ 在验收前被拦截(Layer 0/1),不进入 Accept GateSoftBlock场景(build/test 失败)→ 不应在验收阶段出现,执行阶段就应修复;若到验收阶段仍有失败证据 →Fail这样设计的原因是:
SoftBlock被滥用于几乎所有"内容不正确"的场景(编译失败、测试失败、文件缺失全部返回 SoftBlock),本质上是把验收降级为"辅助提示系统"。行业共识(Devin CI fail = 未交付、Copilot Workspace test fail = PR 不 merge)是验证失败即终态。唯一合理的"可继续"场景是 todo pending,但这应在验收前被 Layer 0/1 拦截,不需要 SoftBlock 语义。6.8 Run 边界与 Todo 生命周期
默认清空策略:回滚 PR 569 的"默认保留"语义,改为默认清空,只有明确续做意图才保留。
终止时统一清理:所有终止路径(Accepted / Failed / Incomplete / user_interrupt / budget_exceeded / max_turn_exceeded / fatal_error)统一清空
session.Todos。删除 stale_todo_reset 提示:如果 todo 边界正确,stale todo 本不应大规模遗留。把"判断哪些 todo 属于上一个任务"推给模型是不可靠的。
这样设计的原因是:当前 Run 终止时从不清理 todo,加上 PR 569 把语义反转为"默认保留",导致旧 todo 大量遗留。
stale_todo_reset提示是症状治疗而非根因修复——让模型为系统的边界污染买单。7. 与现有模块的关系
runtime
run.go:接入task_completion为验收必要条件;删除 FinalIntercept 字段;Progress 评估后硬终止;删除 Decider 调用state.go:删除finalInterceptStreak、pendingFinalProgress、mustUseToolAfterFinalContinue、noToolAfterFinalContinueStreak、lastAcceptanceBlockedReasonacceptance_service.go:删除多层合并逻辑,替换为单一 Accept Gateturn_control.go:删除shouldPromotePendingFinalProgress;删除 PR 569 的HasUnverifiedWrites捷径todo_run_boundary.go:回滚 PR 569 语义反转planning.go:Verify从[]string改为[]AcceptChecktask_kind.go:删除InferTaskKind和DeriveEffectiveTaskKind,清理文件或删除controlplane
progress.go:恢复硬终止能力;删除提醒-纯提醒逻辑;Exploration Progress 增加窗口上限completion.go:task_completion信号纳入CompletionStateverify
orchestrator.go:全量执行所有 verifier,删除短路command_success.go:SoftBlock→Failtodo_convergence.go:permission_wait/user_input_wait/external_resource_wait从 HardBlock 移除,转为 Execute 阶段前置状态检查git_diff.go:删除,零交付检测由WorkspaceWriteVerifier(基于HasSuccessfulWorkspaceWrite和 fileSnapshot diff)替代decider
decide.go、infer.go、types.go中的决策类型DecisionAccepted/DecisionFailed/DecisionContinue/DecisionIncomplete/DecisionBlockedgateway
contracts.go中引用TaskKind或DecisionStatus的类型需要删除或更新PlanArtifact.Summary.Verify类型变更影响 JSON 编解码和 RPC 传输,gateway 侧的反序列化需要兼容新旧两种格式AcceptanceContinue状态消失,新增的 Accept Gate 失败报告(含逐条检查结果)需要更新 gateway 的事件 payload 结构tui
Verify改为结构化后,可逐条渲染检查项(图标 + kind + target),而非之前的自然语言文本session
PlanArtifact.Summary.Verify类型变更影响 session 持久化的序列化/反序列化Verify为[]string)需要向后兼容:反序列化时两种格式都接受,优先[]AcceptCheck,零值时回退读取旧[]stringconfig
VerificationConfig.MaxNoProgress(与RuntimeConfig.MaxNoProgressStreak命名冲突)verifierGitDiff常量及默认配置(Verifiers["git_diff"])prompt assets
stale_todo_reset提示模板8. 测试场景
[{"kind": "output_only"}],模型声明完成且有输出 → Accepted。[{"kind": "command", "target": "go test ./..."}],执行期无测试验证事实 → Failed。[{"kind": "file_exists", "target": "login.go"}],文件不存在 → Failed。task_completion→ 不触发验收,直接 continue。task_completion但仍有 tool calls → continue(工具优先)。filesystem_read_file(无 Business Progress)→NoProgressStreak达到 5,Run 终止为 Incomplete。filesystem_read_file读取相同文件 →RepeatCycleStreak达到 3,Run 终止。command_success检查失败 → Failed。NoProgressStreak达到 2 且存在 open required todo → 不注入<stale_todo_reset>。9. 假设与默认决策
PlanArtifact。Verify改为[]AcceptCheck后,旧 plan(Verify是[]string)需要向后兼容:读取时同时接受两种格式,[]AcceptCheck优先。MaxNoProgressStreak默认值 5 和MaxRepeatCycleStreak默认值 3 在恢复硬终止后仍然适用。finalInterceptStreak不会导致已有测试大面积失效——已有测试TestAcceptanceContinueWithoutToolCallStopsAsIncomplete将改为基于NoProgressStreak终止。VerificationFail后能正确理解任务终止(需在 prompt assets 中明确)。ExplorationWindow默认值设为 3(即连续 3 轮 Exploration Progress 后,第 4 轮若仍无 Business Progress,开始累计 NoProgressStreak)。WorkspaceWriteVerifier依赖toolExecutionSummary中的HasSuccessfulWorkspaceWrite和fileSnapshotdiff 数据,这些数据在toolexec.go中已可靠采集。10. 一句话结论
NeoCode 验收机制的问题不是"gate 有 bug",而是"验收标准来源错误"——系统靠关键词推断任务类型,而不是读模型在 plan 阶段自己声明的验收条件。新架构通过结构化 Plan.Verify(
[]AcceptCheck)让验收 Gate 直接映射检查项——模型说"output_only"就检查输出、说"command: go test"就检查测试证据,零推断,零中间层。同时通过 Progress 硬终止 替代可被操纵的 FinalIntercept,删除 Decider 决策权、TaskKind 推断、SoftBlock 消除架构冗余。