How it works
A TTSR rule is a Markdown file with frontmatter. omp watches the live model stream against every rule’s condition regex as bytes arrive. The first match aborts generation, prepends the rule body as a system reminder, and retries the same request — no tokens are spent on the aborted continuation past the match point.
Each rule fires at most once per session by default (ttsr.repeatMode: after-gap lets it re-arm after a configurable number of turns), and fired rules are persisted with the session, so a spent rule stays spent across resumes. Use TTSR when a class of mistakes shows up only while the model is mid-stream — reaching for a banned API, generating boilerplate you want compressed, misnaming a project-specific convention.
For rules scoped to a tool call, you can set interruptMode: never to fold the reminder into the matched tool’s toolResult as a <system-reminder> payload instead of aborting the stream. The model sees the correction the next turn — useful for soft nudges where you don’t want to pay the abort/retry cost.
Where rules live
-
.omp/rules/<rule>.md— project scope, checked in with the repo -
~/.omp/agent/rules/<rule>.md— user scope, applies everywhere
Project rules shadow user rules with the same filename.
Frontmatter
| Field | Required | Purpose |
|---|---|---|
description | no | One-line summary shown in /extensions and the trigger card. |
condition | yes* | Regex matched against the model’s output as it streams. JavaScript flavor. Escape backslashes for YAML. (*Alternatively astCondition: ast-grep patterns matched structurally on edit/write streams.) |
scope | no | Comma-separated list of stream surfaces to watch. Defaults to prose and tool arguments (not thinking). |
Scope values:
-
text— assistant prose -
thinking— reasoning channels (when visible) -
tool:<name>(<glob>)— arguments to a specific tool, optionally filtered by a path glob — e.g.tool:edit(*.ts),tool:write(*.rs),tool:bash
Worked example
A real rule from a Rust project: stop the model from reaching for Box::leak and steer it toward Arc<str> instead.
---
description: Refuse Box::leak in production code paths
condition: "Box::leak\\("
scope: "tool:edit(*.rs), tool:write(*.rs)"
---
You were about to write `Box::leak` to obtain a `&'static` reference. Stop.
`Box::leak` permanently allocates for the lifetime of the process — harmless
in a one-shot binary, a real leak inside a server that runs for days. In this
codebase use one of:
- `Arc<str>` for cheaply cloneable owned strings
- `Cow<'static, str>` when the value is sometimes a literal, sometimes owned
- `OnceLock<String>` for actual program-lifetime singletons
Re-plan the edit with one of those, then proceed.When the model emits Box::leak( inside an edit or write targeting a .rs file, the stream aborts mid-call, the rule body is prepended as a system reminder, and the request is retried.d the model re-runs from the same point with the new context.
Writing the condition
The regex matches against raw bytes, not parsed tokens. Anchor on the smallest fragment that uniquely identifies the failure mode and leaves enough room for the model to back out. Catching Box::leak\( works; catching only leak over-fires on log messages and benign mentions.
For rules scoped to edit or write, the regex runs against the reconstructed source content the call introduces — not the raw JSON-serialised arguments — so write the pattern against the code itself and use the scope glob (tool:edit(*.rs)) to filter by path. For other tools, the regex sees the raw argument stream as it arrives.
Listing and disabling
omp -p '/extensions' lists every rule with its scope and source path. Disable a single rule by adding its name to ttsr.disabledRules in config.yml:
ttsr:
disabledRules:
- box-leakThe ttsr_triggered hook event fires every time a rule injects, so you can log or count them from a hook.
Related
-
Hooks — intercept tool calls before they run instead of waiting for the model to start typing them.
-
Context files — permanent guidance that goes in every prompt, instead of fire-on-match correction.
-
Skills — on-demand playbooks loaded when a turn matches a description.