长任务最怕的不是中断,而是压缩时悄悄失忆

Agent 跑长任务时,大家最怕什么?

很多人会说:请求失败、模型超时、网络断开、Gateway 掉线。

但 NousResearch/hermes-agent issue #18458 暴露了一个更隐蔽的问题:真正危险的不是某一次中断,而是上下文压缩刚好在中断时失败,然后系统用静态占位符替代真实摘要

任务表面上没有崩。

会话继续活着。

但中间一大段对话、工具调用、决策过程被压掉了,却没有生成真正 summary。

这就是长任务里的“静默失忆”。


现象:incomplete chunked read 之后,Hermes 插入静态 fallback marker

issue 里描述的是长时间运行的 Hermes Agent session。

自动 context compression 在调用 auxiliary summary 时遇到网络/streaming 中断:

peer closed connection without sending complete message body (incomplete chunked read)

日志里出现类似内容:

Failed to generate context summary: peer closed connection without sending complete message body (incomplete chunked read).
Further summary attempts paused for 60 seconds.
Summary generation failed — inserting static fallback context marker
Auxiliary compression: using auto (...) at ...

关键不只是 summary 失败,而是后续行为:

插入静态 fallback context marker
移除中间 conversation turns
没有真实摘要

这意味着压缩完成了,但压缩结果不是“摘要”,而是“这里曾经有内容,但我没总结出来”。


为什么这比直接失败更危险?

如果任务直接失败,用户至少知道要重试。

但静态 fallback marker 的问题是:

系统继续运行,看起来没有大事

后续 assistant 可能继续工作,但它已经失去了:

  • 中间做过哪些尝试;
  • 哪些方案被排除;
  • 用户临时改过什么要求;
  • 哪些文件已经写过;
  • 哪些链接已经查过;
  • 哪些结论只是草稿,哪些已经确认;
  • 哪些外部操作已经完成。

于是长任务会出现很典型的后遗症:

  • 重复检索;
  • 重复执行;
  • 忘记用户刚刚确认的方向;
  • 把已发布内容当成未发布;
  • 需要从文件、日志、session search 里人工恢复;
  • 甚至产生前后矛盾的答复。

这类问题不是“模型不聪明”,而是任务交接摘要丢了。


context compression 本来是为了解决什么?

长任务里,Agent 的上下文会越来越大:

  • 用户消息;
  • assistant 推理与计划;
  • 工具调用结果;
  • web fetch 内容;
  • 文件读写;
  • 代码 diff;
  • 发布结果;
  • 错误日志。

如果一直把所有内容塞进模型上下文,成本高、速度慢,也容易超出 context window。

所以 Hermes 会做 context compression:

把中间大量 conversation turns 压缩成一段摘要
保留任务连续性
释放上下文空间

这个设计本身是对的。

问题在于:压缩摘要生成也是一次 LLM 调用,也会失败。

尤其是长任务中,压缩 prompt 本身可能很大,auxiliary summary response 也可能是长连接请求,更容易遇到:

  • incomplete chunked read;
  • peer closed connection;
  • unexpected EOF;
  • response ended prematurely;
  • timeout;
  • proxy 中断;
  • provider streaming close。

当前问题:把 transient error 当成最终失败

issue 提出的核心改进是:这些错误应该被识别为 transient connection errors,而不是直接走最终静态 fallback。

需要识别的典型字符串包括:

incomplete chunked read
peer closed connection
unexpected eof
response ended prematurely
connection was closed

这些不是 prompt 错误,也不是认证错误,更不是模型拒答。

它们通常意味着:

这次连接半路断了,但重试可能成功

所以更合理的行为应该是:

1. 短暂重试; 2. 如果使用 auxiliary.compression.provider: auto,尝试 fallback provider; 3. 只有重试和 fallback 都失败后,才插入静态 marker; 4. 日志明确区分“retry 成功 / provider fallback 成功 / 最终静态 fallback”。


更稳的压缩流程应该长什么样?

理想流程可以这样设计:

触发 context compression
  ↓
调用 auxiliary summary provider
  ↓
如果成功:写入真实 summary
  ↓
如果 transient connection error:重试 1-2 次
  ↓
如果仍失败且 provider=auto:切换 fallback provider
  ↓
如果 fallback 成功:写入真实 summary
  ↓
全部失败:插入 static fallback marker

这样做的好处是:

  • 保留“不崩溃”的安全性;
  • 降低静默失忆概率;
  • 对网络抖动更鲁棒;
  • 长任务续接质量更稳定。

静态 fallback marker 应该是最后防线,不应该是第一次 summary 连接中断后的默认结果。


为什么长工具任务更容易踩中?

issue 里提到,这更可能发生在 very long、tool-heavy tasks。

原因很简单:

1. 压缩 prompt 更大

工具结果越多,压缩输入越长。

更长的输入意味着辅助模型处理时间更久,也更容易触发代理、网关、provider 的连接边界。

2. summary 输出也可能更长

长任务摘要不能太短,否则保不住关键决策。输出越长,请求持续时间越久。

3. 任务状态更复杂

短任务即使丢一点上下文,也容易从最近几轮恢复。

