Skip to content

Runtime: 收敛 Final Acceptance 闭环并移除伪 Hook / Fallback / 猜测式 Verifier 路径 #465

@phantom5099

Description

@phantom5099

摘要

本 issue的目的是重构 runtime final acceptance 主链,收敛为唯一闭环:

session-owned verification profile -> completion gate -> verifier gate -> acceptance decision -> terminal decision

目标是让 runtime 对“什么时候能收尾、什么时候必须继续、什么时候已经失败”给出稳定、唯一、可测试的决定。

背景

当前仓库已经有 completion gateverifier gateacceptance decision 这些零件,但它们还没有形成一条唯一、稳定、可解释的收尾链路。

目前 runtime 在 final acceptance 上仍然保留了旧路径、猜测逻辑、metadata 旁路和双真相源状态,导致相似任务在相似状态下并不能稳定得到相似的收尾结果。

本次目标是把 final acceptance 收敛为一条主链,而不是继续叠加补丁式修复。

当前真实存在的问题

1. required todo 失败后仍可能被视作完成项

当前 todo_convergence 仍把 required todo 的 failed/canceled 当做任务完成。
这会把“任务做完了”和“任务做失败了/被取消了”混为一谈,导致必做项已明确失败时,acceptance 主链仍可能继续向 accepted 推进。
具体会发生在这些场景:

  • 某个 required todo 执行失败,被 todo_write fail 标记为 failed
  • 某个 required todo 没有真正完成,只是被 todo_write set_status canceled 标记为 canceled

具体后果是:

  • 必做项已经失败,acceptance 仍可能继续向 accepted 推进
  • 必做项只是被取消、但没有明确 replacement,acceptance 仍可能误判为“已收敛”
  • 用户最终看到的结果会是:关键任务并未完成,但 agent 仍可能正常收尾并输出“任务完成”

2. acceptance 主链仍允许通过配置绕过 verifier gate

当前 acceptance engine 仍保留:

  • verification disabled -> compatibility fallback accepted
  • final_intercept=false -> 本轮 final acceptance 不跑 verifier

这意味着 verifier gate 现在还不是 final acceptance 的必经环节,而仍然可以被配置整体跳过。
从主链语义上讲,这会破坏“检查通过后才能完成任务”的基本约束,也使 acceptance 仍然保留旧兼容逃生门。

3. verifier policy 仍依赖 task 文本猜测

当前 verifier 选择仍依赖:

inferTaskType
resolveTaskType
runtime.verification.default_task_policy

现在系统不是自己保存一份正式清单,而是看任务描述里有没有像“fix bug”、“docs”、“config”、“refactor”这样的词,再临时猜这次该检查什么,而不是建立在 runtime 自己持有的结构化任务状态之上。
这会导致同类任务仅因措辞不同,就可能走出不同的验收路径。

4. file/content 验收仍走 metadata 旁路

当前:

  • file_exists 仍读取 metadata.expected_files
  • content_match 仍读取 metadata.content_match
  • session todo / task state 还不是唯一结构化验收输入来源,artifact/content 验收仍存在 metadata 旁路。

可以把这两者理解成:

  • session todo / task state:正式病历、正式工单、正式项目台账
  • metadata:便利贴、口头备注、临时小纸条。临时附带,不稳定

这会导致验收规则容易漂移、丢失,或者与 session 中真实保存的任务状态不一致。

5. intercepted final 仍会先落盘

当前 assistant final 仍在 acceptance 前先写入 session.Messages。
如果 final 随后被拦截并 continue,下一轮模型仍会看到自己刚刚说过的“任务完成”,从而更容易进入重复收尾输出。

6. final interception 仍存在双真相源

  • runState.finalInterceptStreak:判断agent的结束标志第几次没有通过检查
  • progress.LastScore.NoProgressStreak:判断整个 agent 在执行过程中有没有继续推进任务,防止无效空转

当前 continue 分支仍会做一轮空 evidence progress 评估,在什么业务都没有的情况下还做一轮计分操作,并把两个不一样的计分系统混在一起使用。
这意味着 final interception 的计数并不是一个自洽系统,而是会被另一套 progress 状态反向覆盖。

7. git_diff 默认仍漏掉 staged-only 交付

当前 git_diff 默认仍使用能看到工作区相对暂存区变化的命令:git diff --name-only
但 staged-only 场景是:

  • 改动已经 git add
  • 工作区已经干净
  • 真实交付已经存在于 index

