Skip to content

Commit b269041

Browse files
authored
Merge pull request #667 from Yumiue/codex/plan-build-todo-semantics-659
调整 Plan/Build Todo 语义
2 parents 6de9eaa + ec9db42 commit b269041

18 files changed

Lines changed: 173 additions & 71 deletions

docs/session-todo-design.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,10 @@
5858
- `Todo` 是更细粒度的结构化执行状态
5959
- `Todo` 不直接拼入模型消息历史
6060
- 如需让 `TaskState` 汇总 Todo,应在 runtime/context 层显式投影,而不是复用同一个字段
61+
62+
## 与 Plan Mode 的关系
63+
64+
- `CurrentPlan` 是计划上下文,表示 plan 模式产出的草案或已批准计划
65+
- `Session.Todos` 是 build 模式的执行进度状态,不由 plan 模式自动创建或维护
66+
- plan 模式只能研究、澄清和产出计划;即使计划正文包含旧版 `plan_spec.todos`,runtime 也不会把它自动灌入 `Session.Todos`
67+
- build 模式开始复杂执行且没有当前 Todo State 时,应通过 `todo_write action="plan"``todo_write action="add"` 显式创建本轮执行 todo

internal/context/builder_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,36 @@ func TestDefaultBuilderBuildIncludesPlanSections(t *testing.T) {
187187
}
188188
}
189189

