Skip to content

feat(context): 引入持久化 TaskState 并重构 compact 续航语义#252

Merged
phantom5099 merged 6 commits into
1024XEngineer:mainfrom
wynxing:codex/taskstate-compact-pr
Apr 13, 2026
Merged

feat(context): 引入持久化 TaskState 并重构 compact 续航语义#252
phantom5099 merged 6 commits into
1024XEngineer:mainfrom
wynxing:codex/taskstate-compact-pr

Conversation

@wynxing

@wynxing wynxing commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator

背景

当前实现将上下文压缩、聊天摘要和长期任务续航耦合在一起,导致以下问题:

  • micro compact 会在 durable 状态尚未建立前提前清理旧 tool result
  • compact 成功后主要依赖 [compact_summary] 续航,复杂任务在多轮或跨会话场景下容易丢失关键执行状态
  • [compact_summary] 会再次参与后续 compact,存在“摘要的摘要”漂移风险

本次修改将续航能力正式收敛到会话级 TaskState,把 compactmicro compact 的职责重新拆开。

主要改动

1. 引入会话级持久化 TaskState

  • internal/session.Session 中新增 schema_versiontask_state
  • task_state 固定包含目标、进展、未完成事项、下一步、阻塞点、关键工件、决策和用户约束
  • 会话加载时对 schema_versiontask_state 执行 fail-fast 校验,不再兼容开发期旧数据

2. 重构 compact 语义

  • compact 从仅生成 display summary,改为同时生成完整 TaskStatedisplay_summary
  • compact 输入显式带入当前 TaskState
  • compact 成功后,runtime 同时回写 session.TaskState 与压缩后的 session.Messages
  • 过滤旧 [compact_summary],避免“摘要的摘要”继续参与压缩
  • 新增校验:若生成器返回空 task_state,则 compact 直接失败,避免假成功

3. 调整 context 构建优先级

  • context.Builder 中新增 TaskState 输入
  • system prompt 组装顺序调整为:核心规则 -> 项目规则 -> Task State -> 其他上下文
  • micro compact 仅在 TaskState 已建立时才允许清理旧的可重建 tool payload
  • 没有 durable 状态时,只做 trim,不清理旧工具结果

4. 更新测试与文档

  • 补齐 session / context / runtime 相关单元测试与回归测试
  • 同步更新 docs/context-compact.mddocs/session-persistence-design.md

验证

已执行:

  • go test ./...

影响说明

这次改动是开发期的破坏式重构:

  • 不兼容旧 session 数据
  • 不保留旧的 summary-only compact 续航语义
  • 不提供迁移脚本

这样做的目标是尽快把长期任务续航从聊天摘要中解耦出来,避免复杂任务在多轮或跨会话场景下轻易失忆。

- 在 session 中新增 schema_version 与 task_state,并对旧数据执行 fail-fast 校验

- 将 compact 从 summary-only 改为生成 TaskState 与 display summary,并过滤旧 compact_summary

- 在 context builder 中注入 TaskState,并仅在状态已建立后启用 micro compact

- 补齐 session、context、runtime 测试并同步更新相关设计文档
@codecov

codecov Bot commented Apr 13, 2026

Copy link
Copy Markdown

fennoai[bot]

This comment was marked as outdated.

fennoai[bot]

This comment was marked as outdated.

fennoai[bot]

This comment was marked as outdated.

fennoai[bot]

This comment was marked as outdated.

@wynxing

wynxing commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator Author

/review

fennoai[bot]

This comment was marked as outdated.

@wynxing

wynxing commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator Author

/simplify

@fennoai

fennoai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Result

已检查历史评论中的未解决 review 项,并修复了值得修复的两项问题,同时完成了一轮代码简化。

已完成内容:

  • 修复 internal/runtime/compact.go 中 compact 保存失败后的状态回滚不完整问题。
  • 现在回滚字段包含:MessagesTaskStateTokenInputTotalTokenOutputTotalUpdatedAt,避免内存态与持久化态偏移。
  • 修复 internal/runtime/compact_generator.go 的 JSON 宽松解析问题。
  • 通过严格解码(DisallowUnknownFields)拒绝 top-level 与 task_state 内未知字段。
  • 新增回归测试:
    • TestRunCompactForSessionSaveErrorPolicyBranches 增补状态一致性断言
    • TestParseCompactSummaryOutputRejectsUnknownTopLevelField
    • TestParseCompactSummaryOutputRejectsUnknownTaskStateField
  • 执行了 code-simplifier 简化并落地:
    • 收敛 runCompactForSession 重复错误分支为 failCompact 局部闭包
    • 抽取 cloneStringSlice,减少重复切片复制表达式
    • 使用 strings.NewReader 简化解码输入

验证:

  • go test ./internal/runtime/...
  • go test ./...

提交与推送:

未执行内容:

  • 未处理未被 trigger comment 要求的其它建议项。

View job run

xgopilot and others added 2 commits April 13, 2026 13:31
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
@phantom5099

Copy link
Copy Markdown
Collaborator

/review -codex

fennoai[bot]

This comment was marked as outdated.

@wynxing

wynxing commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator Author

/simplify

@fennoai

fennoai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

处理结果

已按 trigger comment 完成:读取历史评论、评估未解决 review comment、修复值得修复项,并执行一轮 code-simplifier 简化收敛。