长任务一旦丢中间段,损失的是完整工作链路。

4. Gateway 场景更脆弱

issue 的环境备注里提到,这是在 gateway / Feishu 长时间 workflow 中观察到的。

Gateway 长任务常常叠加:

  • 消息平台;
  • 网络代理;
  • LLM provider;
  • auxiliary provider;
  • context compression;
  • 工具调用。

链路越长,偶发连接错误越正常。


如何判断自己遇到了“压缩失忆”?

如果 Hermes 长任务突然变得“像换了个人”,可以查这些迹象。

1. 日志出现 incomplete chunked read

重点搜索:

incomplete chunked read
peer closed connection
Failed to generate context summary
Summary generation failed
static fallback context marker

2. 任务没有崩,但开始重复工作

例如:

  • 又去查已经查过的 issue;
  • 又写已经写过的文件;
  • 忘记刚刚发布过的文章;
  • 忘记用户刚改过的标题要求;
  • 需要你重复“继续”。

3. 压缩后摘要明显过短或只是占位

正常 summary 应该包含任务目标、已完成步骤、关键文件、未完成动作。

如果只有静态 marker,那就不是摘要。


对用户的临时建议

在修复完全落地前,跑长任务时可以这样降低风险。

1. 关键节点写文件

不要只依赖会话上下文。

每完成一篇文章、一个发布动作、一个结论,写入:

  • research card;
  • daily memory;
  • TODO 文件;
  • 发布日志;
  • git commit。

文件比上下文压缩更可靠。

2. 长任务分段提交

例如每 1-3 篇文章 commit 一次,而不是全部做完再提交。

3. 使用稳定 auxiliary provider

如果压缩摘要经常失败,优先给 auxiliary/compression 配置更稳定的 provider。

4. 保留 session / tool logs

压缩丢摘要时,日志和文件是恢复上下文的救命绳。


和 DeepAI API 中转站的关系

如果你用 DeepAI API 中转站作为 OpenAI-compatible provider:

Base URL: https://api.deepai.wang/v1
API Key: 你的 DeepAI Key
Model: 以 DeepAI 控制台为准

需要区分两件事:

1. 主任务模型调用; 2. auxiliary compression summary 调用。

如果 Hermes 的 auxiliary compression 也走某个 OpenAI-compatible provider,那么连接稳定性、长响应处理、超时、代理链路都会影响压缩成功率。

DeepAI 可以统一 API 入口,减少多 provider 配置复杂度;但如果网络链路或上游响应中途断开,Hermes 仍然需要在 compression 层做好 retry / fallback。

所以这篇的重点不是“换 provider 就解决”,而是:

压缩摘要失败不能第一次就丢上下文

FAQ

incomplete chunked read 是什么?

通常表示 HTTP 响应还没完整传完,连接就提前关闭了。长响应、streaming、代理链路中比较常见。

为什么不直接插入 fallback marker?

可以插,但应该作为最后兜底。第一次遇到 transient 网络错误就丢掉中间上下文,会让长任务静默失忆。

context compression 失败会让任务崩溃吗?

不一定。issue 中的问题是任务继续运行,但中间 turns 被移除且没有真实 summary,导致后续上下文质量下降。

最好的修复是什么?

识别 premature response / streaming close 为 connection error,增加 1-2 次短重试,并在 auxiliary.compression.provider: auto 时走 fallback provider。

DeepAI API 中转站能避免这个问题吗?

DeepAI 可以作为统一 API 入口,但 Hermes compression 层仍需处理网络中断、长响应失败和 provider fallback。两者不是同一层问题。


总结

#18458 的真正教训是:

长任务最怕的不是一次请求失败,而是失败发生在“记忆压缩”这一刻。

如果 summary 生成被 incomplete chunked read 打断,Hermes 不应该马上把中间对话删掉并插入静态占位。

更稳的策略是:先重试,再 fallback,最后才 static marker。

因为对 Agent 来说,压缩摘要不是装饰品,而是长任务继续工作的记忆桥。

Related Post

Copilot 接入里最隐蔽的坑:模型目录、Token 与企业端点其实是同一条链Copilot 接入里最隐蔽的坑:模型目录、Token 与企业端点其实是同一条链

Hermes Copilot provider 的问题不只是 context window 写错:静态模型目录、原始 GitHub token、OAuth client ID、企业 endpoint 是同一条能力发现链。本文复盘 #7731 与 #15114,解释为什么 Copilot 不能当普通 OpenAI-compatible 接口处理。

Hermes agent deepai max tokens context compression.png

Hermes Agent 接入 DeepAI API 中转站:max_tokens 400 与上下文压缩失败排查Hermes Agent 接入 DeepAI API 中转站:max_tokens 400 与上下文压缩失败排查

Hermes Agent 自动上下文压缩时,辅助模型请求因 max_tokens 参数被新模型拒绝并返回 400,导致 fallback context marker 和上下文丢失怎么办?本文结合 Hermes Agent GitHub 已关闭 Issue,整理 DeepAI API 中转站、多模型路由、max_completion_tokens 和辅助任务参数兼容排查方法。