4 Commits

Author SHA1 Message Date
iomgaa 3492a2994a feat(session): 参数快照补上配方指纹与注入通道,并写定执行器抛异常的契约
四件事,都来自第一个下游的 issue。

**RunRequest 新增 fingerprints。** 上下文与注入内容刻意不进快照(它们是数据不是参数),
而这条规则把生成它们的提示词模板也一起挡在外面了。具体的失败场景:换一份模板续跑不报错,
前几步用 A 模板、后几步用 B,那次运行的数据已经废了却没有任何提示。键形状
request.fingerprint.<name>,和 model_binding 那个坐标分开——一个是「这次运行属于哪一格」,
一个是「用的配方是哪一版」,混在一个字段里事后分不开。默认空映射时一个键都不写,所以已有
配置算出来的快照逐字节不变。

**注入的通道维度不再拍平。** 一通道一键,于是「声明了通道但一条都没选中」和「压根没有这个
通道」分得开:前者是值为空串的键,后者是键不存在。有一档实验要比较的正是这两种情形。

**ActionExecutor 的 docstring 写定抛异常时会怎样**:环境故障走 ENV_ERROR 返回值,实现方真
抛了库不接管、异常原样穿出。理由在 design 0016;简言之库替它编一个结算结果就是在编造,而
副作用状态在那一刻是未知的。

**三个映射字段在构造期冻成只读。** 对抗审查发现 frozen=True 不禁止改字段里那个 dict,于是
构造期那道「快照取值必须是字符串」的校验能被绕过去:构造完往 fingerprints 里塞一个整数,
它一路进日志,要到续跑读日志时才炸——而那时这次运行已经完整跑过一遍。复用 tools 里已有的
_frozen,没另写一套。
2026-08-27 03:58:49 -04:00
iomgaa e71dca7c35 feat(session): 落成事件出口,五个接缝全部有调用点
Event 从零字段变成 kind + run_id + model_binding + step,新增 EventKind(只有一种取值,
但第一天就带 kind,逼每个出口分发)。事件在「一步走完」原子落地之后发,只有这次进程里
真的执行过的步才发;投递失败接住、计数进 RunResult、继续跑,CancelledError 原样穿过。

契约套件那两条 xfail 关掉:一条要断言的是库发了几次、接缝自己看不到;另一条的前提是错的
——审计纪律由意图日志承担不由事件流承担,改成在 unit 层验日志里原文与改写后的文本各有
位置。_project_observation 那段说「将来靠事件流送出去」的注释一并改对。

283 passed / 15 skipped / 2 xfailed,剩下两条 xfail 是原子写与前缀持久性,没有机器兜底。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 22:51:19 -04:00
iomgaa 76495d9e39 fix(session,_recovery,types): 修 Codex 对抗审查报的五条
逐条核实全部成立。最重的是第一条:停止判定的结果只存在于结束记录里,而步记录与结束记录是
两次写。崩在两者之间那次判定就丢了,恢复照常回到预算准入——一次「恰好用满预算完成」被改写
成「预算耗尽」(两条轨迹长度一模一样),一次已经达成目标的运行接着往下跑,一次该以连续解析
失败收尾的运行再花一次模型调用。修法是续跑时把那次判定重演一遍:判定要的东西全在日志里
(动作结果在步记录那条原子写里、工具名在步记录上、模型回复在模型调用结果里)。最终回答那
一档更麻烦,答案文本只存在于结束记录里,靠重新解释那条已存下来的回复找回来——那时副作用还
没发生,重新解释是安全的。五种收尾各一条测试。

其余四条:
- 模型调用失败步在续跑时补写,prompt_chars 填了 0。重新装配出来的和被打断时是同一份,
  照它算。填 0 是把轨迹里那一列改写成假值。
- 取消正好落在写运行开始记录那一下时不留取消标记,留下一份只有开始记录的日志:run 因标识
  已存在而拒绝、resume 当成可以从第 0 步续跑。用 shield 让那条记录一定落地,取消结束记录
  才有地方挂(反过来先写结束记录会拼出结构上说不通的日志)。
- ModelCallResult 能同时带 reply 和 failure,而恢复只看 reply is None,于是把一次失败的
  调用当成成功、接着解释那段回复执行动作。加构造期不变量。
- call_id 空串一路能进持久化记录。docstring 里那句「绝不能是空串」原本没有任何东西守着。

顺带把主循环里重复的一次上下文装配去掉,并把只在续跑第一次迭代成立的那个分支挪出循环。
2026-08-10 03:12:42 -04:00
iomgaa 0132887cb9 feat(session): 落成主循环、两个装配对象与两条入口
A 到 H 八档由这里唯一执行,每档的判定住在 _stopping。三处容易写错的地方都有测试钉着:
恰好用满预算完成记成目标达成而不是预算耗尽(结算在下一次迭代开头);规模超限不写步也不写
意图(唯一一种真的一步都没走的终止);未执行与环境故障两档的观察取库合成的那段,执行器
给的不进历史。

写入序列断言成一条流水:模型意图 → 模型结果 → 动作意图 → 步记录,模型调用失败也落一条
结果记录(不落的话恢复会把一次已知的失败判成状态未知走重放)。结果 ID 从运行标识与序号
推出来而不是随机数——重放同一步拿到同一个 ID,轨迹里也少一列每次都不同的值。

写这块时修掉的两个自己的坑:
- 重放时会重复写意图,而同一步两条同种意图被 _recovery 判成「日志被并发写过」,一次成功的
  重放反倒把日志弄坏。加了两个一次性开关跳过已经落过盘的那条。
- _recovery 数连续解析失败时把模型调用失败那一步也算进去了。它压根没走到解释器,判据改成
  「解释器给了一段回喂文本」——解析失败必定带着那段说明,模型调用失败没有。

取消:CancelledError 原样重抛、run 不返回结果,但结束标记要在宽限期内尽力写下去,写不完
只记日志不再抛(再抛会把取消这件事本身盖掉)。并发隔离:全部可变状态住在每次运行一个的
_Driver 里,定义与请求都是 frozen 的,有一条并发跑两次的测试。

Budget 加了构造期校验(四项都必须为正):零或负数会产出一次「零步、预算耗尽」的运行,
那和一次真的跑满上限的运行在停止原因上完全一样,混进统计里分不出来。

事件出口收下了但没有调用点——Event 还没有字段,发一条内容为空的事件既没用又会变成一份
要兼容的形状。
2026-08-10 02:59:34 -04:00