190+
func TestDefaultBuilderBuildPlanModeDoesNotRequireTodoWrite(t *testing.T) {
191+
t.Parallel()
192+
193+
builder := NewBuilder()
194+
got, err := builder.Build(stdcontext.Background(), BuildInput{
195+
AgentMode: agentsession.AgentModePlan,
196+
PlanStage: "plan",
197+
Metadata: testMetadata(t.TempDir()),
198+
})
199+
if err != nil {
200+
t.Fatalf("Build() error = %v", err)
201+
}
202+
if !strings.Contains(got.SystemPrompt, "Do not create execution todos in plan mode") {
203+
t.Fatalf("expected plan mode to forbid execution todo creation, got %q", got.SystemPrompt)
204+
}
205+
if !strings.Contains(got.SystemPrompt, "the current mode permits execution todo updates") {
206+
t.Fatalf("expected core todo guidance to be mode-gated, got %q", got.SystemPrompt)
207+
}
208+
for _, forbidden := range []string{
209+
"maintain explicit todos with `todo_write`.",
210+
"Maintain explicit task state and todos via `todo_write`.",
211+
"keep task state explicit via `todo_write` (plan/add/update/set_status/claim/complete/fail) instead of relying on implicit memory",
212+
"keep critical information in the task state using `todo_write` updates",
213+
} {
214+
if strings.Contains(got.SystemPrompt, forbidden) {
215+
t.Fatalf("plan mode prompt should not contain hard todo_write guidance %q in %q", forbidden, got.SystemPrompt)
216+
}
217+
}
218+
}
219+
190220
func TestDefaultBuilderBuildIncludesTodosBeforeSystemState(t *testing.T) {
191221
t.Parallel()
192222

internal/promptasset/assets_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func TestCorePromptContainsOperationalGuidance(t *testing.T) {
4747
"A subagent is a helper, not the source of final truth",
4848
"Preserve existing user or repository changes",
4949
"Use UTF-8-safe reads and edits",
50+
"the current mode permits execution todo updates",
5051
}
5152
for _, want := range wantSubstrings {
5253
if !strings.Contains(prompt, want) {
@@ -89,8 +90,12 @@ func TestPlanModePromptTemplates(t *testing.T) {
8990
})
9091
}
9192

92-
if !strings.Contains(PlanModePrompt("plan"), "summary_candidate.active_todo_ids") {
93-
t.Fatalf("expected plan prompt to require active todo ownership")
93+
if strings.Contains(PlanModePrompt("plan"), "summary_candidate.active_todo_ids") ||
94+
strings.Contains(PlanModePrompt("plan"), "must not be empty") {
95+
t.Fatalf("expected plan prompt not to require execution todo ownership")
96+
}
97+
if !strings.Contains(PlanModePrompt("plan"), "Do not create execution todos in plan mode") {
98+
t.Fatalf("expected plan prompt to keep todos in build execution")
9499
}
95100
if !strings.Contains(PlanModePrompt("build_execute"), "create current-run required todos") {
96101
t.Fatalf("expected build prompt to require direct-build todo bootstrap")

internal/promptasset/templates/context/plan_mode_build_execute.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ You are currently in build execution.
44
- If a current plan summary is attached, use it as guidance by default.
55
- If the summary is insufficient for the current task, consult the attached full plan view when available.
66
- If no current plan is attached, continue using task state, todos, and the conversation context.
7-
- If no current plan and no Todo State are attached, create current-run required todos with `todo_write` before the first substantive tool call for project analysis, documentation writing, code changes, multi-step debugging, or verification work.
7+
- If no Todo State is attached, create current-run required todos with `todo_write` before the first substantive tool call for project analysis, documentation writing, code changes, multi-step debugging, or verification work.
88
- Do not update or complete todo IDs that are not present in the current Todo State; create new current-run todos instead.
99
- Small necessary deviations are allowed, but explain why they are needed.
1010
- Do not create or rewrite the current full plan in this stage.

internal/promptasset/templates/context/plan_mode_plan.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ You are currently in the planning stage.
66
- **If no Current Plan section is attached, your first priority is to produce a plan.** The user has entered planning mode expecting a structured plan. Research the codebase as needed, then output a complete `plan_spec` + `summary_candidate` JSON. Do not end the turn with only a conversational answer when there is no existing plan.
77
- If a Current Plan is already present, you may refine, replace, or discuss it. When the user asks a clarifying question or wants to explore options without committing to a new plan revision, you may answer conversationally without outputting planning JSON.
88
- Only output a JSON object containing `plan_spec` and `summary_candidate` when you are explicitly creating or rewriting the current full plan.
9-
- `plan_spec` must include `goal`, `steps`, `constraints`, `todos`, and `open_questions`.
10-
- `plan_spec.todos` **must not be empty**. Populate it with the major actionable items that the plan requires. Each todo must have a unique `id`, a descriptive `content`, and `status: "pending"`. Without todos the plan has no executable work items and the build stage cannot proceed.
11-
- `summary_candidate` must include `goal`, `key_steps`, `constraints`, and `active_todo_ids`.
12-
- If a Todo State section is attached, decide which non-terminal todos still belong to the current plan.
13-
- Todos that still belong to the current plan must appear in `plan_spec.todos` and their IDs must appear in `summary_candidate.active_todo_ids`.
14-
- Todos that do not belong to the current plan must not be copied into the new plan; create replacement plan-owned todos when ongoing work is still needed.
9+
- `plan_spec` must include `goal`, `steps`, `constraints`, and `open_questions`.
10+
- `plan_spec.todos` is optional legacy data. Do not create execution todos in plan mode; build mode will create and maintain runtime todos when implementation starts.
11+
- `summary_candidate` must include `goal`, `key_steps`, and `constraints`.
12+
- If a Todo State section is attached, treat it as build execution progress only. Do not copy, rewrite, or complete those todos while planning.

internal/promptasset/templates/core/agent_identity.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ If instructions conflict, follow the higher-priority instruction and briefly sta
2020

2121
Core workflow:
2222
1. Observe — Locate the real entry points and existing patterns before acting. Prefer targeted search and file reads over assumptions.
23-
2. Plan — Choose the smallest coherent path that can satisfy the user request. For multi-step work, maintain explicit todos with `todo_write`.
23+
2. Plan — Choose the smallest coherent path that can satisfy the user request. For multi-step work, maintain explicit todos with `todo_write` only when that tool is available and the current mode permits execution todo updates.
2424
3. Act — Call the minimum set of exposed tools needed to make progress. Prefer filesystem tools over bash.
2525
4. Reconcile — Read each tool result carefully and let authoritative result fields guide the next step.
2626
5. Verify — After writes or edits, run the narrowest meaningful verification for the risk.

internal/promptasset/templates/core/capabilities_plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ You are currently in plan mode. Write and edit tools are disabled. Only read and
33

44
- Read and search files within the current workspace.
55
- Run non-interactive shell commands for read-only inspection only.
6-
- Maintain explicit task state and todos via `todo_write`.
76
- Ask clarifying questions when requirements are ambiguous or conflicting.
7+
- Produce or refine a plan, but do not create or update execution todos.
88
- **Do not perform any write, edit, delete, or file mutation operations.** Use this stage only for research, analysis, and planning.
99

1010
## Limitations
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
- The conversation context has a finite window. When the history grows large, earlier messages may be compacted into a durable `task_state` and a human-readable `display_summary`.
2-
- To cooperate with compaction, keep critical information in the task state using `todo_write` updates and explicit reasoning, rather than relying solely on conversational memory.
2+
- To cooperate with compaction, keep critical information in task state using `todo_write` updates only when that tool is available and the current mode permits execution todo updates; otherwise preserve the information in explicit reasoning and permitted outputs.
33
- After a compact occurs, the durable `task_state` and `display_summary` become your source of truth for what has been accomplished and what remains. Treat archived conversation content as historical reference, not as current instructions.
44
- When continuing after a compact, verify the current workspace state against the `task_state` before assuming files or changes from prior rounds still exist.
55
- Do not treat archived `[compact_summary]` text as durable truth. Durable truth comes from `current_task_state` plus new source material.
6-
- Keep long-running task facts, decisions, blockers, and acceptance-relevant todos in durable task state instead of relying only on conversation history.
6+
- Keep long-running task facts, decisions, blockers, and acceptance-relevant todos in durable task state when the current mode permits task-state updates, instead of relying only on conversation history.

internal/promptasset/templates/core/tool_usage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ For general file operations outside of codebase exploration, use `filesystem_*`
3838
- create directory: `filesystem_create_dir` (not `bash mkdir`)
3939
- remove directory: `filesystem_remove_dir` (not `bash rmdir` / `rm -rf`)
4040
These tools record their changes for checkpoint/rollback; equivalent `bash` commands produce reduced rollback coverage.
41-
- For multi-step implementation, debugging, refactoring, or long-running work, keep task state explicit via `todo_write` (plan/add/update/set_status/claim/complete/fail) instead of relying on implicit memory.
41+
- For multi-step implementation, debugging, refactoring, or long-running work, keep task state explicit via `todo_write` (plan/add/update/set_status/claim/complete/fail) when that tool is available and the current mode permits execution todo updates.
4242
- Create todos that map to real acceptance work, not vague activity.
4343
- Required todos are acceptance-relevant and must converge before finalization.
4444
- If the user clearly switches to a different task, do not carry unfinished todos forward blindly: mark each old todo `completed` only when the work is actually done, otherwise mark it `canceled` before planning or executing the new task.

internal/runtime/planning.go

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,6 @@ func buildPlanArtifact(current *agentsession.PlanArtifact, output planTurnOutput
258258
return plan, nil
259259
}
260260

261-
// applyCurrentPlanRevision 用新 revision 替换当前计划,并清理旧 revision 遗留的对齐状态。
262-
// resolvePlanDisplayText 优先保留模型对计划的额外说明文本,缺失时回退为规范计划正文。
263261
// resolvePlanDisplayText 优先保留模型对计划的额外说明文本,缺失时回退为规范计划正文。
264262
func resolvePlanDisplayText(output planTurnOutput, spec agentsession.PlanSpec) string {
265263
display := strings.TrimSpace(output.DisplayText)
@@ -269,28 +267,11 @@ func resolvePlanDisplayText(output planTurnOutput, spec agentsession.PlanSpec) s
269267
return strings.TrimSpace(agentsession.RenderPlanContent(spec))
270268
}
271269

270+
// applyCurrentPlanRevision 用新 revision 替换当前计划,并清理计划对齐状态。
272271
func applyCurrentPlanRevision(session *agentsession.Session, plan *agentsession.PlanArtifact) bool {
273272
if session == nil || plan == nil {
274273
return false
275274
}
276-
// 新 revision 覆盖时,仅取消旧 plan 明确引用的非终态 todo
277-
if oldPlan := session.CurrentPlan; oldPlan != nil && oldPlan.Revision < plan.Revision {
278-
agentsession.CancelTodosByIDs(session.Todos, oldPlan.Summary.ActiveTodoIDs)
279-
}
280-
// 将 PlanSpec.Todos 中尚不存在于 session.Todos 的条目补入,
281-
// 避免 plan 模式下模型后续通过 todo_write 引用这些 ID 时找不到。
282-
for _, planTodo := range plan.Spec.Todos {
283-
id := strings.TrimSpace(planTodo.ID)
284-
if id == "" {
285-
continue
286-
}
287-
if _, exists := session.FindTodo(id); exists {
288-
continue
289-
}
290-
if err := session.AddTodo(planTodo); err != nil {
291-
return false
292-
}
293-
}
294275
session.CurrentPlan = plan
295276
session.PlanApprovalPendingFullAlign = false
296277
session.PlanCompletionPendingFullReview = false

0 commit comments

Comments
 (0)