# ADR-0165 — Regex Scope Rule: Lexemes Only, Never Grammar **Status:** Accepted (ratified by ADR-0207, 2026-06-03) **Date:** 2026-05-26 **Author:** Shay **Anchor:** [[thesis-decoding-not-generating]] **Companions:** [ADR-0164 — Incremental Comprehension Reader](./ADR-0164-incremental-comprehension-reader.md), [ADR-0114a — Anti-overfitting proof obligations](./ADR-0114a-anti-overfitting-proof-obligations.md), [ADR-0150 — Autonomous inter-session contemplation](./ADR-0150-autonomous-inter-session-contemplation.md), [ADR-0152 — Learning-arc proof corridor](./ADR-0152-learning-arc-demo.md), [ADR-0161 — HITL async queue](./ADR-0161-hitl-async-queue.md) --- ## Context — where regex creeps in, and why the line matters CORE's existing front-end parsers (`generate/math_candidate_parser.py`, `generate/recognizer_match.py`) use regex for two structurally different jobs that have been collapsed into one: 1. **Recognizing lexemes** — "this token chunk is a currency literal," "this is a fraction `\d+/\d+`," "this is a numeric expression." These have genuinely closed orthographic rules. Regex is the right primitive. 2. **Matching sentence structure** — "this whole sentence is the shape `How much MASS_NOUN does ENTITY VERB ...`." These do not have a closed rule, because natural language doesn't have one. Regex here is enumeration of memorized shapes, dressed up as grammar. Mixing the two has been the engine's most consistent source of overfitting. [ADR-0164](./ADR-0164-incremental-comprehension-reader.md) replaces the sentence-structure use with an incremental compositional reader. This ADR locks the *boundary*: a structural invariant that any future code must respect, independent of which front-end implementation is current. The rule is in the same spirit as the existing structural invariants — `versor_condition(F) < 1e-6`, "no normalization outside the gate", "no approximate vault recall", "no hidden stochastic fallback." It is a typed boundary, not a guideline. --- ## Rule > **Regex is permitted only at the lexeme level.** Regex must operate on the > orthographic shape of one structured token or contiguous token-class run > whose meaning has a genuinely closed rule. Regex must not match across > word combination, syntactic role, or sentence structure. **Test for any regex literal in the runtime path:** If the regex describes "what this piece of orthographic material looks like," it is a *lexeme primitive* and is permitted. If the regex describes "how these words combine to mean X," it is a *grammar template* and is forbidden. --- ## Legitimate uses (lexeme primitives) These are the canonical examples. The list is extensible through the teaching corridor (§Population, below). | Primitive | Example regex | Extracts | Category emitted | |---|---|---|---| | `currency_literal` | `\$(\d+(?:\.\d+)?)` | numeric value | QUANTITY, unit_class=currency | | `numeric_literal` | `\d+(?:\.\d+)?` | numeric value | QUANTITY (unit pending) | | `fraction_literal` | `(\d+)\s*/\s*(\d+)` | numerator, denominator | QUANTITY, kind=fraction | | `percentage_literal` | `(\d+(?:\.\d+)?)\s*%` | numeric value | QUANTITY, unit_class=ratio | | `time_amount_literal` | `(\d+)[- ]?(hour\|minute\|day\|week\|month\|year)s?` | value, unit | QUANTITY, unit_class=time | | `mass_noun_token` | `(?:money\|profit\|interest\|...)` | the lexeme | UNIT_CATEGORY_TOKEN | | `decimal_currency` | `\$\d+\.\d{2}` | value | QUANTITY, unit_class=currency | | `ordinal_token` | `(?:first\|second\|third\|...)` | rank | ORDINAL | Each primitive has: - a **name** (closed registry key), - a **pattern** (a single, focused regex over orthographic shape), - an **emission** (the typed category + extracted values it produces), - a **provenance** (which ADR or teaching ratification introduced it). Primitives never reach across roles. `currency_literal` recognizes `$18.00`; it does not recognize `$18.00 an hour` (that composition is the reader's job, not the primitive's). --- ## Forbidden uses (grammar templates) These are the patterns ADR-0164 deprecates and this rule forbids recurring. Each example below is a *grammar template* and would be rejected in code review under this ADR. ```python # FORBIDDEN — regex matching question shape: _Q_MASS_NOUN_RE = re.compile( r"^How\s+much\s+" rf"(?P{_MASS_NOUN_PATTERN})" r"\s+(?:will|did|does|do|would)\s+" rf"(?P{_Q_ENTITY_OR_PRONOUN})\s+" rf"(?:have\s+earned\s+|be\s+able\s+to\s+)?{_PATTERN_A_VERBS}" r"(?:\s+.*?)?\??\s*$" ) ``` This is a sentence-structure template. It matches across question stem ("How much"), unit ("money"), auxiliary ("will"), entity ("Tina"), and verb ("earn"). It claims the conjunction of these elements forms a closed shape. It doesn't. ```python # FORBIDDEN — regex matching statement structure: _INITIAL_HAS_RE = re.compile( rf"^(?P{_ENTITY})\s+has\s+(?P\d+)\s+(?P\w+)\.?\s*$" ) ``` Same problem: matches across subject, verb, value, unit, and clause shape. Replace with reader composition rules over the categories `proper_noun_entity`, `possession_verb`, `numeric_literal`, `count_unit_noun`. ```python # PERMITTED — regex matching one orthographic shape: _CURRENCY_LITERAL_RE = re.compile(r"\$(\d+(?:\.\d+)?)") ``` This recognizes the surface form of one token (with the `$` prefix). It has a closed rule (currency notation). It is the correct use of regex. --- ## Code-review test (apply to every new regex) When reviewing or writing a regex, answer three questions: 1. **What does the regex match?** If the answer names a *piece of orthographic material* (a number, a currency amount, a unit-noun set, a date), it's a lexeme primitive. If it names *a way words combine* (a question shape, an assertion shape, a clause pattern), it's a grammar template. 2. **Could a competent linguist describe the matched class as a closed set of orthographic rules?** If yes, the regex is appropriate. If the class is "things people sometimes say to mean X," the regex is enumerating memorized shapes and is forbidden. 3. **What happens when a novel phrasing of the same underlying meaning appears?** A lexeme primitive's domain doesn't depend on the rest of the sentence, so novel phrasings around it don't break it. A grammar template refuses on every novel phrasing. If a refusal on novel phrasing is the expected behavior of the regex, it's a grammar template. A regex that fails any of these three is a grammar template and must be restructured (typically: extract the closed-set vocabulary as a primitive, move the structural part into reader composition rules). --- ## Population — how the primitive set grows The closed registry of lexeme primitives is not static. It grows through the same contemplation → proposal → review corridor that already grows the language packs and (under ADR-0164) the operational lexicon. This means: 1. **The reader refuses on an unrecognized token shape** — for example, it encounters `"$1.5M"` and no current primitive matches it. 2. The refusal is logged with its token shape and position (`evals/discovery/discovery_candidates.jsonl` analog). 3. The contemplation runner (ADR-0150/0155) identifies the shape as a candidate new primitive and emits a proposal carrying: - the proposed primitive's pattern, - its emission category and extracted fields, - replay-equivalence evidence (acceptance does not lift `wrong` above 0 on the active corpus), - the originating refused tokens. 4. The proposal lands in the HITL queue (ADR-0161). The operator reviews the pattern and emission rule. On acceptance, the primitive enters the closed registry with its provenance. 5. The reader picks up the new primitive on next run. No code edit. No parser rewrite. The engine has been *taught* a new lexical recognizer. This matches the user's original framing: regex is a **mental tool** the engine wields. The toolset is bounded and reviewed, but not hard-wired. Adding a tool follows the same ratification discipline as adding a lemma or a category. Two consequences of this design: - **Operators do not write production regex.** They review proposed regex against typed evidence. This eliminates the failure mode where a regex sneaks in to "just unblock GSM8K case 0027." - **The regex set has a measurable closure curve.** Each ratification round either does or doesn't reduce the refused-token count. Diminishing returns become visible — the regex set is on the same measurement substrate as the lexicon and the recognizer corpus. The bootstrap primitive set lands as part of the ADR-0164 Phase 1 PR. It covers the closed orthographic forms already known to be needed from the existing parser (currency, numeric, fraction, percentage, time-amount, ordinal). Everything beyond bootstrap enters via the corridor. --- ## Consequences ### Positive 1. **Overfit-by-design becomes structurally impossible at the regex layer.** A grammar-template regex cannot land in code review without violating this ADR explicitly. 2. **Closed-set vocabulary already collected in the existing parser is preserved.** Mass-noun lists, possession-verb lists, name lists — all become lexeme primitives or lexicon entries. 3. **One regex toolkit, one lexicon, one composition layer.** Three layered concerns with separate population pathways and separate review criteria. Maintenance scales linearly, not multiplicatively. 4. **The teaching corridor's purpose generalizes.** Today the corridor teaches words, domains, and (per the deprecated path) regex recognizers. Under this rule it teaches words, categories, and lexeme primitives — three orthogonal kinds of evidence, each with a clean review predicate. ### Negative / tradeoffs 1. **Refactoring cost is real.** Removing existing sentence-template regexes from `math_candidate_parser.py` and `recognizer_match.py` is a substantial edit, even with vocabulary preserved. The transition plan in ADR-0164 (coexistence → incremental removal) absorbs this. 2. **Lexeme primitives are an attractive surface for new overfitting.** A primitive author could try to smuggle structure ("a number followed by a currency word followed by 'an hour'") into a single regex. The review criteria above are explicit about this; the corridor enforces it. 3. **The reader has to do more work.** Composition that used to live inside a regex now lives in update rules. This is the point — composition is the engine's job, not the regex's — but it shifts complexity from one place to another. --- ## Boundaries — what this ADR does **not** say 1. **It does not forbid regex.** Regex remains a primary tool for lexeme recognition. The current `evals/`, `scripts/`, and CLI parsers already use regex appropriately for log parsing, file-path matching, etc. None of that is affected. 2. **It does not specify the reader.** The reader's design is ADR-0164's scope. This ADR only constrains where regex may live in whatever front-end is current. 3. **It does not retroactively reject older code wholesale.** ADRs ADR-0136.S.1–S.4 and ADR-0163.D.2–D.4 introduced grammar templates under previous policy. They are deprecated by ADR-0164 with an explicit transition plan. New work follows this rule from acceptance of this ADR forward. --- ## Cross-references - **Sibling ADR**: ADR-0164 — the comprehension reader that occupies the space this rule clears. - **Existing structural invariants** (same spirit, different domain): - `versor_condition(F) < 1e-6` (CLAUDE.md §Field Invariant) - "Allowed normalization sites" (CLAUDE.md §Normalization Rules) - "Exact CGA recall" (CLAUDE.md §Core Primitives) - ADR-0114a Anti-overfitting proof obligations - **Population corridor**: ADR-0150 (contemplation), ADR-0152 (learning-arc proof), ADR-0155 (CI contemplation runner), ADR-0161 (HITL async queue). - **Anchor**: `[[thesis-decoding-not-generating]]` — regex is a decoder's tool for recognizing fixed orthographic shapes. It is not a generator's tool for hallucinating sentence grammars.