From a3f0b80759fa01094cb9ee26b2a17dba831317e2 Mon Sep 17 00:00:00 2001 From: weichen Date: Thu, 1 Oct 2026 12:25:31 +0000 Subject: [PATCH] Propose the agent harness and link it from the roadmap Why: Tool-call history is flattened into assistant text, so the model imitates calls and invents artifact ids. The fix belongs to a shared harness, not another prompt tweak. Evidence: Proposal 2026-10-01-agent-harness.md records the 2026-10-01 session evidence, the ZCode and Claude Code patterns, and the P0-P4 acceptance. Impact: Documents only. P0 code lands in the backend on the same branch name. P1-P4 are not implemented here. Refs: feature/agent-harness-p0 Co-authored-by: Cursor --- docs/proposals/2026-10-01-agent-harness.md | 124 ++++++++++++++++++ .../2026-10-01-base-extension-roadmap.md | 2 +- 2 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 docs/proposals/2026-10-01-agent-harness.md diff --git a/docs/proposals/2026-10-01-agent-harness.md b/docs/proposals/2026-10-01-agent-harness.md new file mode 100644 index 0000000..ddbda96 --- /dev/null +++ b/docs/proposals/2026-10-01-agent-harness.md @@ -0,0 +1,124 @@ +# 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](https://github.com/zai-org/ZCode)(Apache-2.0,`apps/zcode-cli/packages/core`)、[Claude Code 公开文档](https://code.claude.com/docs/en/overview) 与其插件仓 [anthropics/claude-code](https://github.com/anthropics/claude-code)。 + +本文只定义 harness 的职责、分期与验收。P0 随本文落地;P1–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 段 | 采纳 | +| 异常 | 同名同参数连续 3 次给警告,不直接终止;输出被截断最多续写 3 次,续写提示不入库 | 公开文档未给出同等细节 | 采纳警告与续写 | +| 子代理 | Markdown frontmatter 定义;调用方不能指定模型;嵌套深度从工具白名单上限制为 1;后台完成用通知回传,多条合并 | 同形。子代理只回传最终结果,中间过程不进父上下文 | 采纳 | +| 评审 | expert 的 critic 用 JSON 判定,可按节点重开,每层有上限 | `code-review` 插件:并行找问题,每条再独立验证,未通过的丢弃 | 采纳「并行评审 → 逐条验证 → 过滤 → 汇总」 | +| 工作流 | 阶段产物落盘,下游只传路径;确定性检查优先于模型自报;人工升级以单个节点为粒度 | feature-dev:阶段之间有人工门禁 | 采纳产物落盘与确定性检查优先。脚本化动态工作流留到 P4 再决定 | + +## 3. 目标 + +harness 是基座能力,不是某个下游的领域规则。它回答四件事:这一轮看到什么、循环何时继续或停止、怎样判定结果为真、多个代理怎样拉起、分配、评审和汇总。 + +内核放在 `agent-native` 的 `runtime`,直连、小队和子代理共用同一套循环。`core.Part` 到模型消息的投影留在 backend:内核不认识 `core` 类型。 + +```text +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 意图到工作流 + +给 `WorkflowDef` 补执行器,由引擎驱动 `prompt` / `tool` / `hitl` / `artifact` / `card` 步骤,而不是只在插件清单里列出。用 `IntentID` 把命中的意图接到工作流。critic 可以按节点打回重做,每一层都有次数上限。是否引入脚本化的动态工作流,留到 P4 评审时决定。 + +## 5. 不在本文范围 + +- 单份纪要的内容质量(例如「3 项决议」与表格行数对不上)。那是生成质量,不是回放与门禁。 +- 把直连路径并进小队入口。小队仍是一种入口,循环归内核。 +- 下游领域的阶段门禁、文档树和追溯关系。那些留在下游仓。 + +## 6. 已知待查项 + +以下来自同一次只读梳理,不在 P0 修改。各自另开任务前先复核当前代码。 + +- `quality_review` 的工作区路径疑似用了全局根,而文件工具用的是 `<根>//<会话>`。单测把根直接设成文件所在目录,可能测不出来。 +- 工具确认卡算出了参数预览,但没有放进卡片,用户看不到参数就要批准。 +- 小队入口读的是默认 owner 的设置,不是当前租户的。 +- 插件宿主 `SettingsSchema` 持读锁时再调用会再次取读锁,中间若有写锁等待,有死锁风险。 + +## 7. 验收(P0) + +- `modules/documents`:本文与路线图第 3 阶段的链接。 +- `modules/backend`:`go test ./internal/ctxasm/ ./internal/runtime/ ./internal/prompts/` 通过;随后在 Dev 仓 `make test` 做组合验证。 +- 回放用例覆盖:真实工具调用变成原生消息、缺结果时补齐、超长参数仍是合法 JSON、降级文本被跳过、存量伪造标记被剔除、工件正文回填保持不变。 +- 守卫用例覆盖:只有伪造标记也会拦截、纠正两次后附系统提示、回灌的是占位文本、引用已存在的工件不算谎称。 diff --git a/docs/proposals/2026-10-01-base-extension-roadmap.md b/docs/proposals/2026-10-01-base-extension-roadmap.md index 54323a9..1d852b0 100644 --- a/docs/proposals/2026-10-01-base-extension-roadmap.md +++ b/docs/proposals/2026-10-01-base-extension-roadmap.md @@ -55,7 +55,7 @@ Agent-System 是通用智能体基座:提供入口、能力、接口和基础 1. **公开扩展面与兼容规则**(本提案第 4 节 + 同日兼容提案)。下游今天必须能依赖基座,而不是 fork。 2. **插件契约 v2**(同日插件提案):声明、进程内编译、sidecar、前端 ESM。 -3. **智能体质量回路**:后台任务、记忆、上下文摘要、本地嵌入档、HITL 超时、确认态持久化、LLM 预算、通用评测夹具。 +3. **智能体质量回路**:后台任务、记忆、上下文摘要、本地嵌入档、HITL 超时、确认态持久化、LLM 预算、通用评测夹具。回合循环、完成判定与多代理编排见 [2026-10-01-agent-harness.md](2026-10-01-agent-harness.md)(P0 起实施,内核下沉到 agent-native)。 4. **工程基座**:日志与错误码、通用审计、边界 lint、命令对拍、会话导出。 目标系统薄切片放在 `examples/downstream-rdm`。它只 import 公开包。独立远程仓库由 owner 另行创建;本次不 `git init` 新仓。