Files
iomgaa 4f8812fa82 docs(design): 落成边界、续跑、公共 API 形状与停止语义四份决策
第 ② 阶段需求对齐与第 ③ 阶段架构的产出,代码尚未开始。

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>
2026-08-09 10:48:33 -04:00

155 lines
9.5 KiB
Markdown

# PolyLoop 管什么、不管什么
> **更新触发点:第 2 档(同提交同改)。** 三种事情发生时,本文在同一个提交里改:新消费者
> 接入并带来了新的裁决;某项因为凑齐了两个真实消费者而从界外移进界内;某项被实现时发现
> 判据用错了。判据本身的变更走人类门,并新写一份 design doc 标 supersedes。
>
> **当前状态:本文描述的是已经定下来的边界,`src/` 还不存在。** 界内的东西大部分还没有代码。
> 这段说明在库本体落地后删掉。
一个新项目来问「我要的这个东西,PolyLoop 管不管」,答案在这里。
这个问题不好答,因为「agent 框架」这个词罩着两种不同的东西。一种是**执行内核**:持有
「模型做决策 → 执行动作 → 写回观察 → 再决策」这个循环,管预算、停止判定、逐步轨迹、取消。
另一种是**阶段编排**:把一个大任务切成几个固定阶段,每个阶段跑一次执行内核,阶段之间交换
状态,并检查每个阶段的产物合不合格。
**PolyLoop 是前者。** 阶段编排留在项目侧。这个划分和它的理由记在
`../design/0001-scope-boundary.md`
## 判据
一句话是:**管「一个目标之内」的事,不管「多个目标之间」的事。** 一次 `run` 等于一个目标。
这句话自己判不了案,配三条判据,**三条全中才在界内**:
1. **时机** —— 它发生在一次运行开始之后、返回之前。
2. **信息** —— 它不需要知道这次运行之外的任何事:上一次运行的结果、下一次运行是什么、
此刻还有几次运行在跑、这个目标是谁派下来的。
3. **性质** —— 它回答「怎么跑」,不回答「跑什么」「为什么跑」「跑得好不好」。
再配一条补充规则:**只认接缝,不认接缝后面是什么。** 检索、沙箱、容器、工作区目录,库一概
不知道——它只知道有一个环境接口,交给它一个动作、拿回一段观察。没有这条,第一条判据会被
滥用成「检索发生在一次运行之内,所以检索该进库」。
补充规则有个更好用的等价说法:**能表达成「环境提供的一个动作」的,就在界外。** 检索能,
沙箱执行能,写文件能,调外部 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/` 落地。