本次落地修复:

  • internal/context/compact/runner.go 关联问题:为 durable TaskState 增加持久化边界限幅能力(字段长度、列表长度、列表项长度),在 session.Savesession.Load 解码路径统一执行规范化+限幅,避免状态膨胀污染后续 prompt。
  • internal/runtime/compact_generator.go 关联问题:修复 extractJSONObject 只取首个 { 的误命中风险,改为扫描并选择首个能通过 compact 协议严格解码的 JSON 对象。
  • internal/session/store.go 关联问题:去除 decodeStoredSession 双重反序列化,改为单次解码并保留 schema_version/task_state fail-fast 校验。

测试与回归:

  • 新增/更新测试:
    • internal/runtime/compact_generator_test.go
    • internal/session/store_test.go
    • internal/session/task_state_test.go(新增)
  • 执行:go test ./...(通过)

简化收敛(code-simplifier):

  • 收敛 Normalize+Clamp 重复流程为 normalizeAndClampTaskState
  • 复用测试辅助函数,去除重复 helper。
  • 精简 JSON 扫描循环实现,保持行为不变。

提交与推送:

未执行:

  • 未处理 trigger comment 未要求的其它建议项。

View job run

xgopilot and others added 2 commits April 13, 2026 14:11
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
@phantom5099

Copy link
Copy Markdown
Collaborator

/review -codex

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

发现 2 个需要修复的问题,已按严重度去重合并。

Comment thread internal/session/store.go
if stored.SchemaVersion == nil {
return Summary{}, errors.New("missing required field schema_version")
}
if len(stored.TaskState) == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractJSONObject 在每个 { 位置都尝试抽取并完整解码候选 JSON,且每次通过 strings.IndexByte(text[start+1:], '{') 继续搜索;在含大量花括号噪音的长输出上会退化为接近 O(n^2)。compact 场景可能处理高 token 文本,建议改为单次线性扫描并在扫描过程中识别候选对象边界,避免重复切片和重复解码。

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Focused review completed. I found one correctness issue worth fixing before merge.

Comment thread internal/session/store.go
if stored.SchemaVersion == nil {
return Summary{}, errors.New("missing required field schema_version")
}
if len(stored.TaskState) == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

发现 1 个需要修复的问题,见行内评论。

Comment thread internal/session/store.go
if stored.SchemaVersion == nil {
return Summary{}, errors.New("missing required field schema_version")
}
if len(stored.TaskState) == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decodeStoredSummary 这里仅用 len(stored.TaskState) == 0 判断字段存在,会把 "task_state": null(以及非对象值)当成合法摘要,导致列表页显示该会话,但后续 Load 会因为 task_state 非法而失败,形成“列表可见但不可打开”的不一致状态。建议像 decodeStoredSession 一样把 task_state 解到 *TaskState 并要求非 nil,或至少显式拒绝 null/非对象值。

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 1 issue worth fixing before relying on strict session gating.

Comment thread internal/session/store.go
if stored.SchemaVersion == nil {
return Summary{}, errors.New("missing required field schema_version")
}
if len(stored.TaskState) == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

发现 1 个需要修复的问题(已去重)。

Comment thread internal/session/store.go
if stored.SchemaVersion == nil {
return Summary{}, errors.New("missing required field schema_version")
}
if len(stored.TaskState) == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decodeStoredSummary 目前只校验 len(task_state) > 0,会把 "task_state": null 当作合法摘要。
这会造成状态不一致:ListSummaries 能看到该会话,但后续 Load 会因 decodeStoredSessionnull 视为缺失字段而失败。

建议这里把 null 也判为非法(例如额外校验 bytes.Equal(bytes.TrimSpace(stored.TaskState), []byte("null"))),保证列表过滤逻辑与加载逻辑一致。

@wynxing

wynxing commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator Author

/simplify

@fennoai

fennoai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

处理结果

已按本次 /simplify 要求完成:读取历史评论、评估未解决 review comment、修复值得修复项,并执行 code-simplifier 子代理简化后再验证。

本次实际修复与简化:

  • 修复 internal/session/store.godecodeStoredSummary 不再仅靠 len(task_state) 判定,新增严格校验,拒绝 task_state: null 与非对象值,避免 ListSummaries 可见但 Load 失败的不一致。
  • 优化 internal/runtime/compact_generator.goextractJSONObject 改为单次线性扫描,在扫描过程中识别候选对象并校验,不再按每个 { 重复起点扫描。
  • 引入简化(来自 code-simplifier 并已人工复核):
    • store.go 提取 ensureSessionsDir,收敛重复目录创建逻辑。
    • compact_generator_test.go 提取测试 helper(newCompactGeneratorTestSetuprequireErrorContains)减少重复样板。
    • store_test.go 提取 newTempWorkspaceStoremustSaveSession,简化重复初始化/保存代码。

新增测试:

  • TestDecodeStoredSummaryRejectsNullTaskState
  • TestDecodeStoredSummaryRejectsNonObjectTaskState
  • TestParseCompactSummaryOutputSkipsBraceNoiseAndFindsFirstValidObject

验证:

  • go test ./... 通过。

提交与推送:

未执行内容:

  • 未处理 trigger comment 未要求的其他建议项。

View job run

@phantom5099 phantom5099 merged commit df6c13f into 1024XEngineer:main Apr 13, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants