## 现象(dogfood 实测) 本机链路 `codex 客户端 → codex-layer → mlx-serve` 上,会话反复出现「无人安排的自动结束」: `task_started` 后 30–60 秒即 `task_complete`,且最后一条 agent 消息正文为空。 对近三天 68 份会话 rollout 的逐条核对(判据:一轮内零工具调用且最后 assistant 消息 无可见正文)钉死了形态:**模型只产出 reasoning 就结束**——finalize 后的 completed 终帧里没有任何工具调用项、没有非空可见正文(一条实测 output 1623 token 全部是 reasoning)。这不是 max_tokens 截断:这些收尾的 output 量在 150~8000 之间,从未落在 4096/16384 这类整额上限上;`finish_reason` 是 stop 而非 length。 协议层的投影:Responses 客户端把「completed 且无调用项」判成本轮无动作、立即交回 控制权。于是每一帧这样的「静默终帧」= 一次用户视角的会话中断。中继侧日志同期 全部是 `stream_completed`——流是「成功地」静默的,丢包/超时/子 agent 都不是原因。 ## 修复方案:Chat 方言的空终帧护栏 在转换层 finalize 处判定「completed 终帧是否静默」(纯函数 `silent_final_frame`), 命中则**扣下 completed + `[DONE]`**,由流泵向同一上游重发一轮,把重试藏进同一条 客户端事件流: - 第一次重试**同体重采样**(请求体逐字节不变,前缀缓存全命中,纯赌温度); 第二次带**推动文案**(往 `input` 尾追加一条用户消息,显式要求「可见正文或工具调用」)。 - 重试轮条目续排同一个 response(保留 created 与 output_index 计数),客户端拿到的 始终是单一、连续的 Responses 流。 - 上限 2 次(`EMPTY_FRAME_MAX_RETRIES`);重发失败或额度用尽则把扣下的终帧**原样放行** ——绝不造成「既无 completed 也无 failed」的静默断流,这是本护栏的红线。 - 边界:`finish_reason=length` 的 incomplete 终态不算静默(合法截断,客户端自己看得见); failed 收口不归护栏管;除 reasoning/message 外的任何条目形态都算有效输出不重发; 压缩轮与原生 Responses 透传不挂护栏。 - 护栏开关只在「Chat 方言 + 非压缩轮 + 客户端请求可解析 + 重发通道可用」时打开, 其余路径行为逐字节不变(有专项测试钉住)。 诊断面新增两个事件:`codex_layer.empty_frame_retry`(含 attempt/nudged)与 `codex_layer.empty_frame_exhausted`(含 attempts,重发失败时带 reason)。护栏生效 后可按事件密度量化上游模型的静默帧率。 ## 已知代价(明示) 1. 重试轮的 usage 覆盖被丢弃的静默轮:交付账本只算最终交付物,静默轮另记诊断事件, 两套账不混。 2. 第二次重试的推动请求会在上游造成一次尾部增量与(若钉定基线在意)一条 divergence 记录——只在第一次同体重采样也静默后才会发生,频次远低于原故障本身。 3. 非流式 Chat 回包本期不挂护栏:codex 客户端恒走流式,非流式形态留待有真实需求再立项。
Activity
feat(proxy): Chat 方言空终帧护栏——静默 completed 扣帧重发,根治会话自动结束
GGGBB commentedon Oct 15, 2026
修复已随 PR #81 并入 main(
89a6cca)。验收对应:empty_frame_guard_tests::silent_verdict_covers_reasoning_only_and_empty_but_not_real_output(reasoning-only/空帧=静默;正文/工具/未知项/length=非静默)。nudge_appends_to_input_array_without_touching_original(端到端过 Responses→Chat 转换,落成最后一条 user 消息)与nudge_gives_up_when_input_is_not_an_array。guard_off_keeps_legacy_behavior_byte_for_byte;非静默帧不误扣guard_never_defers_non_silent_frames。retry_attempt_continues_the_same_response_stream(created 不重复、两轮条目同占独立 output_index)。guard_deferred_frame_and_release_restores_it、exhausted_guard_releases_the_original_frame_untouched。codex_layer.empty_frame_retry/codex_layer.empty_frame_exhausted已登记events::layer::ALL。CI 两跑全绿(cargo test / smoke / gates / aftercheck)。本机部署与真机观察结果另行记档。
chore(release): 版本号抬到 0.3.4——发布候选