Files
Agent-System-Documents/docs/proposals/2026-10-01-agent-harness.md
weichenandCursor 233dfb54ea Propose the agent stack audit and completion design
Why: The three libraries cannot be reused or recovered independently, and the tested dependency combination is not the one the product builds.
Evidence: Read-only audit at native c6d867c, squad 590fd86, intent 1dbbe81, recorded with file references in docs/records/2026-10-02-agent-stack-audit.md.
Impact: Documents only. No library code or gitlinks change in this commit. Later stages stay separate tasks.
Refs: Dev feature/agent-stack-blueprint
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-02 16:27:37 +00:00

12 KiB
Raw Permalink Blame History

Agent harness:回合循环、结果把控与多代理编排

状态:proposed(2026-10-01);owner:weichen;主任务:Dev 仓 feature/agent-harness-p0。 规范依据:Development-Guidelines(workflows/delivery.md、standards/collaboration.md、standards/agent-execution.md);本文是项目级消费,不复制其正文。 撰写日期:2026-10-01。对标来源:zai-org/ZCode(Apache-2.0,apps/zcode-cli/packages/core)、Claude Code 公开文档 与其插件仓 anthropics/claude-code。

本文只定义 harness 的职责、分期与验收。P0 随本文落地;P1–P4 各自另开任务,不在本文实施。

后续:三个库如何补齐缺陷、吸收外部做法并彼此解耦,见 2026-10-02-agent-stack-completion.md。本文的 P0–P4 不因该提案作废。


1. 问题与证据

2026-10-01,会话 cnv_96f639abf03a 第 26 轮(开发库 runtime/dev/data/agent-system.db)用户要求「重新总结纪要」。助手正文里出现了 [调用工具 run_python(…)]、[工具结果 ok …]、[产出工件 …(二进制内容,正文见工件查看器)],并给出工件 ID art_1a2b3c4d5e6f。该轮落库部件只有 thinking 与 text,没有任何 tool_call;artifacts 表里没有这个 ID,所以界面显示「未找到该文件」。同会话第 16、20 轮的 art_7b24c0b7e2f1、art_4f9d2c1a8b3e 同样不存在。第 22、24 轮才是真实的工具调用与工件。

直接原因在上下文组装:modules/backend/internal/ctxasm/ctxasm.go 把历史里的工具调用压成文本 [调用工具 …] / [工具结果 …] 写进助手消息。模型把这种文本当成「我以前就是这样调用工具的」格式,在正文里模仿。虚假交付守卫(claimsFalseDeliverable)只纠正一次,第二段「文件已正常生成并登记」既没有 art_ 编号,也匹配不上「已生成….docx」正则,被当成最终回复放行。

同一机制下还有这些结构性缺口(2026-10-01 只读梳理):

  • 三套循环各自为政。直连走 LLMRunner;小队成员走 agent-native 的 runtime.Run(已有 GetSteering / GetFollowUp,直连没用);delegate_task 背后的 SubLLMRunner 是单次调用、没有工具。
  • 是否完成由干活的模型自报。quality_review 与版式检查只靠提示词约定;agent.sub.verify 要求输出的 VERDICT 没有解析方;模型评分 Judge 只有类型、没有接线。
  • 没有逐轮提醒。计划和待办只给界面看,不回灌给模型。技能只按当前这条用户文本匹配一次。
  • 子 run 的助手消息落在同一会话,列表不按 run 过滤,下一轮会被当成主对话回放。
  • 插件工作流只有数据、没有执行器。WorkflowDef.IntentID 没有消费方。意图识别没有路由到工作流。
  • 预算耗尽时直接返回错误,已生成的部件不落库。工具结果按字节截断,可能切在 UTF-8 字符中间。

2. 对标结论

两边的共同点:工具调用按提供方的原生消息回放,而不是压成正文;完成与否由循环或另一个评估者判定,不由干活的模型宣布;子代理上下文隔离,只把摘要交回父会话;每条续跑路径都有次数上限。