在这种情况下:

  • git diff --name-only 可能返回空
  • 但并不代表“没有改动”
  • 只是代表“没有未暂存改动”

8. orchestrator / failure policy / stop reason 语义仍不稳定(第四点指的是输入规则,这里说的是结果解释方式)

当前 verifier orchestrator 存在规则分散在多个层里,且部分规则依赖文案而不是依赖稳定枚举的问题:

  • verifier 不按“首个非 pass 即短路”的语义执行:应该是检查是否结束时,如果遇到首个没通过的 verifier,就直接停止后续检查,并根据它是 soft_block / hard_block / fail 来决定是 continue、incomplete 还是 failed,而不是现在先跑完再把结果聚合起来,决定是否通过检查。
  • HardBlock 不在执行层立即短路:当前不能收尾,而是要等外部条件,例如权限、用户输入、外部资源。和上一点结合来看,也就是说本来其实应该在执行时就发挥作用,而不是白白等到所有都跑完了,再来看是否允许通过检查
  • FailClosed 当前只覆盖很窄的 soft_block + env_missing 情况,没有覆盖 required verifier 未配置、命令不存在、环境缺失、执行被拒绝等“验收根本没法成立”的情况
    -FailOpen 当前会在聚合前把任意 VerificationFail 降成 pass,没有区分执行层失败和领域失败;默认配置下未必触发,但代码语义允许错误放行。
  • stopReason 仍混用文案与 ErrorClass
  • NormalizeResult 仍会污染 pass 结果

9. verifier command 仍依赖 shell string

当前 verifier execution 仍以 shell string 为基础,仍走:

Windows powershell -Command
Unix sh -lc

这与 acceptance 主链收敛和执行语义稳定化的目标冲突,也让命令执行、错误分类和策略约束难以真正收敛。

10. runtime.verification.max_retries 和 acceptance hook stage 仍是旧产物

当前主链中仍真实存在:

  • runtime.verification.max_retries
  • runtime.verification.default_task_policy
  • acceptance hook stage
  • hook failure policy 配置面

这些旧配置 / 旧抽象仍在影响主链,不适合继续保留,否则 acceptance 很难真正收敛成唯一链路。

目标方案

本次将 runtime final acceptance 收敛为一条唯一主链:

session-owned verification profile
  -> completion gate
  -> verifier gate
  -> acceptance decision
  -> terminal decision

这条主链只负责一件事:
对“现在是否可以结束任务”给出唯一、稳定、可测试的决定。

