会话无法加载:"message must have tool source" —— 空 tool callId 能通过 append 写入,却在读取时被拒绝 #6824
Unanswered
xkxkiscoder
asked this question in
Q&A
Replies: 1 comment 1 reply
|
你的三层分析我逐条对着 master( 逐条证据:
你的绕过方案与源码一致:恢复时的校验只针对事件结构(我在 session-persistence 里没有找到对整份日志的哈希/校验和门禁),所以改写事件后能通过; 一个小发现:新的 messages 协议翻译层现在会拒绝空 tool 身份( |
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
版本
0.1.5-alpha-2
症状
一个此前正常工作的会话无法加载(
gateway/internal报错):根因
我解码了存储日志(2134 个拼接的 zstd 帧、3740 个事件)。seq 3070/3071 是这样的:
{ "type": "tool/call", "seq": 3070, "data": { "callId": "", "name": "", "arguments": "{}" } } { "type": "tool/result", "seq": 3071, "data": { "message": { "source": { "kind": "tool", "callId": "" } }, "error": { "name": "ToolNotFoundError", "code": "UNKNOWN_TOOL" } } }三层问题叠加,造就了一份 harness 自己都读不回来的日志:
Provider 翻译层容忍无 id 的 tool_call delta。
packages/llm/llm-deepseek/src/translate.ts的closeBlock()在流式tool_callsdelta 从未携带id/name时回退为block.callId ?? ''/block.name ?? ''(某些 OpenAI 兼容网关确实会这样返回)。甚至有一个测试用例把这个宽松行为固定了下来:translate.spec.ts—— "handles deltas that never carry id or name (empty-string fallbacks)"。agent-loop 原样落盘。
packages/core/agent-loop/src/tool-calls.ts的appendToolCall/appendToolResult把空 id 直接写进tool/call,随后又写进tool/result(执行失败的unknown tool ""成了结果本身)。写入端验证弱于读取端验证。
Session.append()(packages/core/session/src/index.ts)只执行validateSessionEventData+surfaceManager.validateNext。严格的消息不变量(assertMessageEventShape,它要求tool/result的source.callId非空)是在b1af35145b("fix: enforce message snapshot invariants")中加到读取/seed/快照路径上的,却从未加到 append 路径。于是循环可以持久化一个validateStoredEvents->adoptSessionEvent之后会拒绝的事件——一次畸形的模型 delta 就这样把整个会话变成不可加载:整份日志被拒绝,seq 3071 之后的一切都无法再访问。这与
append自己的 JSDoc 相矛盾:"a bad event fails at the append site rather than later during a backend flush."影响
任何一个首个
tool_callsdelta 缺少id的 provider 响应都会悄然污染会话文件。该回合仍会正常完成(模型会收到 unknown-tool 错误),但这个会话从此再也无法加载。修复方向建议
Session.append()中强制执行assertMessageEventShape,让坏事件在 append 处就失败。''。遇到此问题的临时绕过方法:重写存储日志,把两个事件中的空 callId 替换掉,并删除
session_projcache投影缓存让其重建。All reactions