机制 ZCode Claude Code 本基座采纳
历史回放 tool_calls + role=tool;没有结果的调用补占位,并区分「未执行」与「副作用未知」 同形(公开文档描述为工具结果回传) 采纳。占位文案按原因区分
完成判定 同模型、不带工具,按证据清单输出 {passed, reason, nextAction};拿不准判不通过。验证器自身出错时放行 /goal:每轮结束后由另一个模型判断,不成立就开下一轮 采纳证据清单。交付物场景下验证器出错改为判不通过,并设次数上限
结束门禁 Stop hook 返回继续并注入上下文,最多 3 次 Stop / SubagentStop 返回 decision: block + reason;stop_hook_active 防止死循环 采纳 block + reason,上限 3
提醒 按来源节流,包在 <system-reminder> 里,位置不破坏工具消息配对 规范、待办、后台完成以 system-reminder 插入,不改 system 段 采纳
异常 同名同参数连续 3 次给警告,不直接终止;输出被截断最多续写 3 次,续写提示不入库 公开文档未给出同等细节 采纳警告与续写
子代理 Markdown frontmatter 定义;调用方不能指定模型;嵌套深度从工具白名单上限制为 1;后台完成用通知回传,多条合并 同形。子代理只回传最终结果,中间过程不进父上下文 采纳
评审 expert 的 critic 用 JSON 判定,可按节点重开,每层有上限 code-review 插件:并行找问题,每条再独立验证,未通过的丢弃 采纳「并行评审 → 逐条验证 → 过滤 → 汇总」
工作流 阶段产物落盘,下游只传路径;确定性检查优先于模型自报;人工升级以单个节点为粒度 feature-dev:阶段之间有人工门禁 采纳产物落盘与确定性检查优先。P4 同时做声明式工作流、模型生成的步骤和沙箱脚本

3. 目标

harness 是基座能力,不是某个下游的领域规则。它回答四件事:这一轮看到什么、循环何时继续或停止、怎样判定结果为真、多个代理怎样拉起、分配、评审和汇总。

内核放在 agent-native 的 runtime,直连、小队和子代理共用同一套循环。core.Part 到模型消息的投影留在 backend:内核不认识 core 类型。

agent-native runtime
  TurnLoop          预算、输出续写、重复调用检测
  Transcript        原生回放,没有结果的调用补占位
  ReminderBus       按来源节流的提醒,不改 system 段
  HookBus           PreTool / PostTool / Stop / SubagentStop
  CompletionGate    确定性检查 + 不带工具的验证器
  SubagentRuntime   profile、深度 1、只回传摘要、完成通知

backend 适配
  LLMRunner         薄适配:事件、部件、落库
  ctxasm            core.Message 投影为模型消息
  交付物门禁         工件存在、版式检查、quality_review
  工作流执行器       引擎驱动步骤,IntentID 路由

4. 分期