收敛后的边界固定为:

  • session.TaskState 和 session.TodoItem 是唯一结构化验收输入来源
  • completion gate 只判断“是否允许尝试收尾”
  • verifier gate 只判断“这次收尾是否真的通过检查”
  • acceptance engine 只负责聚合 gate 结果并产出终态
  • verify/* 只消费结构化输入并返回稳定结果

收敛后的终态规则固定为:

  • completion gate 未通过:continue
  • verifier 首个非 pass 为 soft_block:continue
  • verifier 首个非 pass 为 hard_block:incomplete
  • verifier 首个非 pass 为 fail:failed
  • 全部 verifier pass:accepted

本次明确删除以下旧语义,不做兼容保留:

  • verification disabled -> compatibility fallback accepted
  • final_intercept=false -> 跳过 verifier gate
  • task_type metadata -> verifier policy mapping
  • metadata.expected_files
  • metadata.content_match
  • intercepted final 先写入会话、再由 acceptance 补救
  • acceptance hook 决策链
  • shell string verifier command
  • FailOpen / FailClosed 这类对 verifier 结果的事后改写策略

实现设计

1. 用 VerificationProfile 固定 verifier 集合

在 session.TaskState 中新增 VerificationProfile,由 runtime/session 持有,作为 verifier 选择的唯一依据,不再从 task_id / goal / next_step 文本猜测任务类型。

允许值固定为:

  • task_only
  • create_file
  • docs
  • config
  • edit_code
  • fix_bug
  • refactor

映射固定为:

  • task_only -> todo_convergence
  • create_file / docs -> todo_convergence, file_exists, content_match
  • config -> todo_convergence, file_exists, content_match, command_success
  • edit_code -> todo_convergence, git_diff, build, test, typecheck
  • fix_bug -> todo_convergence, git_diff, test, build, typecheck
  • refactor -> todo_convergence, git_diff, build, test, lint, typecheck

要求:

  • VerificationProfile 属于 session-owned 任务状态,不进入 TUI 输入面
  • session 的 clone / normalize / clamp / persistence / restore 全链支持该字段
  • compact 输入输出必须完整保留该字段
  • profile 缺失或非法直接返回结构化错误
  • 删除 inferTaskType、resolveTaskType、default_task_policy

2. 用 session 契约统一 todo / artifact / content 验收输入

新增:

type TodoContentCheck struct {
    Artifact string
    Contains []string
}
type TodoItem struct {
    ...
    Acceptance    []string
    Artifacts     []string
    Supersedes    []string
    ContentChecks []TodoContentCheck
    ...
}

契约固定为:

  • Acceptance 只给人读,不参与机器判定
  • Artifacts 是文件交付物声明
  • ContentChecks 是唯一内容规则来源
  • Supersedes 是 replacement todo 的替代关系声明

同时要求:

  • verifier 输入显式带上 Artifacts / ContentChecks / Supersedes
  • file_exists 不再读 metadata,只检查 required todo 的 Artifacts 与 TaskState.KeyArtifacts
  • content_match 不再读 metadata,只检查 ContentChecks
  • content_match 只支持“目标文件包含全部 token”,不支持 regex、不支持语义匹配、不把自然语言 acceptance 解释成规则
  • required todo 的 canceled 状态迁移在 session 层收紧,只有存在显式 replacement required todo 时才合法

3. 固定 verifier 结果模型,不再做策略层改写

verifier 结果只允许四种:

  • pass:该检查通过
  • soft_block:当前不能收尾,但 agent 还能继续补救
  • hard_block:当前不能收尾,且需要外部条件
  • fail:这次任务或这次验收已经明确失败

关键语义固定为:

todo_convergence

  • required completed -> pass

  • required failed -> fail

  • required canceled 且无显式 replacement -> fail

  • blocked 且原因为 permission_wait / user_input_wait / external_resource_wait -> hard_block

  • 其余未完成状态 -> soft_block

  • file_exists

    • 目标 artifact 缺失 -> soft_block
    • artifact 路径非法或越界 -> fail
  • content_match

    • 文件存在但缺少 token -> soft_block
    • 规则结构非法 -> fail
  • git_diff
    无交付证据 -> soft_block

  • build / test / lint / typecheck / command_success

  • 命令成功执行但结果未通过 -> soft_block

  • 命令不存在、未配置、环境缺失、执行被拒绝、执行超时、执行层内部错误 -> fail

  • 同时删除 FailClosed 和 FailOpen。

verifier 结果语义必须在 verifier 本身固定下来,不能再由策略层事后重写。

4. orchestrator 改为“首个非 pass 即短路”

orchestrator 改为按顺序执行 verifier,并在执行层短路:

  • 遇到首个 soft_block,立即停止后续 verifier,返回 continue
  • 遇到首个 hard_block,立即停止后续 verifier,返回 incomplete
  • 遇到首个 fail,立即停止后续 verifier,返回 failed
  • 只有全部 pass,才返回 accepted

同时要求:

  • HardBlock 必须在执行层生效,而不是只做聚合标签
  • stopReason 只按稳定 ErrorClass 映射
  • pass 结果的 ErrorClass 必须为空
  • NormalizeResult 不能给 pass 补失败分类

5. intercepted final 改为 candidate final,interception 只保留一个真相源

provider 返回 final 后拆成两步:

  • 先持久化 usage / provider / model
  • assistant final 仅作为 candidate final 保存在内存

acceptance 决策后:

  • accepted / incomplete / failed 才把 candidate final 写入 session.Messages
  • continue 时不写 candidate final,只追加 reminder

同时收敛 final interception 状态:

  • runState.finalInterceptStreak 是唯一真相源

  • execute 阶段如产生新的 business/exploration progress,则置 pendingFinalProgress = true

  • AcceptanceContinue 时:

    • pendingFinalProgress == true -> streak 清零
    • 否则 streak 自增
  • 之后清空 pendingFinalProgress

  • 删除 continue 分支中的空 evidence progress 评估

  • 不再从 progress.LastScore.NoProgressStreak 反写 interception 计数

6. 收敛执行模型并清理旧配置

执行层统一收敛为:

  • git_diff 默认改为 git status --porcelain --untracked-files=normal
  • VerifierConfig.Command 改为 argv 数组
  • verifier execution 改为直 exec
  • 删除 powershell -Command / sh -lc

主链同步清理:

  • 删除 acceptance hook stage,改为普通校验函数
  • 删除 runtime.verification.max_retries
  • 删除 runtime.verification.default_task_policy
  • 删除 runtime.verification.enabled 和 runtime.verification.final_intercept 对 acceptance 主链的关闭语义
  • 删除 acceptance hooks 配置面
  • 删除 compatibility fallback 相关状态、事件和测试语义

7. Session 契约收敛

本次不仅调整 runtime / verifier 行为,也同时收敛 session 领域状态,使 verifier 所需信息不再通过 runtime metadata 旁路传递,而是正式进入 session.TaskState 与 session.TodoItem。

要求如下:

  • TaskState 新增 VerificationProfile,作为 verifier 选择的唯一结构化来源
  • TodoItem 新增 Supersedes 与 ContentChecks
  • Artifacts 不再只是展示字段,而是正式文件交付物声明
  • Acceptance 保留为人类可读说明,不参与机器判定
  • required todo 的取消语义在 session 层收紧,只有存在显式 replacement required todo,且其 Supersedes 包含原 todo ID 时,原 todo 才允许转为 canceled
  • compact / normalize / clamp / persistence / restore 必须同步支持上述字段,确保同一任务在存盘、恢复和 compact 后仍保持同一验收契约

这一层的目标是把“验什么、凭什么算替代、文件和内容如何验收”从 runtime 临时拼装逻辑,收敛为 session 自己持有的正式领域契约。

任务清单

  • 重写 todo_convergence,收紧 required todo 的 failed/canceled 语义
  • TaskState 新增 VerificationProfile,并让 compact / normalize / persistence / restore 全链支持
  • TodoItem 新增 SupersedesContentChecks,同时收紧 required todo cancel 语义
  • 删除 compatibility fallback、inferTaskType / resolveTaskType / default_task_policy,将 verifier 选择改为只基于 VerificationProfile
  • 扩展 verifier 输入,传入 Artifacts / ContentChecks / Supersedes
  • file_exists 改为消费 Artifacts / KeyArtifacts,将 content_match 改为消费 ContentChecks
  • 拆分 usage 持久化与 assistant final 持久化,intercepted final 改为 candidate final 内存态
  • 删除 continue 分支中的空 progress 评估,将 finalInterceptStreak 收敛为唯一真相源
  • git_diff 默认实现切到 git status --porcelain
  • 删除 FailClosed / FailOpen,收敛 stopReasonNormalizeResult 语义
  • 将 verifier command 从 shell string 改为 argv,并删除 acceptance 主链中的 hook stage
  • 删除 runtime.verification.max_retries 等旧配置,更新 acceptance / verifier / todo 契约文档

测试验证

  • required todo failedcanceled without replacement 时不会再 accepted
  • verifier policy 不再依赖 task 文本,且不存在 compatibility fallback accepted 分支
  • intercepted final 不进入 session.Messages;continue 后 usage / provider / model 仍正确持久化
  • Artifacts 端到端驱动 file_existsContentChecks 端到端驱动 content_match
  • 有新 progress 时清零 finalInterceptStreak,无新 progress 时连续拦截单调递增,且不再受 progress.LastScore.NoProgressStreak 回写影响
  • orchestrator 对首个非 pass 结果短路,pass 结果 ErrorClass 为空
  • argv 执行、超时、权限拒绝、命令不存在、环境缺失映射稳定
  • git_diff 覆盖 staged-only、unstaged-only、untracked-only、ignored-only
  • go test ./internal/runtime/... ./internal/session/... ./internal/config/...go test ./... 通过

非目标

  • 不扩张到 TUI 产品交互改造
  • 不新增新的工具能力
  • 不做更智能的 verifier 推断
  • 不修复独立的 Tool Facts / Completion Gate bug
  • 不保留 compatibility fallback、task 文本猜测、metadata 旁路、shell string verifier、acceptance hook 决策链的长期双轨兼容

验收标准

  • final acceptance 收敛为唯一主链路
  • verifier 集合只由 session 结构化状态决定
  • todo / artifact / content 验收通过 session 状态进入 verifier 主链
  • intercepted final 不再先落盘,final interception 计数只有一个真相源
  • verifier 聚合、stop reason 和执行语义稳定且可测试
  • shell string verifier、compatibility fallback、旧 task policy、hook stage、runtime.verification.max_retries 已清理
  • 文档、配置、测试与真实实现一致

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions