iomgaa d2be742383 docs(design): 落定网关转发改用保留前缀的方案
回答 GovDoc-SaaS 提的 issue #6,取代 0012 决策四。那一条的结论(只转发 session_id 与
parent_call_id)和它的理由(网关只有两个槽位)都不成立:0012 定于 2026-08-10,而依赖下界
polygateway>=1.1 里 registry 上唯一装得到的 1.1.1 发布于 08-06,那一版的 chat() 上已经有四个
str | None 的槽位。被挡在外面的 cache_namespace 恰恰是上游指定的租户隔离手段,挡掉之后两个
租户提交相同文本时,第二个会读到第一个的模型输出。

要换掉的不是那份白名单的取值,是「哪些键往下传由下游取的名字和网关取的名字偶然相同来决定」
这个机制——补成五个键只是把同一个陷阱重新上好膛。改成绑定里带 gateway. 前缀的键才转发,前缀
之后整段当 chat() 的关键字参数名。由此本库不必列举网关能接受哪些坐标维度,依赖下界照旧
>=1.1,<2,上游此后加维度也不用本库发版(限于取值是字符串的维度,绑定装不下别的类型)。

issue 给的三条路都不采纳,理由分别在决策一到决策三;meta 这一维不做,写在决策五。四条防御
(结构性参数、空串或纯空白、前缀后无参数名、不带前缀的两个历史裸键)都在 call() 里抛
ValueError,表现为一次「第 0 步就以 LLM_ERROR 收尾」的运行。

过了两轮硕士生冷读。第二轮抓出一处事实错误:初稿跟着 issue 写了「下游自己写的 ModelClient
契约套件覆盖不到」,而 polyloop.testing.ModelClientContract 1.0.2 起就随包发布,继承它就能跑,
取消传播恰恰是它覆盖的五条之一。背景那一段换成了成立的那条代价——两份网关适配器各自漂移。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 11:52:37 -04:00
2026-08-27 05:29:28 -04:00
2026-08-27 05:29:28 -04:00
2026-08-27 05:29:28 -04:00

PolyLoop

实验室共用的 Agent 执行内核。治理单位是一次运行:围绕一个目标的有界多轮 「模型决策 → 动作 → 观察」循环,含预算、停止语义、取消、逐步轨迹与 Skill 注入。

它和 PolyGateway 是叠起来的两层。PolyGateway 治理一次模型调用(多源、限流、重试、熔断、 缓存、遥测),PolyLoop 治理一次运行,并用 PolyGateway 的顶层公共 API 拿模型。 任务编排、批量调度、评分、检索、Skill 的生成与进化都留在下游项目。


安装

发布在实验室自建的 Gitea PyPI registry 上,公网 PyPI 查它是 404,所以装它必须自己带索引地址:

pip install --extra-index-url https://gitea.iomgaa.online/api/packages/iomgaa/pypi/simple/ \
    "polyloop==1.0.*"

模型调用要经 PolyGateway,那部分是一个单独的 extra——不用它的人不该被迫装上网关:

pip install --extra-index-url https://gitea.iomgaa.online/api/packages/iomgaa/pypi/simple/ \
    "polyloop[gateway]==1.0.*"

契约套件随包发布,它要的 pytest 也是一个单独的 extra

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,不会在别处留下过期条文。

  • ① 协作规范 —— 见 CLAUDE.mdresearch-wiki/README.md(文档体系)

  • ② 需求对齐 —— 从 dissect 的 harness/agent/ 与 GovDoc-SaaS 的 packages/docagent-core/ 提取真实需求,产出 research-wiki/migrations/ 下两份迁移文档(删除清单 + 组件映射 + 验收口径), 并检查 CHSAnalyzer 的 agent 方案落在边界内还是边界外。这一阶段的产物决定库的边界, 所以它排在架构前面:边界画错,后面每一份架构文档都要重写

  • ③ 架构 —— research-wiki/explanation/architecture.mdpyproject.toml 的 import-linter 契约。 架构文档先于代码存在,此期间它是一份规格而不是描述,文档开头须写明这一点

  • ④ 测试框架 —— 四层都在跑(划分判据是「依赖什么」,见 CLAUDE.md §1.9)。 e2e 打真实模型网关、会产生真实费用,所以默认不跑:要 POLYLOOP_E2E=1 加显式 pytest -m e2e,配置见 .env.example。那套公共行为一致性用例住在 polyloop.testing 里随包发出去,五个接缝在本仓库都接上了实现跑起来,接点是 tests/contract/tests/integration/ 下那几个继承契约基类的子类

  • ⑤ 实现 —— 十一个模块全部落地,五个接缝都有调用点。stores 有两种形态:逐行追加进 本地文件的那个,和只留在进程内存里、进程一退就没了的那个。关系数据库那种仍然由下游 自己实现,契约套件是它的准入标准,而套件随包发布在 polyloop.testing

  • ⑥ 验收 —— 两件事都做完了。一是自己造负载压:照三个消费者将来的用法造负载,用真实 数据真的打模型跑完,看这个内核在这个量级上扛不扛得住。这一步之所以必须自己做,是因为 三个消费者一个都还没到能用它的时候,而「从没被任何人用过」是它当时最大的未验证项, 等下游是等不来的。二是把 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    # pyteste2e 默认不跑,它打真实网关要花钱)
make ci      # 上面两条

还没有 CI workflowmake ci 就是当前的全部机器闸,和 PolyGateway 一样。发布怎么做见 research-wiki/guides/releasing.md

文档质量不走机器检查,走 CLAUDE.md §3 的「硕士生阅读」评审。

参考资料

reference/ 下的六个仓库与 agent-core.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,那里是协作规则的唯一来源。

S
Description
实验室共用的 Agent 执行内核:一次运行的预算、停止语义、取消、逐步轨迹与 Skill 注入
Readme 1 MiB
v1.0.3 Latest
2026-08-30 02:20:51 +08:00
Languages
Python 99.3%
Shell 0.6%