0010 取代 0006 决策三里 tool_section_template 那一行。三条理由:两个真实消费者都是项目侧 自己把工具清单拼进上下文的(一个塞在 run 级模板里伪装成示例演示的一次执行输出,另一个的 render_tool_docs 全仓零生产调用方);留着它的话迁移的人会去填,填完提示词里有两份工具清单, 而那个下游的验收标准是轨迹逐字段可比、变了还不报错;参考框架 pi 的内核同样不渲染,它的应用层 虽有 Available tools 段,但每行来自与 description 分开的 promptSnippet 字段——说明就算要渲染, 那段文字也不该是从校验用的 schema 生成的。四者同源不受影响,schema_for_model() 还在。 段序照唯一跑通了的下游:run 级片段 → 注入槽 → 目标级片段 → 逐步的模型输出/观察交替, 按变化频率从低到高排(供应商按前缀缓存计费)。库不合成任何 system 消息——有一个下游全程 没有 system 消息,加一条它的提示词会凭空多出一段。 注入槽每条一条 USER 消息、正文原样、不加任何分隔符(分隔符是渲染格式,归项目侧),通道按 名字排序(映射的迭代顺序取决于调用方怎么构造,用集合建出来的每进程都不同)。空注入等同有 测试守着。观察模板必须含占位符,缺了就报错——不校验的话每一步的观察会整个消失,而模型收到 的是一段看起来完全正常的固定文本。规模度量逐块问,认不得的块类型直接失败而不是当成 0。 零业务假设扫描第三次抓到我自己(模块 docstring 里写了「实验因子」),已改成中性说法。 migrations/govdoc-saas.md 登记了「模型原生工具调用没有路」这个缺口。
11 KiB
设计对齐:GovDoc-SaaS
更新触发点:第 2 档(同提交同改)。 公共类型或接缝语义变更时,在同一个提交里核对 本文的需求条目还能不能被承载;GovDoc 侧的 agent 方案定下来时,在同一个提交里更新需求 来源与缺口清单。
当前状态:这不是一份迁移清单,是一份需求清单。 GovDoc-SaaS 的 agent 部分还没有可迁移 的东西,本文的作用是防止 PolyLoop 只按 dissect 一家的形状长。
PolyLoop 只有 dissect 一个硬消费者(见 dissect.md)。只对着一个消费者做,做出来的库会长成
那个消费者的形状,而这件事在完成之前看不出来。GovDoc 这边不做迁移验收,改做设计级验收:
把它真实需要的东西列成条目,逐条问 PolyLoop 能不能承载,答不上来的登记成缺口。
GovDoc 的项目术语
这些词是 GovDoc 的,不是 PolyLoop 的。PolyLoop 自己的词表在
../explanation/architecture.md 第二节。
PES——Plan-Execute-Summarize,「计划—执行—总结」三阶段。GovDoc 的一个任务被切成这三段, 每段跑一次 agent,段与段之间靠工作区里的文件交换状态:计划阶段产出一份计划文件,执行阶段 读它并产出一批发现,总结阶段读那批发现产出最终结果。
工作区——一次 agent 运行独占的沙箱目录。里面有这次运行能看的数据、能写的输出、以及 运行结束后要留下的产物。它由 GovDoc 侧建立、快照和清理,PolyLoop 不认识它。
必须产出的文件——每个阶段声明几个文件路径,跑完之后这些文件必须存在,否则这个阶段算
失败。这是 GovDoc 判断「阶段目标达成没有」的方式,按 ../explanation/scope.md 它在界外。
提交型完成——agent 靠调用一个特定的工具来宣布自己做完了,环境状态不发生任何变化。它跟 另一种完成方式(环境自己报告「这道题结束了」)是两回事,两者都要能表达。
为什么这里没有迁移清单
GovDoc 有两套 agent,形态完全不同,而两套都不能直接当迁移对象。
GovDoc-Editor 是今天跑在生产上的系统。 它的 agent 循环归 Claude Agent SDK,业务层明令 禁止 import 那个 SDK,中间隔着一层叫 Scrivai 的编排框架。Scrivai 做的是「计划—执行—总结」 三阶段编排:每个阶段跑一次 agent,阶段之间通过工作区里的文件交换状态,每个阶段有自己的 步数上限、可用工具集和必须产出的文件清单。
按 ../explanation/scope.md 的判据,阶段编排在 PolyLoop 界外。所以 GovDoc-Editor 不是
迁移对象,它是需求来源——它告诉我们「阶段编排要建在执行内核之上」时,对执行内核提了哪些
要求。
GovDoc-SaaS 是 GovDoc-Editor 的重构版,它的 packages/docagent-core/ 正在把上面那套东西
换成自建:workflow/phase.py 的注释写着「三阶段泛化为 N 阶段」,PhasedWorkflow 自己接
AgentLoop,Claude Agent SDK 不见了。
但那份 AgentLoop 不能当迁移基准。文件头第一句写着它「完整保留」某个项目的循环逻辑,而
那个项目已经废弃;更要紧的是,grep "from docagent_core" src/ 在业务层零匹配——它有测试,
但从来没被任何业务代码调用过。拿一份没有调用方、且血统来自废弃项目的代码当验收标准,
验的是那个废弃项目的行为。
所以需求来源是两处:GovDoc-Editor 的生产实践,以及 docagent-core 作为一次已有的抽象尝试
所暴露的形状。
需求条目
一、一份 agent 定义要能按次运行配不同的预算。 生产上那份审核 agent 的三个阶段分别是 50、50、16 步。如果预算只能挂在定义上,就得为三个 阶段建三份定义,而它们除了预算之外完全一样。
二、要能按次运行收窄工具集。 三个阶段可见的工具不同:计划阶段有读、写、搜索、执行;执行阶段多一个技能调用;总结阶段 只有读、写、通配查找。这条和「模型可见的 schema、存在性校验、分发三者同源」不冲突—— 每次运行构造一个窄的注册表就行。
三、工具调用协议是 JSON,而且要容错。 模型输出的是一段 JSON,含反思、计划、动作三部分,动作里有工具名和参数。实际遇到的两种 偏差都得处理:整段 JSON 被包在代码围栏里,以及参数被平铺在动作层而没有嵌套。后者的收拢 要保守——只有当除工具名之外确实存在平铺参数时才收拢,否则会把「缺参数」这个错误静默升级 成「参数为空但合法」。
四、无效的工具调用不计有效步,但要计入总迭代上限。 不计有效步是因为它没真的做事;计入总迭代是因为模型可能一直调用不存在的工具,没有这个上界 循环不会停。这两个计数必须分开,混成一个就防不住无限循环。
五、提交型完成:某个特定工具被调用即视为完成。 生产上那份 agent 靠调用一个提交工具来结束,环境状态不发生变化。这和另一种完成方式——环境 自己报告「这道题结束了」——是两回事,两者都要能表达。
六、审计事件必须覆盖模型步与工具调用,且事件发送失败不能中断循环。 业务侧有一条硬纪律:agent 的原始输出、修复后的输出、恢复来源全程留痕,禁止静默修复。所以 「模型原文」和「解析/修复之后的结果」要分别可见。
七、取消要能穿透。 长任务的所有权以数据库租约为准,取消信号从外部进来之后必须能打断 正在进行的模型调用与工具执行,并释放在途资源。
八、工作区是一个有边界的端口。
生产实现把它抽象成六个操作:按行读、原子写、grep、列文件、判断存在、算校验和;所有路径都
是工作区内的相对路径,越界要抛异常。按 ../explanation/scope.md,工作区的建立、快照与清理
在界外,但提示词的构造要能读工作区(阶段的提示词是从上一阶段的产物生成的),这一点见
下面的缺口。
九、阶段级续跑要能判断上一次运行是不是真的跑完了。
阶段级续跑本身在界外,但它依赖一个界内的事实:运行结果必须是可持久化、可读回、可判定的
结构,而且「这次运行结束了」要由库自己写下来。这条已经落进 ../design/0002-step-level-resume.md。
已经有答案的(原缺口登记,0003 回答)
这三条曾经登记为「PolyLoop 答不上来」,../design/0003-public-api-shape.md 已经定了。
留在这里是因为它们是 GovDoc 侧真实的接入约束,迁移时要照着改代码。
预算挂在请求上,不是定义上。 一份定义可以被三个阶段共用,各传一份预算,不会长出三份 除预算外完全相同的定义。不设「定义给默认值、请求可覆盖」——两处取值意味着「这次到底 跑的什么设置」要对照两个地方才答得出来。
提示词的内容由项目读好了传进来,库不接触工作区。 上下文装配不是接缝,所以「让库接受 一个项目对象再转交」这条路不存在。每个阶段的提示词构造器仍然在 GovDoc 侧,它照常读工作区, 只是把读出来的结果作为上下文的一段交给库。库只管段的顺序和注入槽的位置。
两层重试都不归本库,预算口径按库能观测的量算。 一次模型调用之内的重试与换源归 PolyGateway,整次运行的重试归 GovDoc 自己的编排层;中间那一层(库在模型调用失败后自己再 调一次)举不出两个消费者,不做。PolyGateway 内部换源重试不消耗任何预算,因为库数的是 一次模型调用接缝的调用——口径必须是库自己能观测的量,否则换个后端口径就变了。
提交型完成靠工具注册表上的完成标记。 注册表知道哪个工具一旦被成功执行就代表目标达成, 库在动作结算之后查它。GovDoc 的动作执行接缝完成信号恒为「未完成」——它的环境状态不因为 提交而改变,所以环境这条通路对它永远不成立,收尾走的是完成标记这条。
这一条曾经差点出大事:0003/0004 的初稿把完成信号定成「布尔或空、空表示取不到」,并且
规定「取不到即环境故障」。照那个写法,GovDoc 的每一次运行都会在第一步撞环境故障终止。
Codex 对抗审查抓出来了,修法见 ../design/0004-stopping-and-step-record.md 决策三 G 档。
这一条对 GovDoc 是实质变化:docagent-core 现有的那层步级重试(超时与网络异常,退避
20 秒和 40 秒)迁移后没有对应位置。它当初存在的理由是「治理层的某些异常类型如果没在装配点
显式注入,重试对它们就静默失效」——那是 PolyGateway 装配的问题,要在装配点解决,不是在
Agent 层补一层。
缺口登记
这些是 PolyLoop 现在答不上来的问题。答不上来不等于设计错了,但每一条都得有明确结论—— 哪怕结论是「不支持,理由是什么」。
模型原生的工具调用没有路。 PolyLoop 的模型调用入参上只有消息序列,没有 tools 字段,
所以工具要让模型看见,唯一的路是变成项目自己拼进上下文的文字
(../design/0010-context-assembly.md 决策一与文末)。生产实现今天的动作协议是写在文本里的
JSON,不用原生工具调用,所以现在不缺。哪天要换成原生工具调用,PolyLoop 的模型调用入参要加一
个 tools 字段——那是兼容变更,但还要看下面那一层的共用库暴不暴露这个参数,以及原生工具调用的
返回怎么进决策解释接缝那三个分支。这条要在换协议之前定,不能边换边定。
审计出口与事件流的关系。 生产实现的审计出口是一个「发一条带类型和载荷的事件」的接口, 而 PolyLoop 的方向是「观察走事件流、干预走具名回调」。事件流能不能覆盖审计的需求,取决于 事件里带不带原始响应——审计纪律要求原始输出和修复后的输出都留痕。事件集与回调清单要独立 成一份 design doc,这条在那时候定。
动作被拒绝之后的重复行为。 生产实现对无效工具调用不设单独的重试上限,靠总迭代上界收敛。 PolyLoop 目前的想法一致,但要确认这在长阶段(50 步)上够不够——模型反复调用同一个不存在的 工具,会把整个步数预算烧光而不产出任何东西,而这在轨迹上表现成「预算耗尽」,与真的做不完 混在一起。
CHSAnalyzer 为什么没有对应的文档
CHSAnalyzer 是第三个已知消费者,定位是远期兼容。它的架构文档第十四节明确写了评估层和诊断层 现在是空的,而且「不为它们预留任何结构」,理由是「留了位置整个系统会立刻复杂一个量级, 而现在还不知道它们真正需要什么形状——猜出来的接缝比没有接缝更难拆」。
没有需求可以登记,所以不建那份文档。 等它的方案定下来再建。这一段写在这里,是为了让 读者知道这是一个决定而不是遗漏。