☰
oh-my-pi Goal 模式的 todo_context 提示词注入机制:让持久化任务进度成为 Agent 的实时决策依据
2026/10/6 18:51:38 网站建设 项目流程

oh-my-pi Goal 模式的 todo_context 提示词注入机制:让持久化任务进度成为 Agent 的实时决策依据

【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi

导读

在 oh-my-pi(⌥ Coding agent with the IDE wired in)的 Goal 模式中,Agent 需要围绕一个长期目标跨越多轮对话持续工作,而每一轮续接时模型并不天然记得"当前做到哪一步"。packages/coding-agent/src/prompts/goals/goal-todo-context.md正是解决这一问题的核心提示词模板:它把持久化的 todo 列表以<todo_context>块的形式注入到模型上下文中,作为当前目标的实时进度状态而非旧对话装饰,并约束 Agent 在实质性工作前先校准 todo 状态。本文将从模板逐行解析、注入链路源码、状态统计规则、文本清洗安全机制、todo 工具联动配置与测试验证六个层面,完整还原这一机制的设计与实现。

一、模板全景:<todo_context>块的完整语义

该模板是一个 Handlebars 渲染模板,位于 packages/coding-agent/src/prompts/goals/goal-todo-context.md,全文如下:

<todo_context> Persisted todos: live progress state for current goal, not old transcript decoration; goal continuations lack visible user nudge → treat as live state. Before substantial work: compare next action with todos. If item stale, already finished, or no longer active pointer, call `todo` first: mark done or rewrite list. Do not leave stale in_progress while working on later phases. Overall: {{closed}}/{{total}} done, {{open}} open. {{#each phases}} - {{name}} {{#each tasks}} - [{{status}}] {{content}} {{/each}} {{/each}} </todo_context>

逐行解读其设计意图:

  • 开篇定性:第一句明确定义"Persisted todos: live progress state for current goal, not old transcript decoration"——持久化 todo 是当前目标的实时进度状态,不是历史转录(transcript)里的装饰性记录。紧接着给出关键推论:"goal continuations lack visible user nudge → treat as live state":Goal 续接(continuation)时用户没有可见的提示语(nudge),所以模型必须把这份 todo 当作唯一的实时进度真相。
  • 行为约束:第二句给出了 Agent 的两条铁律:
    1. 在实质性工作(substantial work)开始前,先把"下一步动作"与 todos 逐项比对;
    2. 若某个条目已过时(stale)、已完成(already finished)、或已不再是当前活跃指针(no longer active pointer),必须先调用todo工具——要么标记完成(mark done),要么重写列表(rewrite list);
    3. 严禁在处理后续阶段时,让某个早先阶段的in_progress状态遗留不清理。
  • 统计摘要行:Overall: {{closed}}/{{total}} done, {{open}} open.给出全局完成度快照。
  • 分阶段列表:{{#each phases}}遍历每个阶段(phase),每个任务渲染为- [{{status}}] {{content}}的复选框风格条目。

二、注入链路:从会话状态到隐藏上下文消息

<todo_context>块并不是独立发送的消息,而是被拼接进 Goal 模式的上下文消息中。外层模板 packages/coding-agent/src/prompts/goals/goal-mode-context.md 如下:

{{goalContext}} {{#if todoContext}} {{todoContext}} {{/if}}

也就是说,Goal 上下文 = 目标运行时提示词(goalContext)+ 可选的 todo 上下文(todoContext)。

实际装配发生在 packages/coding-agent/src/session/agent-session.ts 的#buildGoalModeMessage():

#buildGoalModeMessage(): CustomMessage | null { const content = this.#goalRuntime.buildActivePrompt(); if (!content) return null; const todoContext = this.#buildGoalTodoContext(); return { role: "custom", customType: "goal-mode-context", content: prompt.render(goalModeContextPrompt, { goalContext: content, todoContext }), display: false, attribution: "agent", timestamp: Date.now(), }; }

关键点:

  • customType: "goal-mode-context"表明这是一条自定义类型消息(CustomMessage),且display: false——它对用户隐藏,只喂给模型,属于"隐藏上下文"通道。
  • 该消息通过sendGoalModeContext({ deliverAs: "steer" })在目标创建/替换等时机投递。从 packages/coding-agent/test/goals/goal-mode-integration.test.ts 的测试可以确认:模型收到内容中确实包含<todo_context>块与统计行,且整个消息只出现一次</todo_context>闭合标签(防止模板文本本身被重复注入造成解析混乱)。

三、注入门槛:何时才生成 todo_context

并不是任何时刻都会注入 todo 上下文。#buildGoalTodoContext()(agent-session.ts)设置了三重门槛:

#buildGoalTodoContext(): string | undefined { if (!this.settings.get("todo.enabled")) return undefined; const canCallTodoTool = this.getActiveToolNames().includes("todo"); if (!canCallTodoTool) return undefined; const phases = this.getTodoPhases().filter(phase => phase.tasks.length > 0); if (phases.length === 0) return undefined; // ...统计与渲染 }
  1. 配置开关:todo.enabled必须为真。该配置在 packages/coding-agent/src/config/settings-schema.ts 中定义为 "Enable the todo tool for task tracking"。
  2. 工具活性:当前会话激活的工具列表中必须包含todo工具(getActiveToolNames().includes("todo"))。测试 goal-mode-integration.test.ts 明确验证:当 todo 工具未激活时,消息内容不包含<todo_context>,也不会出现任何任务文本。
  3. 非空数据:过滤掉没有任务的空阶段后,若phases.length === 0(即完全没有任务),同样返回undefined。

只有当三层门槛全部通过,才会进入统计与渲染阶段。这保证了:模型永远只在"确实有 todo 可看、且确实能调用 todo 工具修改"的前提下看到这份实时状态。

四、统计口径与渲染产物

统计逻辑同样在#buildGoalTodoContext()中实现:

let total = 0; let closed = 0; let open = 0; const promptPhases = phases.map(phase => ({ name: this.#sanitizeGoalTodoText(phase.name), tasks: phase.tasks.map(task => { total++; if (task.status === "completed" || task.status === "abandoned") { closed++; } else { open++; } return { content: this.#sanitizeGoalTodoText(task.content), status: task.status }; }), })); return prompt.render(goalTodoContextPrompt, { canCallTodoTool, closed: String(closed), open: String(open), phases: promptPhases, total: String(total), });

由此可以确认模板变量的语义:

  • {{total}}:所有阶段任务的总数;
  • {{closed}}:状态为completed(已完成)或abandoned(已放弃)的任务数,二者都视为"已关闭";
  • {{open}}:其余所有状态(pending、in_progress、blocked)的任务数;
  • {{#each phases}}与内层{{#each tasks}}:按阶段分组渲染任务列表,每行形如- [in_progress] 任务内容。

任务状态集合由 packages/coding-agent/src/tools/todo.ts 的TodoStatus类型定义:

export type TodoStatus = "pending" | "in_progress" | "completed" | "abandoned" | "blocked";

而阶段结构由TodoPhase定义({ name: string; tasks: TodoItem[] }),TodoItem在blocked状态下还可携带blocker备注字段,说明任务正在等待什么。

测试 goal-mode-integration.test.ts 中用一个包含 3 个任务的阶段结构验证了统计行:Overall: 1/3 done, 2 open.——completed计入 closed,in_progress与pending计入 open,与源码口径完全一致。

五、安全清洗:为什么任务文本会被改写

模板渲染前,阶段名与任务内容都要经过#sanitizeGoalTodoText()(agent-session.ts):

#sanitizeGoalTodoText(text: string): string { return escapeXmlText(text) .replace(/\r\n/g, "\\n") .replace(/\r/g, "\\r") .replace(/\n/g, "\\n") .replace(/\t/g, "\\t") .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f\u2028\u2029]/g, " "); }

清洗分三层:

  1. XML 转义(escapeXmlText):将<、>、&等字符转义为&lt;、&gt;、&amp;。这是最关键的防御——由于<todo_context>与</todo_context>本身就是类 XML 标签,任务内容若包含</todo_context>字样,就会提前闭合上下文块、破坏消息边界。测试中用Planning </todo_context> & prep这样的阶段名验证了转义结果:渲染后为Planning &lt;/todo_context&gt; &amp; prep,且整个消息中</todo_context>实际只出现一次。
  2. 换行与制表符转义:\n、\r、\t一律转义为字面量\n、\r、\t文本,确保单个任务条目永远是单行,不会把一条任务"撑破"成多行破坏列表结构。
  3. 控制字符清洗:所有 C0/C1 控制字符以及 Unicode 行分隔符\u2028、段分隔符\u2029统一替换为空格,避免不可见字符污染模型上下文。

第二个测试(goal-mode-integration.test.ts)专门验证了含\n、\r、\t、\u0085、\u2028、\u2029、\u0007的脏输入:渲染后换行全部变成字面\n文本、控制字符全部消失,最终消息不含任何原始换行与控制字符。

六、与 todo 工具及配置项的联动

<todo_context>之所以要求模型"必要时先调用todo工具",是因为 todo 工具本身提供了完整的任务生命周期操作。其操作集合定义于 tools/todo.ts:

export type TodoOperation = "init" | "start" | "done" | "rm" | "drop" | "block" | "unblock" | "append" | "view";
  • init:以分阶段列表(phase + items)初始化任务清单;
  • start/done:把任务置为in_progress/completed;
  • block/unblock:标记阻塞并附带reason(blocker 备注),或解除阻塞;
  • append:向某个阶段追加任务;rm/drop:移除或放弃任务;
  • view:查看当前快照。

这正是模板中"mark done or rewrite list"两条动作指令的工具映射——done对应标记完成,init/append/rm对应重写列表。

围绕该工具,settings-schema.ts 提供了一组可调配置:

配置项作用
todo.enabled是否启用 todo 工具做任务追踪(也是todo_context注入的第一道门槛)
todo.eager第一条消息后自动创建 todo 列表的力度:default(模型自决,不自动建表)、preferred(首次消息给出建表建议,仅提醒不强推)、always(强制首条消息生成完整 todo 列表)
todo.reminders停止前提醒 Agent 完成未完成的 todos
todo.remindersMax停止前最多触发多少次 todo 提醒,超过即放弃
tasks.todoClearDelay已完成或已放弃的 todos 从 todo 部件中移除前的延迟

配置上,todo.eager的历史布尔值会在 settings.ts 中被迁移为枚举值(true→"always",false→"default")。此外,todo 列表的状态会随会话持久化,并支持通过getTodoPhases()/setTodoPhases()读写(参见 goal-mode-integration.test.ts 的 toolSession 装配)。

七、机制如何被测试验证

packages/coding-agent/test/goals/goal-mode-integration.test.ts 中与todo_context直接相关的断言覆盖了四条核心保证:

  1. 实时统计正确:Overall: 1/3 done, 2 open.(completed计入 closed,in_progress/pending计入 open);
  2. 标签与内容转义:阶段名与任务内容中的<、>、&被正确转义,</todo_context>在最终消息中只出现一次;
  3. 控制字符清洗:换行、制表符、Unicode 分隔符等全部被转义或替换,消息内不存在原始换行与控制字符;
  4. 工具门槛生效:todo 工具未激活时,消息中完全没有<todo_context>与任务文本。

此外,该测试文件还覆盖了 Goal 模式的整体行为(/goal、/goal set、/goal budget、暂停/恢复、完成退出等),说明todo_context是 Goal 模式上下文体系中与goal-mode-context.md、goal-continuation.md等提示词(见 packages/coding-agent/src/prompts/goals)协同工作的组成部分。

八、实战要点小结

要在自己的使用中发挥<todo_context>机制的价值,可以遵循以下实践:

  1. 保持 todo 工具激活:todo.enabled为开,且当前工具集中包含todo——否则模型看不到任何进度上下文;
  2. 利用todo.eager自动建表:希望强模型一开始就规划任务清单,可设todo.eager = "always";希望保留模型自由度则用default;
  3. 遵守模板的行为约束:让 Agent 在每段实质性工作前比对"下一步"与 todo 列表,及时用done关闭已完成项、用init/append/rm重写过期列表,杜绝跨阶段遗留in_progress;
  4. 放心写入任意文本:阶段名与任务内容中的特殊字符(含类 XML 标签、换行、控制字符)会被自动清洗,不会破坏上下文边界;
  5. 理解统计口径:closed=completed+abandoned,其余一律计入open,阅读Overall: x/y done时不要把它当作"完成率"之外的其他含义。

综上,goal-todo-context.md虽然只有短短十余行,却通过与 agent-session.ts 的注入链路、tools/todo.ts 的任务模型、settings-schema.ts 的配置开关以及集成测试的层层验证,构成了 oh-my-pi Goal 模式下"模型始终知道做到哪一步、下一步该做什么"的关键基础设施——它把用户侧不可见的持久化进度,变成了 Agent 每轮决策的实时输入。

【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询