c4e5732587
套件从 tests/contract/ 搬进 polyloop.testing 之后,全仓库 28 处引用要重新指过。修了 12 处, 其余在 design/(只增不改)与 scratch/(由人清理)里。 **CLAUDE.md 改了四处事实**:§0 权威表里行为契约的权威、§0 那句依赖规则的条数、§5 目录树与 模块数、§1.8 那句「谁断言公共 Protocol 的签名」。§1 的其余硬约束与 §2 的人类门一条没动。 **architecture.md**:分层图第 4 层加一格,装配层从三个变四个;代码地图加一行;第十节按代码 逐项重写——那笔「工具段渲染样式」的欠账**没有被数字对上盖掉**,加了 fingerprints 之后请求 的字段数恰好还是十一,而组成已经换过,所以那一节正面写着它仍然欠着;新增第十条依赖规则 (pytest 只在 testing 那个 extra 里,别处 import 它会让下游的生产环境一 import 本库就 ModuleNotFoundError),带静态与运行时两半;删掉「src/ 下一行代码都没有」那段过期状态说明; 决策索引补齐 0008 到 0016,其中四行原描述说的不是那份文档真正定的东西。 **migrations/dissect.md** 那笔「内存实现不存在」的欠账还掉了。 **压测那边**三条测试守的是一条已经撤销的公共契约,改名并写清它们现在守的是场景自己的选择。 AppWorld 那处刻意的偏离(不补三个反引号)留着不恢复——那条路径要模型输出被 stop 序列截断才 触发,而压测不配 stop 序列,恢复的收益不抵重跑一次压测的成本。但注释的理由改对了:它现在是 一笔有出处的欠账,不是一个决定。 CHANGELOG 攒在「未发布」段,版本号不提前写(§1.10)。
126 lines
8.4 KiB
Markdown
126 lines
8.4 KiB
Markdown
# PolyLoop
|
||
|
||
实验室共用的 Agent 执行内核。治理单位是**一次运行**:围绕一个目标的有界多轮
|
||
「模型决策 → 动作 → 观察」循环,含预算、停止语义、取消、逐步轨迹与 Skill 注入。
|
||
|
||
它和 PolyGateway 是叠起来的两层。PolyGateway 治理**一次模型调用**(多源、限流、重试、熔断、
|
||
缓存、遥测),PolyLoop 治理**一次运行**,并用 PolyGateway 的顶层公共 API 拿模型。
|
||
任务编排、批量调度、评分、检索、Skill 的生成与进化都留在下游项目。
|
||
|
||
---
|
||
|
||
## 安装
|
||
|
||
发布在实验室自建的 Gitea PyPI registry 上,公网 PyPI 查它是 404,所以装它必须自己带索引地址:
|
||
|
||
```bash
|
||
pip install --extra-index-url https://gitea.iomgaa.online/api/packages/iomgaa/pypi/simple/ \
|
||
"polyloop==1.0.*"
|
||
```
|
||
|
||
模型调用要经 PolyGateway,那部分是一个单独的 extra——不用它的人不该被迫装上网关:
|
||
|
||
```bash
|
||
pip install --extra-index-url https://gitea.iomgaa.online/api/packages/iomgaa/pypi/simple/ \
|
||
"polyloop[gateway]==1.0.*"
|
||
```
|
||
|
||
契约套件随包发布,它要的 pytest 也是一个单独的 extra:
|
||
|
||
```bash
|
||
pip install --extra-index-url https://gitea.iomgaa.online/api/packages/iomgaa/pypi/simple/ \
|
||
"polyloop[testing]==1.0.*"
|
||
```
|
||
|
||
**只有自己实现了某个接缝、要拿库这边的契约套件验它的时候才装这个。** 存储、模型调用、决策
|
||
解释、动作执行、事件出口五处允许换实现,谁换了谁就得证明自己那份还满足接缝的行为契约,而
|
||
证明的方式就是继承 `polyloop.testing` 里对应的基类跑一遍。只调 `run` / `resume`、五个接缝
|
||
全用库自带或别人写好的实现的项目,不需要它。
|
||
|
||
写进 `requirements.txt` 的话,那一行 `--extra-index-url` 必须排在 `polyloop` 之前。
|
||
|
||
**这台开发机上的注意事项**:它设了 `http_proxy` 指向一个到不了外面的本地代理,走代理会失败,
|
||
装的时候加 `NO_PROXY=gitea.iomgaa.online`。
|
||
|
||
## 现状
|
||
|
||
十一个模块全部落地,四层测试都在跑。**还没有任何下游项目真的用过它**——这是它现在最大的未验证项,
|
||
下面的阶段清单是唯一的进度权威。
|
||
|
||
## 消费者与验收标准
|
||
|
||
| 项目 | 现状 | 本库对它的验收标准 |
|
||
|---|---|---|
|
||
| dissect | 已有跑着的 `harness/agent/`(loop / context / memory / parser) | **能把那套循环搬到本库上,dissect 原有测试全绿。** 一手需求证据最强 |
|
||
| GovDoc-SaaS | 仓库 2026-08-03 起整体重建,实现全部清空,自己的清单停在「构建文档框架」 | **不做迁移验收,做设计级验收**:它真实需要的东西逐条能不能被承载,见 `research-wiki/migrations/govdoc-saas.md`。它将来写 agent 层时是直接长在本库上,不是迁过来 |
|
||
| CHSAnalyzer | 还没写到 agent 那一步,只有设计方案 | **远期可以兼容使用。** 它的 agent 需求要么本库能满足,要么明确写进「不属于本库」清单并说明为什么 |
|
||
|
||
三者的证据强度不同,能进本库的语义也就分档:dissect 和 GovDoc-SaaS 的真实代码是一手证据,
|
||
两边都需要的机制才有资格做成稳定内核;CHSAnalyzer 只用来检验边界画得对不对,不能凭它的
|
||
设计方案单独长出一个组件——一个没有真实调用方的抽象,等到有调用方那天多半是错的。
|
||
|
||
## 阶段清单
|
||
|
||
**这份清单是「当前处在哪个阶段」的唯一权威。** `CLAUDE.md` 不重复这里的内容,只有一条常青规则
|
||
(§1.11)要求动手前先看这里——这样阶段推进时不需要改 `CLAUDE.md`,不会在别处留下过期条文。
|
||
|
||
- [x] ① 协作规范 —— 见 [CLAUDE.md](CLAUDE.md) 与 [research-wiki/README.md](research-wiki/README.md)(文档体系)
|
||
- [x] ② 需求对齐 —— 从 dissect 的 `harness/agent/` 与 GovDoc-SaaS 的 `packages/docagent-core/`
|
||
提取真实需求,产出 `research-wiki/migrations/` 下两份迁移文档(删除清单 + 组件映射 + 验收口径),
|
||
并检查 CHSAnalyzer 的 agent 方案落在边界内还是边界外。**这一阶段的产物决定库的边界**,
|
||
所以它排在架构前面:边界画错,后面每一份架构文档都要重写
|
||
- [x] ③ 架构 —— `research-wiki/explanation/architecture.md` 与 `pyproject.toml` 的 import-linter 契约。
|
||
架构文档先于代码存在,此期间它是一份规格而不是描述,文档开头须写明这一点
|
||
- [x] ④ 测试框架 —— 四层都在跑(划分判据是「依赖什么」,见 [CLAUDE.md](CLAUDE.md) §1.9)。
|
||
e2e 打真实模型网关、会产生真实费用,所以默认不跑:要 `POLYLOOP_E2E=1` 加显式
|
||
`pytest -m e2e`,配置见 [.env.example](.env.example)。那套公共行为一致性用例住在
|
||
`polyloop.testing` 里随包发出去,五个接缝在本仓库都接上了实现跑起来,接点是
|
||
`tests/contract/` 与 `tests/integration/` 下那几个继承契约基类的子类
|
||
- [x] ⑤ 实现 —— 十一个模块全部落地,五个接缝都有调用点。`stores` 有两种形态:逐行追加进
|
||
本地文件的那个,和只留在进程内存里、进程一退就没了的那个。关系数据库那种仍然由下游
|
||
自己实现,契约套件是它的准入标准,而套件随包发布在 `polyloop.testing` 里
|
||
- [x] ⑥ 验收 —— 两件事都做完了。**一是自己造负载压**:照三个消费者将来的用法造负载,用真实
|
||
数据真的打模型跑完,看这个内核在这个量级上扛不扛得住。**这一步之所以必须自己做,是因为
|
||
三个消费者一个都还没到能用它的时候**,而「从没被任何人用过」是它当时最大的未验证项,
|
||
等下游是等不来的。**二是把 dissect 的迁移方案交出去**:不在 dissect 仓库里写代码,
|
||
出一份方案提到它的 issue 上,由那边自己排期。GovDoc-SaaS 那半是设计级验收(理由见上面
|
||
那张表),口径在 `research-wiki/migrations/govdoc-saas.md`
|
||
|
||
压测这套东西住在 `tools/soak/`,它守什么、怎么重跑见
|
||
[research-wiki/explanation/soak-harness.md](research-wiki/explanation/soak-harness.md)。
|
||
2026-08-11 那一跑:193 次运行、1743 次真实模型调用,两个场景各自的十一条不变量全部通过;
|
||
九类故障注入 58 条判据零击穿。**库本身没有被压出 bug**,压出来的四个问题全在压测这一侧
|
||
(判据写错、场景缺完成通路、崩溃时机抢不到、环境客户端漏了「连不上」那一档),各自的
|
||
commit 里写了是怎么发现的。AppWorld 那一路的步数分布与 dissect 已有的 937 条真实轨迹
|
||
基本重合,这是「同一个 benchmark 换个内核驱动、轨迹形状没变」的证据
|
||
|
||
## 本地检查
|
||
|
||
```
|
||
make check # ruff format --check + ruff check + lint-imports
|
||
make test # pytest(e2e 默认不跑,它打真实网关要花钱)
|
||
make ci # 上面两条
|
||
```
|
||
|
||
还没有 CI workflow,`make ci` 就是当前的全部机器闸,和 PolyGateway 一样。发布怎么做见
|
||
[research-wiki/guides/releasing.md](research-wiki/guides/releasing.md)。
|
||
|
||
文档质量不走机器检查,走 [CLAUDE.md](CLAUDE.md) §3 的「硕士生阅读」评审。
|
||
|
||
## 参考资料
|
||
|
||
`reference/` 下的六个仓库与 `agent-core.md` **都只是参考,不是本项目的设计,也不是任何事实的权威**
|
||
(理由见 [CLAUDE.md](CLAUDE.md) §0)。它们只读、不改、不入库。
|
||
|
||
| 位置 | 是什么 |
|
||
|---|---|
|
||
| `reference/agent-core.md` | 别人为本项目写的一份架构提案。其中任何一条在被我们自己的 design doc 采纳前都不作数 |
|
||
| `reference/dissect/` | 消费者,已有 ReAct 循环实现 |
|
||
| `reference/GovDoc-SaaS/`(`background` 分支) | 消费者的**旧代码**,第一次抽库尝试 `packages/docagent-core/`。它那边的活仓库已经把这份降级成「只作研究输入,不是当前事实来源」 |
|
||
| `reference/GovDoc-Editor/` | GovDoc-SaaS 重构之前的那一版,今天跑在生产上。需求来源,不是迁移对象 |
|
||
| `reference/CHSAnalyzer/` | 远期消费者;同时是本仓库协作规范的蓝本 |
|
||
| `reference/PolyGateway/` | 本库的依赖,也是「实验室共用库该怎么做」的蓝本 |
|
||
| `reference/pi/` | 外部参考实现 |
|
||
|
||
其余约定见 [CLAUDE.md](CLAUDE.md),那里是协作规则的唯一来源。
|