P0 回放保真与守卫加固(本文随附实施)

  • 助手历史按 assistant{ToolCalls} 与 tool{ToolCallID, Content, IsError} 回放。没有结果的调用补「(无结果:运行中断)」;孤立的结果跳过;空 ID 用 hist_<消息ID>_<序号> 补上。
  • 参数超过 4000 字符时截断其中的长字符串,结果仍是合法 JSON。
  • 跳过被守卫降级的过程文本(Process=true 且后面紧跟的不是工具调用)。从助手历史文本里剔除存量的 [调用工具 、[工具结果 、[产出工件 。
  • 守卫改成 deliverableGate(body, produced, rc) (block bool, reason string),形状与 P1 的 Stop 门禁一致,P1 时平移进内核。
  • 新增判定:正文里出现伪造调用标记;本轮没有工件时声称文件已生成、已登记或已保存。纠正上限 2 次。回灌用占位文本,不把伪造正文再塞给模型。两次之后仍谎称,终轮正文降为过程,并追加一条非过程提示,说明本轮没有实际生成文件。
  • 提示词 base.deliverables 写明:工具只能通过函数调用触发,不要在正文里书写调用标记。版本升到 5。

验收:回放后的模型输入里不再出现 [调用工具 文本;用第 26 轮那种「只有伪造标记、没有真实工具调用」的输入,守卫会拦住,不会再登记一个不存在的工件 ID。

P1 内核原语

给 agent-native 的 runtime.Config 加上 Stop 门禁、工具前后钩子、提醒通道、重复调用检测、输出被截断时的续写,以及预算耗尽时保留已有部件再收尾。LLMRunner 改为 runtime.Run 的薄适配。P0 的 deliverableGate 平移成一个 Stop 门禁。

验收:backend 与 agent-native 的既有测试保持通过;直连与小队成员走同一个循环。

风险:agent-native 在 Dev 仓 deps/、backend 的 deps/、agent-squad 的 deps/ 各有一处 gitlink。P1 起要按依赖顺序推进指针,不能只改最外层。

P2 结果把控

  • 独立验证器:不带工具的一次模型调用,按证据清单输出 JSON {passed, reason, nextAction}。拿不准判不通过。验证器出错或返回无法解析的内容时也判不通过(与 ZCode 的放行不同),整个门禁最多 3 次。
  • 确定性检查排在模型自报之前:本轮登记的工件确实存在、housestyle.lint 没有 error、quality_review 达到通过线。
  • 计划和待办按节奏用提醒回灌。例如连续多轮没有更新待办才提醒一次,避免每轮都占上下文。

验收:评测夹具里「声称已交付但没有工件」的用例全部被拦下,并留下可核对的原因。

P3 多代理

  • 子代理用 Markdown 加 frontmatter 声明(name、description、tools、model、maxTurns),复用现有 agent-sub-*.md 并补上 frontmatter。调用方不能指定模型。
  • delegate_task 改为带工具的子循环。工具白名单里去掉派发类工具,嵌套深度为 1。父会话只收到摘要和工件路径。
  • 支持后台运行。完成后用通知回传,多条合并成一次注入。
  • 子 run 的消息与父会话历史隔离,不再被下一轮当成主对话回放。
  • squad_orchestrate 与直连子代理用同一套运行时。评审走「并行找问题 → 每条独立验证 → 丢掉未验证的 → 汇总」。

P4 意图到工作流

执行器驱动 prompt / tool / hitl / artifact / card / script。三种来源共用同一步骤循环:插件声明的工作流、模型生成并经过同一套校验的步骤、以及在会话沙箱里跑的 Python 脚本。

  • 路由先走命令总线,再看意图绑定的工作流,最后才是普通回合。小队入口不提供 run_workflow。
  • 工作流启动时拷贝定义。插件卸载不影响已经在跑的那一份。
  • 声明式工作流最多 20 步。模型生成的最多 8 步,不能含 script,执行前必须经用户确认。critic 每步最多重试 3 次、缺省 1 次,整条工作流最多重试 6 次。无法解析的 critic 结果按不通过处理。
  • 脚本最多回调 50 次,单行请求不超过 1MB,超时缺省 600 秒、最长 1800 秒,墙钟时间包含等待人工和子代理。开关 runtime.workflowScripts 缺省关闭。模型写的脚本运行前必须确认;插件自带的脚本不需要确认。
  • 脚本以当前用户身份运行,工作目录是会话沙箱,没有网络或文件系统隔离。回调一律经过工具执行入口,不能调用 run_python、run_shell、run_workflow 和派发类工具。

5. 不在本文范围

  • 单份纪要的内容质量(例如「3 项决议」与表格行数对不上)。那是生成质量,不是回放与门禁。
  • 把直连路径并进小队入口。小队仍是一种入口,循环归内核。
  • 下游领域的阶段门禁、文档树和追溯关系。那些留在下游仓。

6. 已知待查项

以下来自同一次只读梳理,不在 P0 修改。各自另开任务前先复核当前代码。

  • quality_review 的工作区路径疑似用了全局根,而文件工具用的是 <根>/<owner>/<会话>。单测把根直接设成文件所在目录,可能测不出来。
  • 工具确认卡算出了参数预览,但没有放进卡片,用户看不到参数就要批准。
  • 小队入口读的是默认 owner 的设置,不是当前租户的。
  • 插件宿主 SettingsSchema 持读锁时再调用会再次取读锁,中间若有写锁等待,有死锁风险。

7. 验收(P0)

  • modules/documents:本文与路线图第 3 阶段的链接。
  • modules/backend:go test ./internal/ctxasm/ ./internal/runtime/ ./internal/prompts/ 通过;随后在 Dev 仓 make test 做组合验证。
  • 回放用例覆盖:真实工具调用变成原生消息、缺结果时补齐、超长参数仍是合法 JSON、降级文本被跳过、存量伪造标记被剔除、工件正文回填保持不变。
  • 守卫用例覆盖:只有伪造标记也会拦截、纠正两次后附系统提示、回灌的是占位文本、引用已存在的工件不算谎称。