This document describes how coding-agent discovers rules from supported config formats, normalizes them into a single Rule shape, resolves precedence conflicts, and splits the result into:
- Rulebook rules (available to the model via system prompt +
rule://URLs) - TTSR rules (Time Traveling Stream Rules)
It reflects the current implementation, including partial semantics and metadata that is parsed but not enforced.
Implementation files
packages/coding-agent/src/capability/rule.tspackages/coding-agent/src/capability/rule-buckets.tspackages/coding-agent/src/capability/index.tspackages/coding-agent/src/discovery/index.tspackages/coding-agent/src/discovery/helpers.tspackages/coding-agent/src/discovery/builtin.tspackages/coding-agent/src/discovery/omp-plugins.tspackages/coding-agent/src/discovery/builtin-defaults.tspackages/coding-agent/src/discovery/agents.tspackages/coding-agent/src/discovery/cursor.tspackages/coding-agent/src/discovery/windsurf.tspackages/coding-agent/src/discovery/cline.tspackages/coding-agent/src/sdk.tspackages/coding-agent/src/system-prompt.tspackages/coding-agent/src/internal-urls/rule-protocol.tspackages/utils/src/frontmatter.ts
1. Canonical rule shape
All providers normalize source files into Rule:
interface Rule {
name: string
path: string
content: string
globs?: string[]
alwaysApply?: boolean
description?: string
condition?: string[]
astCondition?: string[]
scope?: string[]
interruptMode?: "never" | "prose-only" | "tool-only" | "always"
_source: SourceMeta
}Capability identity is rule.name (ruleCapability.key = rule => rule.name).
Consequence: precedence and deduplication are name-based only. Two different files with the same name are considered the same logical rule.
2. Discovery sources and normalization
src/discovery/index.ts auto-registers providers. For rules, current providers are:
native(priority100)omp-plugins(priority90) —rules/*.{md,mdc}inside configured extension package roots, normalized via the sharedbuildRuleFromMarkdownpathagents(priority70)cursor(priority50)windsurf(priority50)cline(priority40)builtin-defaults(priority1)
Native provider (builtin.ts)
Loads .omp rules from:
- project:
<cwd>/.omp/rules/*.{md,mdc}when the cwd.ompdirectory exists - user:
~/.omp/agent/rules/*.{md,mdc} - sticky user rule:
~/.omp/agent/RULES.md - sticky project rule: nearest ancestor
.omp/RULES.mdwhile walking from cwd toward the repository root
Normalization:
name= filename without.md/.mdc- frontmatter parsed via
parseFrontmatter content= body (frontmatter stripped)globs,alwaysApply,description,condition/legacyttsr_trigger,astCondition,scope, andinterruptModeare parsed bybuildRuleFromMarkdown- top-level
RULES.mdis synthesized as rule nameRULESand forced toalwaysApply: true
Important caveat: condition values that look like file globs are converted into tool:edit(...) / tool:write(...) scope shorthands with catch-all condition .*.
Agents provider (agents.ts)
Loads from both .agent and .agents directories:
- project: walk upward from
cwdto repo root, loading<ancestor>/.agent/rules/*.{md,mdc}and<ancestor>/.agents/rules/*.{md,mdc} - user:
~/.agent/rules/*.{md,mdc}and~/.agents/rules/*.{md,mdc}
Normalization uses the shared buildRuleFromMarkdown path: filename-derived name, stripped frontmatter body, and parsed globs, alwaysApply, description, condition/legacy ttsr_trigger, astCondition, scope, and interruptMode.
Cursor provider (cursor.ts)
Loads from:
- user:
~/.cursor/rules/*.{mdc,md} - project:
<cwd>/.cursor/rules/*.{mdc,md}
Normalization (transformMDCRule):
description: kept only if stringalwaysApply: normalized to a boolean —trueonly when frontmatter hasalwaysApply: true(anything else becomesfalse)globs: accepts array (string elements only) or single stringcondition/legacyttsr_trigger,astCondition,scope, andinterruptModeare parsed by shared rule helpersnamefrom filename without extension
Windsurf provider (windsurf.ts)
Loads from:
- user:
~/.codeium/windsurf/memories/global_rules.md(fixed rule nameglobal_rules) - project:
<cwd>/.windsurf/rules/*.md
Normalization:
globs: array-of-string or single stringalwaysApply,description,condition/legacyttsr_trigger,astCondition,scope, andinterruptModeparsed by shared rule helpersnameis fixed toglobal_rulesfor the user global file and derived from filename for project rules
Cline provider (cline.ts)
Searches upward from cwd for nearest .clinerules:
- if directory: loads
*.mdinside it - if file: loads single file as rule named
clinerules
Normalization:
globs: array-of-string or single stringalwaysApply,description,condition/legacyttsr_trigger,astCondition,scope, andinterruptModeparsed by shared rule helpersnameis fixed toclinerulesfor a.clinerulesfile and derived from filename for.clinerules/*.md
3. Frontmatter parsing behavior and ambiguity
All providers use parseFrontmatter (utils/frontmatter.ts) with these semantics:
- Frontmatter is parsed only when content starts with
---and has a closing\n---. - Body is trimmed after frontmatter extraction.
- If YAML parse fails:
- warning is logged,
- parser falls back to simple
key: valueline parsing (^([\w-]+):\s*(.*)$).
Ambiguity consequences:
- Fallback parser does not support arrays, nested objects, or quoting rules.
- Fallback values become strings (for example
alwaysApply: truebecomes string"true"), so providers requiring boolean/string types may drop metadata. ttsr_triggerworks in fallback (underscore key); hyphenated keys likethinking-levelalso parse and are normalized to camelCase (thinkingLevel) — key normalization applies to the YAML path too.- Files without valid frontmatter still load as rules with empty metadata and full content body.
4. Provider precedence and deduplication
loadCapability("rules") (capability/index.ts) merges provider outputs and then deduplicates by rule.name.
Precedence model
- Providers are ordered by priority descending.
- Equal priority keeps registration order (
cursorbeforewindsurffromdiscovery/index.ts). - Dedup is first-wins: first encountered rule name is kept; later same-name items are marked
_shadowedinalland excluded fromitems.
Effective rule provider order is currently:
native(100)omp-plugins(90)agents(70)cursor(50)windsurf(50)cline(40)builtin-defaults(1)
Intra-provider ordering caveat
Within a provider, item order comes from loadFilesFromDir glob result ordering plus explicit push order. This is deterministic enough for normal use but not explicitly sorted in code.
Notable source-order differences:
nativeappends project.omp/rules, user~/.omp/agent/rules, userRULES.md, then nearest projectRULES.md.omp-pluginsappendsrules/results per configured extension package root.agentsappends project-walk.agent/.agentsrule dirs before user home dirs.cursorappends user then project results.windsurfappends userglobal_rulesfirst, then project rules.clineloads only nearest.clinerulessource.builtin-defaultsuses the embedded rule source order.
5. Split into Rulebook, Always-Apply, and TTSR buckets
After rule discovery in createAgentSession (sdk.ts), bucketRules(...) applies session-level filtering and bucket assignment:
- Drop rules listed in
ttsr.disabledRules. - Drop rules from the
builtin-defaultsprovider whenttsr.builtinRules === false. - Register rules with a non-empty
conditionorastConditionintoTtsrManager; if registration succeeds, the rule is TTSR-only. - Put remaining
alwaysApply === truerules intoalwaysApplyRules. - Put remaining rules with
descriptionintorulebookRules.
Bucket behavior
- TTSR bucket: any enabled rule with a non-empty parsed
condition(regex) orastCondition(ast-grep patterns) thatTtsrManager.addRule(...)accepts. Takes priority over other buckets. - Always-apply bucket:
alwaysApply === true, not TTSR. Full content injected into system prompt. Resolvable viarule://. - Rulebook bucket: must have description, must not be TTSR, must not be
alwaysApply. Listed in system prompt by name+description; content read on demand viarule://. - A rule with both a trigger condition and
alwaysApplygoes to TTSR only if TTSR registration accepts it; otherwise it can fall through to always-apply. - A rule with both
alwaysApplyanddescriptiongoes to always-apply only (not rulebook).
6. How metadata affects runtime surfaces
description
- Required for inclusion in rulebook.
- Rendered in the system prompt rulebook block (
<domain-rules>in the default template,<rules>in the custom-prompt template). - Missing description keeps the rule out of the rulebook listing; unless it is always-apply or an accepted TTSR rule, it is also not addressable via
rule://.
globs
- Carried through on
Rule. - Rendered inline in the default prompt’s rulebook listing (
- <name> (<glob>, ...): <description>); the custom-prompt template renders them as<glob>...</glob>entries. - Exposed in rules UI state (
extensionsmode list). - Used by TTSR as a global path gate: if a TTSR rule has globs, the match context must include at least one matching file path.
- Not used to automatically select rulebook rules for
rule://; rulebook matching remains advisory prompt behavior.
alwaysApply
- Parsed and preserved by providers.
- Used in UI display (
"always"trigger label in extensions state manager). - Used as an exclusion condition from
rulebookRules. - Full rule content is auto-injected into the system prompt (before the rulebook rules section).
- Rule is also addressable via
rule://<name>for re-reading.
condition, astCondition, scope, and interruptMode
conditionis the regex TTSR trigger field; legacyttsr_trigger/ttsrTriggerare accepted as fallback inputs during parsing.astConditionis the ast-grep trigger field: a string or list of structural patterns, kept verbatim (no glob inference). It only matches on edit/write tool streams, where the language is inferred from the file path. A rule may setcondition,astCondition, or both.scopenarrows TTSR matching scope. Aconditiontoken that looks like a file glob becomestool:edit(<glob>)andtool:write(<glob>)scope entries plus catch-all condition.*;astConditiontokens never trigger this shorthand.interruptModecan override the global TTSR interrupt mode for the rule.
7. System prompt inclusion path
buildSystemPromptInternal receives both rules (rulebook) and alwaysApplyRules.
Always-apply rules are deduped against custom prompt sources (dedupeAlwaysApplyRules drops a rule whose content already appears in the SYSTEM/APPEND_SYSTEM customization) and rendered first, injecting their raw content directly into the prompt (inside a <generic-rules> block in the default template).
Rulebook rules are rendered in a <domain-rules> block as - <name> (<globs>): <description> lines; the URL list in the prompt documents rule://<name> and the workflow section tells the model to read relevant rules first. The custom-prompt template (custom-system-prompt.md) instead renders <rule name="..."> entries with <glob> children under an explicit “You MUST read rule://<name>” instruction.
This is advisory/contextual: prompt text asks the model to read applicable rules, but code does not enforce glob applicability.
8. rule:// internal URL behavior
RuleProtocolHandler resolves against the process-global active-rule snapshot
installed once per top-level session in sdk.ts:
setActiveRules([...rulebookRules, ...alwaysApplyRules, ...ttsrManager.getRules()])Implications:
rule://<name>resolves against rulebookRules, alwaysApplyRules, and registered TTSR rules.- TTSR rules are bucketed out before rulebook/always, but
ttsrManager.getRules()re-adds them to the snapshot so a triggered rule (e.g. a builtin) stays addressable for re-reading. - Rules with no description, no
alwaysApply, and no accepted TTSR condition are not addressable viarule://. - Resolution is exact name match.
- Unknown names return error listing available rule names.
- Returned content is raw
rule.content(frontmatter stripped), content typetext/markdown.
9. Known partial / non-enforced semantics
- The rule providers currently loaded for
rulesarenative,omp-plugins,agents,cursor,windsurf,cline, and embeddedbuiltin-defaults; provider files for other tools may parse other config formats but do not register rule loaders. globsmetadata is surfaced to prompt/UI and is used as a global path gate for TTSR matching, but it is not used to automatically select rulebook rules forrule://.- Rule selection for
rule://includes rulebook, always-apply, and registered TTSR rules (so a triggered TTSR rule can be re-read), but not rules that registered no condition and carry neither a description noralwaysApply. - Discovery warnings (
loadCapability("rules").warnings) are produced butcreateAgentSessiondoes not currently surface/log them in this path.