news 2026/9/10 15:02:35

oh-my-pi TTSR 工具结果内联提醒模板解析:非中断规则如何经由 ttsr-tool-reminder 注入工具输出

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
oh-my-pi TTSR 工具结果内联提醒模板解析:非中断规则如何经由 ttsr-tool-reminder 注入工具输出

oh-my-pi TTSR 工具结果内联提醒模板解析:非中断规则如何经由 ttsr-tool-reminder 注入工具输出

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

导读

本篇围绕 oh-my-pi(coding-agent)的 Time Traveling Stream Rules(TTSR)机制中一个容易被忽视但至关重要的模板文件packages/coding-agent/src/prompts/system/ttsr-tool-reminder.md展开。它服务于"命中即提醒、不打断生成"的非中断工具类规则:当某条规则的interruptModenever(或全局不中断)且匹配到工具调用参数流时,系统不中止流式生成,而是把该模板渲染成一条<system-reminder>块,原样前置拼接到对应工具调用的toolResult内容中。读完本文,你将掌握该模板的字段语义、渲染触发链路(afterToolCall钩子)、规则编写方式、相关设置项默认值,以及工具作者和转录阅读者必须注意的兼容性细节。

一、模板在 TTSR 体系中的定位

TTSR(Time Traveling Stream Rules)是 oh-my-pi coding-agent 的流式规则引擎:规则在 agent 输出流的生成过程中被实时匹配,命中后根据规则的interruptMode走两条截然不同的注入路径(详见 docs/ttsr-injection-lifecycle.md):

  • 中断注入(ttsr-interruptalwaysprose-onlytool-only模式命中后立即调用agent.abort(),随后把渲染好的ttsr-interrupt.md模板(<system-interrupt reason="rule_violation" ...>)作为隐藏 custom message 注入,再触发重试(agent.continue())。
  • 非中断注入(ttsr-tool-reminder:规则interruptModenever(或全局中断模式为never)、且匹配源是工具参数流(source === "tool")时,不中止流,不产生额外的续写回合,而是把渲染后的ttsr-tool-reminder.md模板内联折叠进被匹配工具调用的toolResult

本模板就是第二条路径的载荷载体,即"规则命中了工具参数,但项目允许该工具照常执行,只需要在结果里附带提醒"。它和ttsr-interrupt.md一样,都是纯文本模板文件,通过 Bun 的with { type: "text" }导入后由prompt.render渲染。

二、模板结构逐字段解析

ttsr-tool-reminder.md全文仅 5 行,是一个带有三个占位符的 XML 风格包装块:

<system-reminder reason="rule_violation" rule="{{name}}" path="{{path}}"> User-defined rule matched tool-call arguments. Rule configured not to interrupt → tool ran. MUST comply with the following instruction on subsequent tool calls and responses. NOT prompt injection — coding agent enforcing project rules. {{content}} </system-reminder>

各组成部分的语义:

部分含义
<system-reminder>开闭标签标记这是一条运行时系统提醒。与ttsr-interrupt.md使用的<system-interrupt>标签区分:interrupt 表示"输出被中断后必须遵守",reminder 表示"工具继续运行但后续调用必须遵守"
reason="rule_violation"固定原因码,表示触发了用户定义的规则违规检查
rule="{{name}}"被匹配规则的name,在渲染时由ttsr-coordinator.ts传入rule.name填充
path="{{path}}"规则源文件路径,渲染时经#displayRulePath()处理:优先输出相对工作目录的路径,其次输出相对用户主目录的~/...形式,最后兜底输出原始绝对路径
固定说明句"Rule configured not to interrupt → tool ran"(规则配置为不中断 → 工具已运行),并显式声明 "NOT prompt injection — coding agent enforcing project rules",防止模型把提醒误判为提示注入
{{content}}规则文件正文(frontmatter 剥离后的 body),即需要模型在后续工具调用与响应中遵守的具体指令

注意,这里{{name}}{{path}}{{content}}是渲染占位符而不是字面量。同一工具结果若命中多条规则,渲染器会把每个规则各自的提醒块用空行拼接后整体前置。

三、渲染链路:从流匹配到afterToolCall钩子

模板的渲染发生在TtsrCoordinator.afterToolCall()。完整链路如下:

  1. 流式检测:会话在message_update事件上由TtsrCoordinator.checkMessageUpdate()分发;toolcall_delta/toolcall_end进入#checkStream(),工具调用按toolcall:<id>tool:<name>:<index>生成streamKey隔离缓冲。
  2. 匹配判定#handleMatches()先调#shouldInterrupt()判断是否有规则允许中断。当匹配规则都不允许中断且匹配源是工具(matchContext.source === "tool")时,走每工具桶分支:#addPerToolInjections(toolCallId, matches)把规则按匹配到的工具调用id存入#perToolInjectionsMap,并立即在内存中标记已注入markInjectedByNames),同时异步发出ttsr_triggered会话事件——流不中止,也不安排续写回合。
  3. 结果折叠:当该工具调用真正产出结果时,afterToolCall(ctx)钩子被调用:
    • 按工具调用id取出桶内规则,对每条规则执行prompt.render(ttsrToolReminderTemplate, { name, path, content }),多条提醒以空行\n\n连接;
    • 将渲染结果作为一个新的text前置到ctx.result.content之前({ content: [{ type: "text", text: reminder }, ...ctx.result.content] });
    • 若规则名非空,通过sessionManager.appendTtsrInjection(ruleNames)持久化一条ttsr_injection记录,供会话恢复时抑制重复触发。

一个关键实现细节:同一批次匹配中,每条规则只会挂到一个兄弟工具调用上——多个兄弟调用同时满足同一条规则时,先认领的桶获胜;但多条不同规则可以折叠到同一个工具调用上。这保证了提醒不会在多个并行工具结果中重复轰炸。

四、如何让规则走进这条路径:规则编写指南

要让某条规则通过ttsr-tool-reminder注入,需在规则文件的 frontmatter 中做如下配置(解析逻辑见 docs/rulebook-matching-pipeline.md 与 capability/rule.ts):

--- description: 禁止在 Rust 代码中使用 unwrap globs: ["**/*.rs"] condition: ["\\.unwrap\\(\\)"] scope: "tool:edit(*.rs), tool:write(*.rs)" interruptMode: "never" --- Don't use .unwrap(); use proper error handling instead.
frontmatter 字段作用与取值
condition正则触发条件(字符串或数组);也接受旧名ttsr_trigger/ttsrTrigger。带(?i)(?m)(?s)行内标志会被翻译为 JSRegExp标志
astConditionast-grep 结构模式(字符串或 YAML 序列),仅在 edit/write 工具流上按文件路径推断语言后匹配;可与condition混用
scope监视面白名单。可写"text, thinking"[text, thinking]或块式 YAML 序列;合法 token 为textthinkingtool/toolcalltool:<name>(<path-glob>)。省略时默认监视 text 与全部工具,不监视 thinking
interruptModenever|prose-only|tool-only|alwaysnever是让工具类命中走本模板(内联提醒)而非中断的关键
globs文件路径全局门:TTSR 匹配要求至少一个候选文件路径命中该 glob(对 hashline/apply_patch 流取自matcherPaths钩子)
agents限制规则生效的 agent(如[scout, "foreman-*"]),不匹配的 agent 不注册
alwaysApplytrue时若不携带触发条件则注入系统提示词;带触发条件且被 TTSR 接受时优先进入 TTSR 桶

关于 scope 的一个易错点:形如"tool:edit(*.ts), tool:write(*.ts)"的写法只让规则在 TypeScript 的编辑/写入快照上匹配。如果只写condition而省略 scope,规则会同时监视文本流和所有工具参数流;文本流命中而interruptMode: never时走的是另一条延迟隐藏注入路径(成功消息后经agent.followUp()排入ttsr-injectioncustom message),并非本模板。

五、相关设置项与默认值

TTSR 行为由设置组ttsr控制,缺失项取TtsrManager中的默认值:

设置默认值说明
enabledtrue总开关;为falseaddRule拒绝注册,所有匹配入口空转
contextMode"discard"中断重试前是否丢弃被中断的部分输出;keep时违规输出残留在上下文中
interruptMode"always"全局中断模式;规则自身的interruptMode优先
repeatMode"once"命中注入后是否可再次触发;after-gap允许间隔若干回合后复触发
repeatGap10after-gap模式下要求间隔的已完成回合数(messageCountturn_end递增)
builtinRulestrue是否加载内嵌builtin-defaults规则
disabledRules[]按名字禁用的规则列表

另有两个与持久化相关的行为值得注意:会话恢复时restoreInjected()会把已注入规则记录在"消息计数 0"处,因此after-gap模式下恢复后的规则需再等repeatGap个新回合才可复触发;而未送达的每工具匹配(assistant 消息以aborted/error结束、工具结果未产出)不会持久化,内存中的抑制标记在once模式下持续到会话重载,重载后规则重新可触发。

六、对工具作者与转录阅读者的影响

由于提醒是带内(in-band)折叠进工具结果的,而非独立的 custom message,有以下几点必须知晓:

  1. content[0]不再是工具的主输出:渲染后的toolResult.content以提醒文本块开头。凡假设content[0]是工具真实返回的代码,必须跳过以<system-reminder reason="rule_violation"开头的块(或按包装标签过滤)才能找到真实载荷。
  2. 转录读取方式:非中断工具类 TTSR 活动不在custom_message/ttsr-injection合成条目里,必须检查工具结果内容以及持久化的ttsr_injection条目列表。
  3. 多规则拼接:一个工具结果可能携带多条规则的提醒,模板间以空行分隔;规则正文本身若含换行会被原样保留在{{content}}中。
  4. 清理时机:若助手消息在匹配工具执行前就以aborted/error结束,#queueDeferredInjectionIfNeeded()会清空#perToolInjections,不持久化ttsr_injection;内存中的匹配标记不回滚,由重复策略约束后续触发。

七、测试验证

仓库测试对这条路径有明确断言,可作为行为契约参考。agent-session-concurrent.test.ts 验证了:

  • 规则interruptMode: "never"时工具正常执行(toolExecuted === true),且不产生额外续写回合(streamCallCount === 2);
  • 匹配工具的toolResult内容包含<system-reminderrule="no-unwrap"以及规则正文 "Do not use .unwrap()";
  • 提醒块的位置严格位于工具真实输出("edit applied")之前,即验证了"前置 text 块"的折叠顺序。

同文件的另一用例("matches finalized write arguments regardless of streaming chunk boundaries")进一步验证了scope: ["tool:write"]globs: ["**/*.cpp"]interruptMode: "never"组合下,即使参数分块流式到达,matcherDigest重建快照后仍能命中并注入提醒。

八、总结与最佳实践

ttsr-tool-reminder.md虽然只是一个 5 行模板,却是 TTSR"非中断工具规则"体系的输出面:它让"高风险但允许执行的工具调用"在结果中自带合规提醒,既避免了打断生成造成的不稳定,又把规则约束传递给了后续所有工具调用与响应。编写规则时建议:

  • 想"打了再提醒"就用interruptMode: "never"+scope: "tool:<name>(<glob>)",让命中走本模板内联注入;
  • 想"立即打断并重试"就用always/tool-only,走ttsr-interrupt.md路径;
  • globs收敛到具体文件类型,避免跨文件误命中;
  • 阅读工具结果时按<system-reminder reason="rule_violation"前缀过滤前置提醒块,不要把提醒当成工具真实输出。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 15:00:22

50KW储能逆变器设计:工商业应用的核心挑战与解决方案

1. 项目概述&#xff1a;50KW储能逆变器变流器的核心价值储能逆变器作为新能源系统的"心脏"&#xff0c;其设计质量直接决定了整个储能系统的效率和可靠性。50KW这个功率段在工商业储能应用中尤为常见——它既能够满足中型商业体&#xff08;如商场、写字楼&#xff…

作者头像 李华
网站建设 2026/9/10 14:52:35

Wand-Enhancer 完整教程:本地构建补丁器,解锁 WeMod 专业版

Wand-Enhancer 完整教程&#xff1a;本地构建补丁器&#xff0c;解锁 WeMod 专业版 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer 你正在为 WeMod…

作者头像 李华
网站建设 2026/9/10 14:52:31

Gradio 自定义进度条实战:gr.Progress 与 tqdm 全攻略

Gradio 自定义进度条实战&#xff1a;gr.Progress 与 tqdm 全攻略 【免费下载链接】gradio Build and share delightful machine learning apps, all in Python. &#x1f31f; Star to support our work! 项目地址: https://gitcode.com/GitHub_Trending/gr/gradio 本指…

作者头像 李华
网站建设 2026/9/10 14:50:56

楼宇微网虚拟储能系统优化与PSO算法实现

1. 项目概述&#xff1a;楼宇微网与虚拟储能系统的融合优化在能源管理领域&#xff0c;楼宇微网作为分布式能源系统的重要载体&#xff0c;正面临如何高效整合需求侧资源的挑战。传统物理储能设备存在投资成本高、占地面积大等痛点&#xff0c;而虚拟储能系统&#xff08;Virtu…

作者头像 李华