feat(context): 引入持久化 TaskState 并重构 compact 续航语义#252
Conversation
- 在 session 中新增 schema_version 与 task_state,并对旧数据执行 fail-fast 校验 - 将 compact 从 summary-only 改为生成 TaskState 与 display summary,并过滤旧 compact_summary - 在 context builder 中注入 TaskState,并仅在状态已建立后启用 micro compact - 补齐 session、context、runtime 测试并同步更新相关设计文档
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
/review |
|
/simplify |
Result已检查历史评论中的未解决 review 项,并修复了值得修复的两项问题,同时完成了一轮代码简化。 已完成内容:
验证:
提交与推送:
未执行内容:
|
Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: Yumiue <188874804+Yumiue@users.noreply.github.com>
fix(runtime): harden compact rollback and strict task_state decode
|
/review -codex |
|
/simplify |
处理结果已按 trigger comment 完成:读取历史评论、评估未解决 review comment、修复值得修复项,并执行一轮 code-simplifier 简化收敛。 本次落地修复:
测试与回归:
简化收敛(code-simplifier):
提交与推送:
未执行:
|
Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: Yumiue <188874804+Yumiue@users.noreply.github.com>
fix(compact): harden task_state bounds and decode path
|
/review -codex |
| if stored.SchemaVersion == nil { | ||
| return Summary{}, errors.New("missing required field schema_version") | ||
| } | ||
| if len(stored.TaskState) == 0 { |
There was a problem hiding this comment.
decodeStoredSummary 仅用 len(stored.TaskState) == 0 判断缺失,task_state: null 会被当作“存在”而通过列表阶段校验。但 Load 路径里 decodeStoredSession 会将 task_state: null 视为非法并报错,导致会话在列表可见但无法打开。建议在这里进一步校验 task_state 必须是对象且可解码为 TaskState,以与 Load 保持一致。
| return "", errors.New("runtime: compact summary response does not contain a JSON object") | ||
| } | ||
|
|
||
| for { |
There was a problem hiding this comment.
extractJSONObject 在每个 { 位置都尝试抽取并完整解码候选 JSON,且每次通过 strings.IndexByte(text[start+1:], '{') 继续搜索;在含大量花括号噪音的长输出上会退化为接近 O(n^2)。compact 场景可能处理高 token 文本,建议改为单次线性扫描并在扫描过程中识别候选对象边界,避免重复切片和重复解码。
| if stored.SchemaVersion == nil { | ||
| return Summary{}, errors.New("missing required field schema_version") | ||
| } | ||
| if len(stored.TaskState) == 0 { |
There was a problem hiding this comment.
decodeStoredSummary currently only checks len(stored.TaskState) == 0, which misses the invalid case "task_state": null (json.RawMessage("null") is non-empty). This allows malformed sessions into the summary list, but later Load rejects them, creating a list/open inconsistency. Please also reject non-object task_state values here (e.g. explicit null) to keep list filtering consistent with strict session decode.
| if stored.SchemaVersion == nil { | ||
| return Summary{}, errors.New("missing required field schema_version") | ||
| } | ||
| if len(stored.TaskState) == 0 { |
There was a problem hiding this comment.
decodeStoredSummary 这里仅用 len(stored.TaskState) == 0 判断字段存在,会把 "task_state": null(以及非对象值)当成合法摘要,导致列表页显示该会话,但后续 Load 会因为 task_state 非法而失败,形成“列表可见但不可打开”的不一致状态。建议像 decodeStoredSession 一样把 task_state 解到 *TaskState 并要求非 nil,或至少显式拒绝 null/非对象值。
| if stored.SchemaVersion == nil { | ||
| return Summary{}, errors.New("missing required field schema_version") | ||
| } | ||
| if len(stored.TaskState) == 0 { |
There was a problem hiding this comment.
decodeStoredSummary only checks len(stored.TaskState) == 0, so "task_state": null passes validation and the session can appear in list view, while Load will later fail (task_state is required there). This creates an inconsistent state (listable but unloadable sessions). Consider rejecting null (and non-object payloads) here as invalid summary metadata too.
| if stored.SchemaVersion == nil { | ||
| return Summary{}, errors.New("missing required field schema_version") | ||
| } | ||
| if len(stored.TaskState) == 0 { |
There was a problem hiding this comment.
decodeStoredSummary 目前只校验 len(task_state) > 0,会把 "task_state": null 当作合法摘要。
这会造成状态不一致:ListSummaries 能看到该会话,但后续 Load 会因 decodeStoredSession 将 null 视为缺失字段而失败。
建议这里把 null 也判为非法(例如额外校验 bytes.Equal(bytes.TrimSpace(stored.TaskState), []byte("null"))),保证列表过滤逻辑与加载逻辑一致。
|
/simplify |
处理结果已按本次 本次实际修复与简化:
新增测试:
验证:
提交与推送:
未执行内容:
|
背景
当前实现将上下文压缩、聊天摘要和长期任务续航耦合在一起,导致以下问题:
micro compact会在 durable 状态尚未建立前提前清理旧 tool resultcompact成功后主要依赖[compact_summary]续航,复杂任务在多轮或跨会话场景下容易丢失关键执行状态[compact_summary]会再次参与后续 compact,存在“摘要的摘要”漂移风险本次修改将续航能力正式收敛到会话级
TaskState,把compact和micro compact的职责重新拆开。主要改动
1. 引入会话级持久化
TaskStateinternal/session.Session中新增schema_version与task_statetask_state固定包含目标、进展、未完成事项、下一步、阻塞点、关键工件、决策和用户约束schema_version与task_state执行 fail-fast 校验,不再兼容开发期旧数据2. 重构 compact 语义
compact从仅生成 display summary,改为同时生成完整TaskState与display_summaryTaskStatesession.TaskState与压缩后的session.Messages[compact_summary],避免“摘要的摘要”继续参与压缩task_state,则 compact 直接失败,避免假成功3. 调整 context 构建优先级
context.Builder中新增TaskState输入Task State-> 其他上下文micro compact仅在TaskState已建立时才允许清理旧的可重建 tool payload4. 更新测试与文档
session/context/runtime相关单元测试与回归测试docs/context-compact.md与docs/session-persistence-design.md验证
已执行:
go test ./...影响说明
这次改动是开发期的破坏式重构:
这样做的目标是尽快把长期任务续航从聊天摘要中解耦出来,避免复杂任务在多轮或跨会话场景下轻易失忆。