第 ② 阶段需求对齐与第 ③ 阶段架构的产出,代码尚未开始。 design/0001 定边界判据:三道测试(时机 / 信息 / 性质)全过才在界内, 外加「只认接缝、不认接缝后面是什么」与不夺走下游实验因子的排除条款。 design/0002 定步级续跑:不承诺原子性,承诺绝不静默丢失与不替工具猜幂等性; 先写意图再执行、结果 ID 预分配、重放策略由工具声明且默认绝不重放。 design/0003 定公共 API 形状:单一入口两个动词、五个接缝、三个伪接缝的排除理由、 分层与九条依赖规则。design/0004 定停止判定顺序、十个停止原因取值与步记录字段表。 0003 与 0004 需过 CLAUDE.md §2 人类门,已由项目负责人确认,状态转为已接受。 explanation/scope.md 与 explanation/architecture.md 是这四份决策的常青回写, 分层与模块边界的权威在 architecture.md,将来由 import-linter 契约机器断言。 migrations/ 下 dissect 是唯一的硬迁移验收,govdoc-saas 只做设计级对齐。 三道闸都过了:14 agent 对抗辩论定骨架,两轮硕士生阅读报的 30 余条已修完, Codex 对抗审查抓出的两条致命问题(提交型完成被误判成环境故障、 崩溃恢复漏一个状态)已修,修完的形状还没送 Codex 复审。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
9.5 KiB
PolyLoop 管什么、不管什么
更新触发点:第 2 档(同提交同改)。 三种事情发生时,本文在同一个提交里改:新消费者 接入并带来了新的裁决;某项因为凑齐了两个真实消费者而从界外移进界内;某项被实现时发现 判据用错了。判据本身的变更走人类门,并新写一份 design doc 标 supersedes。
当前状态:本文描述的是已经定下来的边界,
src/还不存在。 界内的东西大部分还没有代码。 这段说明在库本体落地后删掉。
一个新项目来问「我要的这个东西,PolyLoop 管不管」,答案在这里。
这个问题不好答,因为「agent 框架」这个词罩着两种不同的东西。一种是执行内核:持有 「模型做决策 → 执行动作 → 写回观察 → 再决策」这个循环,管预算、停止判定、逐步轨迹、取消。 另一种是阶段编排:把一个大任务切成几个固定阶段,每个阶段跑一次执行内核,阶段之间交换 状态,并检查每个阶段的产物合不合格。
PolyLoop 是前者。 阶段编排留在项目侧。这个划分和它的理由记在
../design/0001-scope-boundary.md。
判据
一句话是:管「一个目标之内」的事,不管「多个目标之间」的事。 一次 run 等于一个目标。
这句话自己判不了案,配三条判据,三条全中才在界内:
- 时机 —— 它发生在一次运行开始之后、返回之前。
- 信息 —— 它不需要知道这次运行之外的任何事:上一次运行的结果、下一次运行是什么、 此刻还有几次运行在跑、这个目标是谁派下来的。
- 性质 —— 它回答「怎么跑」,不回答「跑什么」「为什么跑」「跑得好不好」。
再配一条补充规则:只认接缝,不认接缝后面是什么。 检索、沙箱、容器、工作区目录,库一概 不知道——它只知道有一个环境接口,交给它一个动作、拿回一段观察。没有这条,第一条判据会被 滥用成「检索发生在一次运行之内,所以检索该进库」。
补充规则有个更好用的等价说法:能表达成「环境提供的一个动作」的,就在界外。 检索能, 沙箱执行能,写文件能,调外部 API 能。而「记这一步花了多少预算」不能——没有任何环境会提供 这么个动作,那是库自己的算法。
排除条款:不夺走下游要调的那个旋钮
三条判据都过、补充规则也不适用,仍然可能不该进库——如果它是下游要扫的实验因子或要调的 业务刺激。
Skill 的渲染形态是这一条唯一的现役例子。把注入内容用什么格式贴进提示词,按三条判据全过: 它发生在一次运行之内、不需要知道运行之外的事、回答的是「怎么跑」。但 dissect 要扫这个因子 来测它的效应量。库一旦把格式写死,那段文本就成了库定的实验刺激,而且它逃出了项目的 参数快照——扫不动了。
判据是:这个取值变一下,下游的实验结论或业务结果会不会跟着变? 会,就留在项目侧, 哪怕它长得再像库该管的事。
当前裁决
「已落地」一栏区分两件不同的事:某项归本库管(在界内),和本库现在真的做了它。凡是标 「未落地」的,第一版不实现、不设接缝、不预留字段——归属不变,只是还没到做的时候。
界内
| 事项 | 已落地 | 说明 |
|---|---|---|
| 主循环:决策、执行、观察、再决策 | 待落地 | 结构已定,代码未写 |
| 预算与停止判定 | 待落地 | 判定顺序与预算计数语义还没定,见第十四节缺口 |
| 停止原因的分类与取值 | 待落地 | 取值还没定 |
| 逐步轨迹 | 待落地 | 字段清单还没定 |
| 取消传播 | 待落地 | |
| 决策解释接缝 | 待落地 | 把模型回复解释成动作 / 最终回答 / 无效决策 |
| 动作执行接缝 | 待落地 | 含完成信号查询 |
| 工具的注册、模型可见 schema 生成、存在性与参数校验、分发 | 待落地 | 四者必须同源 |
| 通用工具的现成实现 | 待落地 | 可选装配;读写文件、grep、跑 shell 这类 |
| Skill 的注入与注入顺序快照 | 待落地 | 只注入和记录 |
| 崩溃后「接着跑」 | 待落地 | 见 ../design/0002-step-level-resume.md |
| 意图日志与恢复状态判定 | 待落地 | 同上 |
| 运行结果的持久化存储端口 | 待落地 | 同上 |
| 最终输出对项目所给 schema 的校验,以及校验失败后的有界重问 | 不做 | 凑不齐两个真实消费者 |
| 上下文压缩 | 不做 | 同上。做的时候必须是显式策略,基线关闭 |
后两项标「不做」而不是「待落地」,区别在于前面那些只是排期问题,这两项是准入问题—— 它们要等第二个真实消费者出现,见下一节。
界外
| 事项 | 栽在哪一条 |
|---|---|
| 评分、业务正确性判定 | 性质 |
| 阶段编排 | 信息、性质 |
| 阶段产物是否齐全、合不合格 | 性质 |
| 崩溃后「判断该不该续、历史存哪、怎么读回来」 | 时机、信息 |
| 阶段级的跳过与续跑 | 时机、信息 |
| 并行调度、批量运行、结果汇总 | 时机、信息 |
| 跨运行的长期记忆 | 信息 |
| Skill 的生成、评测、进化、审批、激活 | 时机、信息、性质 |
| Skill 的选择规则(这次注入哪几条) | 信息 |
| Skill 的渲染形态 | 排除条款 |
| 上下文的渲染格式 | 排除条款 |
| 检索、文本切分、向量库、重排 | 接缝后面 |
| 工作区目录的建立、快照与清理 | 接缝后面 |
| 沙箱与容器的生命周期 | 接缝后面 |
| 业务工具的实现 | 接缝后面 |
| 任务队列、租约、心跳、多租户规则 | 时机、信息 |
| 模型调用的重试、换源、限流、熔断、缓存、遥测 | 归 PolyGateway |
| 一次模型调用失败之后要不要再来一次 | 见下 |
最后一条要说清楚,因为它容易被读成「库要做一层重试」。 重试分两个尺度,两个都不归 本库:一次模型调用之内的重试与换源归 PolyGateway;整次运行失败之后要不要重跑, 归下游的 runner。中间那一层——「这一步的模型调用失败了,库自己再调一次」——举不出两个 消费者,所以不做。预算口径一并定死:PolyGateway 内部换源重试不消耗任何预算,因为库数的 是一次模型调用接缝的调用,口径必须是库自己能观测的量。
三个容易判错的地方
「校验」这个词底下压着三件不同的事,两件在界内。 模型吐的文本能不能解析成一个动作—— 那是决策解释接缝,界内。动作的参数合不合法、这个工具存不存在——那是动作执行接缝裁决、 库定语义,界内。一次运行结束后文件系统上的产物齐不齐、合不合格——那是「跑得好不好」,界外。
接缝的完整清单、各自的职责、以及为什么恰好是这几个,在 architecture.md 第九节。本文只
判归属,不定形状。
工具调用在界内,某个具体工具的实现不一定。 模型说要调 search,库解析它、校验它、分发
它、把结果变成观察,这一整套在界内。search 本身怎么实现在界外,由项目注册进来。库自带
的通用工具是个例外,判据是不含业务概念且有两个以上消费者需要——读写文件过得了,检索过不了。
恢复要拆成两件事。 拿着一段已有历史从中间接着跑,界内。判断该不该续、历史存在哪、怎么 读回来,界外。
界外的东西怎么进来
先有两个真实消费者,并且它们的语义稳定一致。「真实消费者」指有跑着的代码或已冻结的 设计,不包括「将来可能需要」。
在满足这个条件之前,界外的需求只登记不实现,也不为它预留结构。不预留是刻意的:预留一个 接缝要占公共类型的位置、受兼容性约束、还要有人维护它的测试,而它守护的那个形状是猜出来的, 等真需求到了多半不合用,那时候拆比重写贵。
登记的地方是 ../migrations/ 下对应那份。
边界之外,库仍然要为它们留出余地
不做某件事,不等于可以挡住别人做。三条已知的、由界外功能反向施加给界内设计的约束:
阶段编排要给每次运行配不同的预算。 一份 agent 定义在三个阶段里可能要用三个不同的步数 上限,所以预算不能只挂在定义上。
阶段编排要按阶段收窄工具集。 每个阶段可见的工具不同,所以工具集要能按次运行装配,而 不是固定在定义里。这条和「四者同源」不冲突:每次运行构造一个窄的注册表就行。
阶段级续跑要能判断上一次运行是不是真的跑完了。 所以运行结果必须是可持久化、可读回、
可判定的结构,而且「结束了」这个事实要由库自己写下来,不能跨两个存储。这条已经落进
../design/0002-step-level-resume.md。
已知没有机器兜底的部分
pyproject.toml 的 import-linter 契约将来能守住其中一部分——不反向 import 下游、业务概念
不进内核。但「这个机制该不该进库」这类判断守不住,只能靠 ../../CLAUDE.md §3 的评审。
那些契约现在还没写,要等 src/ 落地。