# Agent Note: Terminal turn errors survive same-turn retry history

Status: implemented

[English](2026-08-20-turn-error-survives-same-turn-retry-history.md) | 中文

## 问题

Web 的 `turn-error` Definition 一旦发现所属轮次携带任何 `llm/retry` 事件，就永久抑制自身节点。这条规则编码的是[有界 LLM 请求恢复](../architecture/2026-06-21-bounded-llm-request-recovery.zh.md)最初交付的重试模型：当时重试会关闭失败轮次并开启下一个编号轮次——带重试历史的轮次只可能是中间失败，其事实已经落在重试行上，而耗尽后的终态失败落在一个没有重试事件的后续轮次里。

此后 agent loop 改为在失败的轮次与步骤内部重试——`llm-retry` 的运行时不变量要求 `llm/retry` 出现在打开的轮次与步骤内，其 loop 测试断言恢复后的轮次只有一条 `step/start`。在这一生产方行为下，"该轮次拥有重试历史"与"这条 `turn/end` 错误就是耗尽后的终态失败"总是同时成立，于是这条抑制规则恰好藏掉了它本想让位的那个失败：所有暂时性重试耗尽后，对话里只剩一条中性的折叠"已重试模型请求（N/N）"行，没有任何错误行，输入框恢复可用。live e2e 场景没有抓住这个缺口，因为它们只覆盖了不可重试的 AUTH 失败（没有重试事件，错误行照常渲染）和重试后恢复成功的暂时性失败（completed 轮次派生不出失败），从未覆盖耗尽。

## 决策

删除该抑制。`turn-error` Definition 只匹配 `turn/start` 和 error 原因的 `turn/end`，只要所属轮次记录了终态错误就渲染；定格的重试链通过独立的 `model-retry` 节点在其旁边渲染。没有 hidden 状态，也没有撤回分支：在同轮次重试下，不存在"已渲染的终态错误随后被取代"的事件顺序，因为 `turn/end` 即关闭轮次。

局部历史窗口在构造上就与完整历史一致——只含 error 原因 `turn/end` 的尾部窗口派生出与完整历史相同的节点；旧规则下窗口恰好包含哪些重试事件，决定了同一失败一处隐藏一处显示。

## 测试

Definition 套件驱动真实 assembler 走完同轮次重试链并以 error 原因 `turn/end` 收尾，断言 `turn-error` 节点连同消息与错误码物化——覆盖完整历史、仅尾部窗口，以及向前补齐早期链之后三种情况。一个无密钥 Web 组合场景用场景自有的两次重试策略对撞三个注入的 SERVER throw 直至耗尽，并在 golden 中钉住终态错误行与定格重试行并列的画面；scaffold 新增 `replayRetryPolicy` 选项，让耗尽在毫秒级完成，而不是走共享默认的五次退避尝试。

## 备选方案

**终态失败到达时重置 `hidden`。** 已否决：这会保留一台状态机，而它剩下的唯一迁移正是引发此 bug 的那一条。在同轮次重试下，没有任何事件序列需要这个抑制。

**区分中间与终态的 `turn/end` 错误。** 已否决：日志中不存在这一区分。一个轮次只结束一次；error 原因对其轮次而言永远是终态。

## 影响

恢复耗尽后现在留下持久、可回放的反馈：红色终态行带展示安全的消息与错误码，加上折叠的重试链作为恢复上下文。按已退役的"新轮次重试"模型录制的会话日志在回放时会为每个失败轮次各渲染一条 `turn-error` 行；预发布格式立场接受这一点，且自同轮次重试落地以来，没有任何已交付的日志生产方再发出过那种形态。
