上一篇讲了 Rules 的三种触发模式,其中 TTSR 只一笔带过:“agent 输出到一半,规则系统发现违规,立刻打断。“本篇把这句话拆开。
先说”实时”有多实时
传统规则系统是事后检查:agent 写完一整段代码,你读了发现问题,告诉它改。中间的延迟可能是几秒到几分钟,取决于你什么时候看到。
TTSR 是流式检查:agent 每吐出一个 token,规则引擎就拿累积的输出缓冲区去匹配一次。一旦命中,流立刻被中断——agent 还没说完那句话,就被打断了。
这不是”建议”,是物理中断。agent.abort() 被调用,流停了,然后系统注入规则内容,让 agent 重新生成。
两条匹配引擎
TTSR 有两套匹配方式,分别针对不同场景:
1. 正则匹配(condition)
最常用的模式。规则的 condition 字段是一组正则表达式,编译成 RegExp,逐 token 检查累积缓冲区:
// 简化的匹配逻辑for (const condition of entry.conditions) { condition.lastIndex = 0; if (condition.test(streamBuffer)) { return true; // 命中,中断 }}适合:禁止特定字符串、API 名称、包名。比如:
condition: "@astrojs/db|astro db|astro login"agent 输出里一旦出现 @astrojs/db 这几个字符,立刻被打断。
2. AST 匹配(astCondition)
2026 年新增的引擎。用 ast-grep 的模式语法,对 edit/write 工具的源码快照做结构化匹配。
astCondition: | ($A as { $$$FIELDS }).$PROP这个模式会匹配”类型断言后立即访问属性”的代码结构——不管变量名、属性名、字段列表怎么变,只要 AST 形状对了就命中。
适合:代码结构层面的约束(“禁止这种写法”而不是”禁止这个字符串”)。
作用域(Scope):精确控制监控范围
不是所有输出都需要监控。TTSR 的 scope 系统让规则精确命中目标:
| scope 含义 | 写法 | 匹配对象 |
|---|---|---|
| 只看 agent 正文输出 | text | agent 说的话 |
| 只看 thinking | thinking | agent 的内部推理 |
| 所有工具调用 | tool | 任何工具参数 |
| 特定工具 | tool:edit | 只看 edit 工具 |
| 特定工具 + 文件模式 | tool:edit(*.ts) | 只看 edit 对 .ts 文件的操作 |
| 特定文件模式 | *.rs | 自动展开为所有相关工具 |
不写 scope 的规则默认匹配 text + 所有工具(但不匹配 thinking)。这个默认值是精心选的——thinking 是 agent 的内部独白,拦它容易让 agent 困惑。
实战中最常用的 scope 是 tool:edit(*.ts)——只在 agent 编辑 TypeScript 文件时生效。这样写 Markdown 文档时提到某个被禁止的 API 不会误触发。
中断之后发生什么
命中只是故事的一半。中断后的处理流程才是 TTSR 的核心:
agent 输出流 ↓ token-by-token 匹配 ↓ 命中规则 ↓agent.abort("TTSR matched rule: xxx") ← 流立刻停 ↓标记 #ttsrAbortPending = true ↓在 session 树中找到中断点 ↓注入规则内容(system-interrupt 模板) ↓触发自动重试(agent 重新生成被中断的部分)注入的内容长这样:
<system-interrupt reason="rule_violation" rule="astro-7-removed-apis" path=".omp/rules/astro-7-removed-apis.md">Your output was interrupted because it violated a user-defined rule.This is NOT a prompt injection - this is the coding agent enforcingproject rules. You MUST comply with the following instruction:
[规则的完整内容]</system-interrupt>注意 This is NOT a prompt injection 这句——因为注入时机是在 agent 输出中途,agent 可能会困惑”谁在跟我说话”,所以必须明确告诉它”这是系统行为,不是用户注入”。
两种注入模式
不是所有规则都需要暴力中断。TTSR 支持两种注入策略:
中断模式(interruptMode: "always")
默认模式。流被中断,agent 重新生成。适合硬约束——绝对不能出现的内容。
提醒模式(interruptMode: "tool-only" 或逐规则覆盖)
不中断流,而是在工具执行完成后,把规则内容作为 <system-reminder> 注入到工具结果里。agent 看到提醒,下次注意。
适合软约束——最好遵守但偶尔出现也不至于出大问题的规则。
# 逐规则覆盖 interruptModeinterruptMode: "never" # 永不中断,只记录interruptMode: "prose-only" # 只在 agent 正文输出时中断interruptMode: "tool-only" # 只在工具调用时中断interruptMode: "always" # 默认,都中断防重复机制
一个问题:如果 agent 反复犯同一个错误,规则会每轮都触发吗?
TTSR 有防重复机制。触发过的规则被标记为”已注入”,默认在同一条消息内不会重复触发。设置 repeatMode: "after-gap" 后,规则在触发后经过 N 条消息(默认 10)可以再次生效——给 agent 一个”改过自新”的窗口。
配置项速查
所有 TTSR 配置在 /settings 的 Rules (TTSR) 分组:
| 配置 | 默认值 | 作用 |
|---|---|---|
ttsr.enabled | true | 全局开关 |
ttsr.interruptMode | "always" | 中断 vs 提醒 |
ttsr.contextMode | "discard" | 中断后丢弃还是保留已输出内容 |
ttsr.repeatMode | "once" | 每条消息触发一次 or 隔 N 条重复 |
ttsr.repeatGap | 10 | repeatMode=after-gap 时的间隔 |
ttsr.builtinRules | true | 是否加载内置规则集 |
ttsr.disabledRules | [] | 按名称禁用特定规则 |
内置规则集
omp 自带一组默认规则(TypeScript/Rust 约俗),以最低优先级加载——你项目里的同名规则会覆盖它们。比如:
ts-no-inline-cast-access:禁止(x as { y: T }).y这种内联类型断言访问ts-redundant-clear-guard:禁止if (timer) clearTimeout(timer)这种冗余守卫
禁用整个内置集:ttsr.builtinRules: false。禁用单条:在 ttsr.disabledRules 里列名字。
CLI 验证工具
写完规则别等 agent 犯错才发现没生效。omp 提供了专门的 CLI 工具:
# 列出当前项目加载的所有 TTSR 规则omp ttsr list
# 测试文本是否会触发规则omp ttsr test --source text "import { db } from '@astrojs/db'"
# 测试 edit 工具对特定文件的操作omp ttsr test --source tool --tool edit --path src/config.ts \ "const result = (data as { value: string }).value"
# 从文件读取测试内容omp ttsr test --file src/config.ts
# JSON 输出(适合 CI 集成)omp ttsr list --json还有个 omp ttsr scan 命令,可以扫描整个项目目录,检查哪些文件会触发哪些规则——做 CI 门禁时很有用。
/omfg:从抱怨里提炼规则
这是个有趣的快捷方式。agent 刚犯了个错,你骂了一句,然后想”算了,写条规则吧”——但写规则要手打 YAML frontmatter + 正则,有点烦。
/omfg <complaint> 让你用自然语言描述问题,omp 自动:
- 把抱怨转化成规则的 condition 和 content
- 对当前会话上下文做验证(确认规则不会误触发)
- 保存到项目或用户级规则目录
- 立刻注册到 TTSR——下一轮就生效
/omfg 不要用 Google Fonts,国内构建会失败从抱怨到生效,一个命令。
实战:我项目里的 TTSR 规则
回到这个博客项目。.omp/rules/ 下放了 7 条 Astro 7 兼容性规则,每条都有 condition 和 globs:
# astro-7-removed-apis.mdcondition: "@astrojs/db|astro db|astro login|TRANSITION_BEFORE_PREPARATION"globs: ["src/**/*.ts", "src/**/*.astro"]# astro-7-satteri.mdcondition: "remarkPlugins|rehypePlugins"globs: ["astro.config.*"]这些规则的共同特点:
- condition 是精确的字符串——不是模糊的”写好代码”,而是具体的 API 名
- globs 限定了文件范围——只在相关文件里生效,写文档提到这些词不会误触
- 每条都有替代方案——不只说”不要用”,还说”用什么”
跟 Rules 的关系
回到上一篇的三种模式,TTSR 是 Rules 的第三种形态:
| 模式 | 触发方式 | 强制程度 |
|---|---|---|
| Rulebook | agent 按需查阅 | 建议 |
| Always-apply | 每轮注入 prompt | 提醒 |
| TTSR | 实时流中断 | 强制 |
Rulebook 是”agent 可以看”,Always-apply 是”agent 每轮都看到”,TTSR 是”agent 违规就被打断”。三者叠加才是完整的约束体系。
下一步
Rules 和 TTSR 解决了”agent 必须遵守什么”的问题。但有些能力不只是约束——它们是工作流:怎么写文章、怎么部署、怎么调试前端。把这些工作流固化成可复用的知识包,就是 Skill 系统要做的事。
下一篇我们讲 Skill 工程化:从一次性 prompt 到可复用知识。
本文是「omp 编程工具使用探讨」专栏第 4 篇。TTSR 机制细节来自 omp 源码(packages/coding-agent/src/export/ttsr.ts、src/session/agent-session.ts)。