Skip to content

feat(todo): 接入会话级 todo_write 工具链与上下文注入#310

Merged
pionxe merged 6 commits into
1024XEngineer:mainfrom
Cai-Tang-www:feat(todo)
Apr 16, 2026
Merged

feat(todo): 接入会话级 todo_write 工具链与上下文注入#310
pionxe merged 6 commits into
1024XEngineer:mainfrom
Cai-Tang-www:feat(todo)

Conversation

@Cai-Tang-www

@Cai-Tang-www Cai-Tang-www commented Apr 15, 2026

Copy link
Copy Markdown
Collaborator

变更说明

本 PR 完成 Todo 能力在主链路中的落地,保持 TUI -> Runtime -> Provider -> Tools 边界不变。

核心改动

  • 新增 todo_write 工具(plan/add/update/set_status/remove/claim/complete/fail
  • 在 runtime 中注入 SessionMutator,使工具可安全写回会话 Todo
  • 新增 Todo 领域错误类型与状态流转/版本冲突相关处理
  • 在 context builder 中加入 Todo State 注入源,提升模型可见性
  • 新增 Todo 相关 runtime 事件:todo_updated / todo_conflict / todo_summary_injected
  • 提示词补充 todo_write 使用约束,提升模型任务编排稳定性

测试

  • 新增并补强以下测试:
    • internal/tools/todo/*_test.go
    • internal/runtime/todo_mutator_test.go
    • internal/runtime/todo_runtime_integration_test.go
    • internal/context/source_todos_test.go
    • internal/session/todo_test.go(补充分支)
  • 本地验证:go test ./... -count=1 通过

风险与兼容

  • 主要风险在于 Todo 状态机与 revision 校验比旧逻辑更严格,调用方若传参不规范会收到结构化错误
  • 改动保持在 tools/runtime/context/session 边界内,不引入跨层直连

Closes #261
Closes #262

fennoai[bot]

This comment was marked as outdated.

@codecov

codecov Bot commented Apr 15, 2026

Copy link
Copy Markdown

@Cai-Tang-www

Copy link
Copy Markdown
Collaborator Author

/code 解决review

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: Cai-Tang-www <106404101+Cai-Tang-www@users.noreply.github.com>
@fennoai fennoai Bot mentioned this pull request Apr 15, 2026
@Cai-Tang-www Cai-Tang-www self-assigned this Apr 15, 2026
@pionxe

pionxe commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator

/review

@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 个需要修复的问题,主要集中在 Todo 写入的状态一致性与 revision 约束一致性。

Comment thread internal/runtime/todo_mutator.go Outdated
}

m.state.mu.Lock()
if err := mutate(&m.state.session); err != nil {

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.

这里在持锁区先执行 mutate(&m.state.session) 改写内存态,然后才在解锁后调用 sessionStore.Save。如果 Save 失败,todo_write 会返回错误,但会话内存已经被修改,后续同一 run 仍会读取到“未持久化但已生效”的 Todo 状态,造成语义不一致。建议先克隆会话并在副本上 mutate+save,成功后再原子替换内存态,或在 save 失败时回滚。

Comment thread internal/tools/todo/write.go Outdated
if input.ID == "" {
return fmt.Errorf("%w: action %q requires id", errTodoInvalidArguments, actionRemove)
}
return call.SessionMutator.DeleteTodo(input.ID)

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.

remove 分支忽略了 expected_revision(直接调用 DeleteTodo(id)),这会绕过本 PR 引入的 optimistic concurrency 语义:并发场景下,旧 revision 的调用也能删除最新 Todo。建议让删除路径也接入 revision 校验(例如 DeleteTodo(id, expectedRevision)),并在冲突时返回 revision_conflict

@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 noteworthy issue after reviewing the PR diff.

  • runtimeSessionMutator mutates in-memory session state before durable save, which can leak failed writes into later runtime behavior.

Please consider applying mutation on a cloned session and only swapping into state.session after Save succeeds (or explicitly rollback on save failure).

Comment thread internal/runtime/todo_mutator.go Outdated
}

m.state.mu.Lock()
if err := mutate(&m.state.session); err != nil {

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.

mutate(&m.state.session) updates the live in-memory session before sessionStore.Save is attempted. If Save fails, this function returns an error but state.session remains mutated. In the current run loop, a later successful save (for example while appending tool messages) can persist this mutation anyway, so callers observe an error while the Todo change still takes effect.

This breaks atomicity between tool success/failure and session state. Consider mutating a cloned session snapshot first, saving it, then swapping into state.session only after save succeeds (or restoring the old state on save error).

@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 one correctness issue worth fixing before merge.

Comment thread internal/runtime/todo_mutator.go Outdated
m.state.touchSession()
sessionSnapshot := cloneSessionForPersistence(m.state.session)
m.state.mu.Unlock()
return m.service.sessionStore.Save(m.ctx, &sessionSnapshot)

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.

mutateAndSave persists a full session snapshot after releasing state.mu. Because tool calls run concurrently, another worker can append/save newer messages between unlock and this Save, and then this older snapshot may win the file write order and clobber those newer changes. This PR adds an extra save on every todo_write mutation, which makes lost-update races more likely. Please serialize these saves with the same critical section/version check (or otherwise make persistence compare-and-swap) so stale snapshots cannot overwrite newer session state.

@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 2 issues that can cause incorrect todo behavior under malformed/stale tool arguments.

}

// validateInputLimits 校验 todo_write 入参的字符串与数组规模,避免放大 token/内存占用。
func validateInputLimits(input writeInput) error {

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.

expected_revision is never validated as a positive value. Because session-side checking treats <= 0 as "no revision check", a caller can pass -1 and silently bypass optimistic concurrency checks. Please reject negative revisions during input validation (for top-level and patch/action paths) to preserve conflict detection semantics.

// dispatch 按 action 执行对应 Todo 变更。
func (t *Tool) dispatch(call tools.ToolCallInput, input writeInput) error {
switch input.Action {
case actionPlan:

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.

For action="plan", missing items currently falls through to ReplaceTodos(nil), which clears all todos silently. This makes malformed/incomplete model tool calls destructive. Consider requiring items to be explicitly present (or at least non-nil) for plan, and return invalid_arguments otherwise.

@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.

聚焦本次 diff,我确认了 3 个值得修复的问题(已去重)。

Comment thread internal/runtime/todo_mutator.go Outdated
Comment thread internal/tools/todo/write.go
Comment thread internal/tools/todo/write.go Outdated
@Cai-Tang-www

Copy link
Copy Markdown
Collaborator Author

/code 解决这些review的问题

@fennoai

fennoai Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

处理结果

已按本次 trigger 要求修复 review 指出的问题,并已提交推送到当前 PR 分支。

已完成

  • 修复 runtime 中 todo 变更的原子性问题:改为“基于快照 mutate + 持锁保存 + 成功后替换内存态”,避免 Save 失败时内存态已污染。
  • 修复 todo_write remove 并发语义:remove 现在走 expected_revision 校验链路。
  • 修复参数校验问题:
    • 拒绝 expected_revision < 0
    • action=plan 缺失 items 时返回 invalid_arguments,不再静默清空。
  • 同步更新接口与会话层:SessionMutator.DeleteTodo(id, expectedRevision)session.DeleteTodo(id, expectedRevision)
  • 补充回归测试(runtime/session/tools todo 相关用例)。

验证

  • go test ./... -count=1 通过。

提交信息

  • Commit: 3fbcf5a
  • 已推送分支:fork-pr-310-1776302805

额外说明

按 fork push 流程同步创建了 fork 侧 PR:

未处理项:无。

View job run

xgopilot and others added 3 commits April 16, 2026 01:32
…checks

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: Cai-Tang-www <106404101+Cai-Tang-www@users.noreply.github.com>
fix(todo): address review issues for todo_write and mutator

@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.

I found 4 actionable issues in the current diff (deduplicated). The first one is blocking because the branch does not compile.

}

// todoWireItem 表示 todo_write 入参中的单个 todo 项,并兼容 legacy title 字段。
type todoWireItem struct {

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.

todoPatchInput.toSessionPatch() sets RetryCount/RetryLimit/NextRetryAt, but session.TodoPatch does not have these fields (internal/session/todo.go). This currently causes build failure (unknown field ... in struct literal). The same contract mismatch also appears in decodeLegacyItem for session.TodoItem.

return fmt.Errorf("%w: action %q requires item", errTodoInvalidArguments, actionAdd)
}
return call.SessionMutator.AddTodo(*input.Item)
case actionUpdate:

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.

action="plan" directly calls ReplaceTodos(input.Items) without validating that items was actually provided. Because tool manager does not enforce JSON schema at runtime, malformed calls missing items will silently clear all todos (nil slice). Please return invalid_arguments when items is absent for plan.

if err := ensureTodoWriteTextLength("owner_id", input.OwnerID); err != nil {
return err
}
if err := ensureTodoWriteTextLength("reason", input.Reason); err != nil {

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.

expected_revision is never validated as non-negative. In session logic, expectedRevision <= 0 means "skip revision check", so negative values can bypass optimistic concurrency checks unintentionally. Please reject negative values during input validation.

return fmt.Errorf("%w: action %q requires id", errTodoInvalidArguments, actionClaim)
}
if strings.TrimSpace(input.OwnerType) == "" || strings.TrimSpace(input.OwnerID) == "" {
return fmt.Errorf("%w: action %q requires owner_type and owner_id", errTodoInvalidArguments, actionClaim)

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.

action="remove" ignores expected_revision and calls DeleteTodo(id) unconditionally. This breaks revision consistency with other mutating actions (update/set_status/claim/complete/fail) and allows stale callers to delete newer todos.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

这不是已经解决的吗???

@Cai-Tang-www

Copy link
Copy Markdown
Collaborator Author

/code 修复reviews

@fennoai

fennoai Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

处理结果

已完成“修复reviews”核查与验证。基于历史 review 评论逐项对照当前分支代码后,相关问题均已修复,未发现新的待改项,因此本次未新增代码提交。

已确认项

  • todo_writeplan 在缺失 items 时会返回 invalid_arguments,不再静默清空。
  • todo_writeremove 已接入 expected_revision,不会绕过并发校验。
  • expected_revision < 0 已在参数校验阶段拒绝。
  • runtime todo 持久化路径已改为持锁下“快照 mutate + save + 替换内存态”,避免保存失败后内存污染与旧快照覆盖。
  • 工具层与 session 层字段契约已对齐(此前构建失败点已修复)。

验证

  • 运行 go test ./... -count=1:全部通过。

未执行项

  • 无(本次无需额外补丁或新增提交)。

View job run

@pionxe pionxe merged commit e3b01af into 1024XEngineer:main Apr 16, 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

3 participants