4f8812fa82
第 ② 阶段需求对齐与第 ③ 阶段架构的产出,代码尚未开始。 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>
148 lines
10 KiB
Markdown
148 lines
10 KiB
Markdown
# Design 0001 · PolyLoop 的边界
|
|
|
|
**日期** 2026-08-07 · **状态** 已接受
|
|
|
|
## 背景
|
|
|
|
### 需要的三个前提
|
|
|
|
**PolyLoop 是一个库,它的治理单位是一次 Agent Session。** 所谓一次 Agent Session,指围绕
|
|
一个目标的有界多轮循环:模型做一次决策,执行一个动作,把观察写回历史,再决策,直到完成
|
|
或者撞上某个停止条件。一次模型调用本身不归它管,那是 PolyGateway 的治理单位。
|
|
|
|
**「执行内核」与「阶段编排」是两种不同的东西,日常都被叫作「agent 框架」。**
|
|
|
|
执行内核持有上面那个循环,管预算、停止判定、逐步轨迹、取消。阶段编排是另一层:它把一个
|
|
大任务切成几个固定阶段,每个阶段跑一次执行内核,阶段之间交换状态,并检查每个阶段的产物
|
|
合不合格。
|
|
|
|
四个已知消费者里,两种都有真实实现。dissect 的 `harness/agent/loop.py` 是执行内核。
|
|
GovDoc-SaaS 的 `packages/docagent-core/` 两种都有——`agent/loop.py` 是执行内核,
|
|
`workflow/` 是阶段编排(它的 `phase.py` 第一行注释写着「PES 三阶段泛化为 N 阶段」)。
|
|
GovDoc-Editor 今天在生产上跑的是阶段编排,而它把执行内核外包给了 Claude Agent SDK。
|
|
|
|
**四个消费者的成熟度差得很远。** dissect 的循环是活的,有测试,跑在真实实验里。
|
|
GovDoc-Editor 是活的生产系统,但形态是阶段编排。GovDoc-SaaS 正在重构,它的执行内核代码
|
|
移植自一个已废弃的项目,且业务层一次都没调用过。CHSAnalyzer 的评估层和诊断层还是空的,
|
|
它自己的架构文档明确写了不为它们预留结构。除这四个之外,实验室还有若干将来可能接入的
|
|
项目,现在连需求形状都不知道。
|
|
|
|
### 问题
|
|
|
|
边界画错有两个方向,代价不对称。
|
|
|
|
**画大了**:为一个还不存在的需求写代码。这类代码删不掉——它会长出配套的字段、分支和测试,
|
|
而且一旦下游装上了,公共类型的字段就受「只增不删不改名」的约束,真要删得发新 major。
|
|
|
|
**画小了**:每个下游各写一遍同样的东西。这正是抽库要消灭的事,而且写三遍就有三份 bug。
|
|
|
|
真正麻烦的不是这两个方向本身,是**没有判据**。当前只有 dissect 一个硬消费者,如果不给判据,
|
|
库会不知不觉长成 dissect 的形状;而每来一个新项目,「这个该不该进库」就要重新吵一遍,
|
|
每次吵的结论还未必一致。
|
|
|
|
## 决策
|
|
|
|
### 一、只做执行内核,阶段编排留在项目侧
|
|
|
|
三条理由。
|
|
|
|
执行内核有两份独立的一手证据——dissect 和 docagent-core 各写了一遍。阶段编排只有 GovDoc
|
|
一家,dissect 连「阶段」这个概念都没有。
|
|
|
|
依赖方向是单向的。docagent-core 自己的 import-linter 契约就写着子包互不依赖,唯一豁免是
|
|
workflow 依赖 agent。所以先做执行内核不会走进死胡同:阶段编排将来无论留在项目里还是抽成
|
|
另一个库,都是在执行内核之上加东西,不需要回头改它。
|
|
|
|
反过来做会把 dissect 挡在门外。
|
|
|
|
### 二、判据:一个目标之内的事在界内,多个目标之间的事在界外
|
|
|
|
这句话本身判不了案,配三条判据,**三条全中才在界内**:
|
|
|
|
1. **时机** —— 它发生在一次运行开始之后、返回之前。
|
|
2. **信息** —— 它不需要知道这次运行之外的任何事:上一次运行的结果、下一次运行是什么、
|
|
此刻还有几次运行在跑、这个目标是谁派下来的。
|
|
3. **性质** —— 它回答「怎么跑」,不回答「跑什么」「为什么跑」「跑得好不好」。
|
|
|
|
再配一条补充规则,否则第一条会被滥用:**只认接缝,不认接缝后面是什么。** 检索、沙箱、
|
|
容器、工作区目录,库一概不知道——它只知道有一个环境接口,交给它一个动作、拿回一段观察。
|
|
没有这条,「检索发生在一次运行之内,所以检索该进库」这种推理就成立了。
|
|
|
|
补充规则有个好用的等价说法:**能表达成「环境提供的一个动作」的,就在界外。** 检索能,
|
|
沙箱执行能,写文件能,调外部 API 能。而「记这一步花了多少预算」不能——没有任何环境会
|
|
提供这么个动作,那是库自己的算法。
|
|
|
|
当前的完整裁决清单在 `../explanation/scope.md`,那份是常青文档,会随新消费者接入而更新。
|
|
这里只记几条有代表性的推理,因为它们是判据怎么用的示范。
|
|
|
|
**评分和业务正确性判定在界外**,栽在「性质」上:它们回答「跑得好不好」。这条对 dissect 是
|
|
硬要求——让循环拿得到分数等于给被试开真值后门,实验当场作废。
|
|
|
|
**阶段产物校验在界外**,同样栽在「性质」上:`required_outputs` 判的是「这个阶段的目标达成
|
|
没有」,和评分同类。但要注意「校验」这个词底下压着三件不同的事,另外两件在界内:模型输出
|
|
能不能解析成一个动作(那是解析接缝),以及动作参数合不合法(那是环境裁决,库定语义)。
|
|
|
|
**工具的注册与分发机制在界内,而且必须由库提供。** 三条判据全过,但更硬的理由是:模型看见
|
|
的工具 schema、「这个工具存不存在」的校验、以及最后真正分发的那张表,**三者必须同源**。
|
|
不同源就会漂移——模型看见一个已经删掉的工具,或者校验放行了一个分发时找不到的名字。库不
|
|
提供的话,每个项目自己写一个注册表,就等于每个项目自己漂移一次。
|
|
|
|
**具体某个工具的实现要分两类。** 通用工具(读写文件、grep、跑 shell)不含业务概念,可以
|
|
作为可选装配放进库。业务工具(查法条、读超声图像、向 benchmark 提交答案)留在项目。检索
|
|
属于后者:三个下游里只有 GovDoc 需要,而它已经有自己的检索引擎;把检索放进来还会拖进
|
|
embedding、向量库、文本切分一整套依赖,而这些东西的形态差异极大,抽出来那个八成谁都不合用。
|
|
|
|
**「接着跑」在界内,「知道要接着跑、以及从哪儿读」在界外。** 崩溃后恢复这件事被当成一个词
|
|
用,实际要拆开:拿着一段已有历史从中间继续,三条判据全过;而判断该不该续、历史存在哪、
|
|
怎么读回来,要知道这次运行之外的事。展开见 `0002-step-level-resume.md`。
|
|
|
|
### 三、准入规矩:界外的东西要进来,先有两个真实消费者
|
|
|
|
「真实消费者」指有跑着的代码或已冻结的设计,不包括「将来可能需要」。在满足这个条件之前,
|
|
界外的需求只登记不实现,也**不为它预留结构**。
|
|
|
|
不预留是刻意的。预留一个接缝的成本不是零:它会出现在公共类型里,受兼容性约束,还要有人
|
|
维护它的测试;而它守护的那个形状是猜出来的,等真需求到了多半不合用,那时候拆比重写贵。
|
|
CHSAnalyzer 在自己的架构文档里对同一件事的判断是「猜出来的接缝比没有接缝更难拆」,这里
|
|
采用同一个判断。
|
|
|
|
## 否决的方案
|
|
|
|
**执行内核和阶段编排都做。** GovDoc-SaaS 已经写了一版 `PhasedWorkflow`,看起来不用白不用。
|
|
否决理由是阶段编排只有一个消费者验证过它的语义,而 dissect 用不上那半个库——一个下游只用
|
|
一半的库,另一半就没人替它挑错。另外阶段编排的形状分歧很大:GovDoc-Editor 是固定三阶段
|
|
靠文件交换状态,docagent-core 泛化成了 N 阶段带断点续跑,两者已经不是一个东西了。
|
|
|
|
**列一份「界内功能清单」,不给判据。** 这是最省事的做法,也是最先失效的。清单没法回答
|
|
它没列到的东西,而实验室将来要接入的项目现在连需求形状都不知道;每来一个新项目,清单就要
|
|
补一次,而补的人手上没有判据,只能凭当时的感觉。判据的价值正在于它对没见过的情况也能出结论。
|
|
|
|
**照 `reference/agent-core.md` 那份提案画边界。** 那是别人为本项目写的架构提案,不是本项目
|
|
的决策。它对执行内核和阶段编排的切法与这里基本一致,这一点是它的贡献。但它的证据基础是
|
|
两个消费者(其中一个的形态与今天的实际情况已经对不上),而且它把崩溃恢复整个推到了库外,
|
|
与本文的裁决不同。按 `../../CLAUDE.md` §0,那份文档不是任何事实的权威,其中任何一条在被
|
|
本项目的 design doc 明确采纳之前都不作数。
|
|
|
|
**为不确定的未来消费者预留接缝。** 见上面「准入规矩」那一节的理由。
|
|
|
|
## 代价
|
|
|
|
**当前只有一个硬消费者,库有长成 dissect 形状的风险。** 这是最实在的代价,没法消除。对策是
|
|
GovDoc 那边不做迁移但做设计级验收:把 GovDoc-Editor 今天在跑的阶段编排、以及 docagent-core
|
|
的 `Phase` 与 `PhasedWorkflow` 当成一份需求清单,逐条问「PolyLoop 能不能承载它」,答不上来的
|
|
就是边界缺口,登记进 `../migrations/`。
|
|
|
|
**「性质」那条判据有主观空间。** 「怎么跑」和「跑得好不好」在多数情况下分得清,但边缘情况会
|
|
有争议。缓解办法是按 `../../CLAUDE.md` §3 那条:结论有分歧时,以能否指出具体失败场景为准。
|
|
|
|
**「两个真实消费者」这条规矩会拖慢真需求。** 一个只有 dissect 需要、但确实属于执行内核的
|
|
机制,按规矩要等第二个消费者。接受这个代价,因为反方向的错误更贵:一个提前进库的错抽象会
|
|
同时污染所有下游,而且受兼容性约束删不掉。
|
|
|
|
## 结论落到哪里
|
|
|
|
本文的裁决清单落在 `../explanation/scope.md`(常青层,会随新消费者更新),判据本身也在那里。
|
|
`../../CLAUDE.md` §0 的权威表指向它。代码还不存在,所以现阶段没有机器兜底——将来
|
|
`pyproject.toml` 的 import-linter 契约能守住其中一部分(不反向 import 下游、业务概念不进内核),
|
|
但「这个机制该不该进库」这类判断守不住,只能靠评审。
|