Implements the 4-phase documentation reorganization master plan. - Consolidation: Merged brief/, handoff/, planning/, and decisions/ into briefs/, handoffs/, plans/, and adr/ respectively (101 ADRs relocated) - Root Cleanup: Relocated HANDOFF-gpt55-*.md and key top-level docs (runtime_contracts.md, etc.) to canonical folders. Added superseded alerts. - Indices & Navigation: Created docs/README.md navigation document, docs/sessions/README.md index, docs/adr/README.md index - Note: Also includes prior commit adding ADR-0200+ corpus hygiene governance (ADR-0225, dependency map, backfilled cross-references)
9.7 KiB
ADR-0049 — Intent Classifier Head-Noun Subject Extraction
Status: Accepted Date: 2026-05-18 Author: Shay
Context
ADR-0048 added a pack-grounded
surface for cold-start DEFINITION / RECALL turns where the subject lemma
is in en_core_cognition_v1. The eval lift was real but partial.
Investigating the misses showed they were not pack gaps — the pack
does carry the lemmas — they were subject-extraction gaps in
generate/intent.py:
| Prompt | Pre-0049 subject |
Reason for miss |
|---|---|---|
What is a procedure? |
"a procedure" |
Article not stripped |
What is a relation? |
"a relation" |
Article not stripped |
Why does light exist? |
"does light exist" |
Aux verb + tail not stripped |
Why does knowledge require evidence? |
"does knowledge require evidence" |
Aux verb + tail not stripped |
Does memory require recall? |
"Does memory require recall?" |
Whole prompt; rule matched full string |
Is light a wave? |
"Is light a wave?" |
Whole prompt; rule matched full string |
The _RULES table in generate/intent.py was producing subject
spans, not subject lemmas. Downstream consumers
(graph_planner.graph_from_intent, ADR-0048
_maybe_pack_grounded_surface, future teaching-store inference) need
the lemma — they cannot match a noun phrase like "a procedure"
against a lexicon keyed on procedure, and they cannot key a graph
node off "does light exist" cleanly.
The cleanest fix is at the classifier boundary: produce a clean
lemma in DialogueIntent.subject so every consumer benefits without
each implementing its own article-stripping heuristic.
Decision
Add a deterministic, pack-agnostic post-processor
_normalize_subject(phrase, tag) in generate/intent.py that runs
after the rule table fires and rewrites the subject span according
to its intent's syntactic shape.
Behaviour by intent
| Intent | Transform |
|---|---|
DEFINITION / RECALL / PROCEDURE |
strip trailing punctuation, strip leading articles; preserve multi-word noun phrases (e.g. "artificial intelligence") |
CAUSE / VERIFICATION |
strip trailing punctuation, strip leading aux verbs (is, are, does, do, can, could, …), strip leading articles, return the head noun (first remaining token) |
CORRECTION |
strip trailing punctuation, strip leading articles |
UNKNOWN |
bypass (preserve raw input for debugging) |
COMPARISON / TRANSITIVE_QUERY / FRAME_TRANSFER |
already captured by their own named-group regexes; not routed through _RULES |
Aux-verb and article sets
Frozen sets in generate/intent.py:
_ARTICLES = frozenset({"a", "an", "the"})
_AUX_VERBS = frozenset({
"is", "are", "am", "was", "were", "be", "been", "being",
"does", "do", "did",
"has", "have", "had",
"can", "could", "would", "should", "shall", "will",
"might", "may", "must",
})
These are closed word lists. The normalizer does not depend on the cognition pack, the language pack manifold, or any external state — it is a pure syntactic transform.
Fallback
If stripping aux verbs and articles would empty the subject (e.g.
"What is the?"), the normalizer returns the cleaned original phrase
rather than producing an empty subject. Downstream consumers
(_maybe_pack_grounded_surface) already handle empty subjects
correctly (return None), but preserving a non-empty subject keeps
debugging output and trace surfaces readable.
Why this is doctrine-aligned
CLAUDE.md prohibits opaque LLM fallbacks, stochastic sampling, hidden normalisation. This ADR:
- Is not opaque. Both word sets are static frozen Python sets, visible at module scope. Every transformation rule is explicit.
- Is not stochastic. Identical input produces byte-identical
DialogueIntent(test_normalization_is_deterministic). - Is not hidden normalisation of the algebra. The normalizer
rewrites a typed dataclass field, not a versor, not a manifold,
not a field state. No hot-path math is touched. No
versor_conditioninvariant is in scope. - Is not coupled to a specific pack. The aux-verb / article lists are English syntactic categories, not pack vocabulary. The ADR-0048 pack lookup remains the consumer of the cleaner lemma; the classifier itself does not load any pack.
The trust-boundary discipline is preserved: user-controlled text is still escaped at all log/display sites by their respective handlers; this ADR changes only the in-process classification output.
Characterisation — core eval cognition
A/B run on the 13-case public cognition split, identical
RuntimeConfig except for the merge of this ADR:
| Metric | Pre-ADR-0049 | Post-ADR-0049 | Δ |
|---|---|---|---|
intent_accuracy |
100.0 % | 100.0 % | 0 |
surface_groundedness |
46.2 % | 61.5 % | +15.3 pp |
term_capture_rate |
33.3 % | 50.0 % | +16.7 pp |
versor_closure_rate |
100.0 % | 100.0 % | 0 |
versor_condition < 1e-6 |
preserved | preserved | invariant |
The two cases that lift through the pack-grounded path
(definition_procedure_023 and definition_relation_026) carry the
article-stripping flow:
"What is a procedure?" -> intent.subject == "procedure"
"What is a relation?" -> intent.subject == "relation"
Both then match the cognition pack lexicon and emit a pack-grounded surface via ADR-0048.
The CAUSE / VERIFICATION head-noun extraction ("Why does light exist?" → "light", "Does memory require recall?" → "memory")
does not directly move the eval on this split because CAUSE and
VERIFICATION intents are scope-excluded from ADR-0048's pack path.
That work is foundational for the next ADRs: a future
COMPARISON / CAUSE / VERIFICATION pack path or teaching-store
inference will inherit clean lemmas without re-implementing the
extraction.
Consequences
What changes
generate/intent.pygains the_normalize_subjectpost-processor and two closed-set frozen sets (_ARTICLES,_AUX_VERBS).DialogueIntent.subjectis now a clean lemma (or noun phrase) for every intent that routes through_RULES.- ADR-0048 pack-grounded surface coverage broadens from 4 → 6 of 13 cognition-eval cases.
What does not change
IntentTagenum is unchanged.- The rule table (
_RULES) is unchanged — the post-processor runs after a rule fires. - COMPARISON, TRANSITIVE_QUERY, FRAME_TRANSFER, and BELONG_QUERY
paths use their own named-group regexes and were already producing
clean subjects; they are not routed through
_normalize_subject. UnknownDomainGatesemantics are unchanged.versor_condition(F) < 1e-6invariant — no algebra is touched.
Scope limits
- English only. The aux-verb / article lists are English; a future multilingual cognition pack ADR would extend the sets or move them into the language pack itself.
- The PROCEDURE intent's
"How can I VERB ARTICLE NOUN"shape ("How can I correct an error?") is not handled: stripping the verb requires either part-of-speech tagging or a closed list of imperative verbs. Out of scope here. The caseprocedure_correct_035has emptyexpected_surface_containsin the eval anyway, so it does not affect surface_groundedness. - Multi-word noun phrases for DEFINITION / RECALL (e.g.
"artificial intelligence") are preserved as-is. Pack lookup matches on the cleaned phrase; if the pack carries the multi-word lemma, it lifts; if not, it falls through to the universal disclosure. This is the doctrinally correct behaviour.
Cross-References
- ADR-0018 — defines
DialogueIntentand the_RULEStable this ADR post-processes. - ADR-0048 — the consumer whose pack-lookup gap this ADR closes by producing clean lemmas.
- ADR-0046 /
ADR-0047 — the
forward-graph-constraint pipeline that consumes
intent.subjectviagraph_planner.graph_from_intent; cleaner subjects make graph nodes single-lemma rather than noun-phrase, increasing the chance the AdmissibilityRegion's CGA neighbourhood intersects the walk's candidate pool.
Verification
tests/test_intent_subject_extraction.py — 30 tests, all green
tests/test_intent_proposition_graph.py — pre-existing tests still green
tests/test_pack_grounding.py — pre-existing tests still green
tests/test_semantic_realizer_integration.py — pre-existing tests still green
Lanes (all green on this branch):
core test --suite smoke 67 passed
core test --suite cognition 121 passed
core test --suite runtime 19 passed
core eval cognition (pre → post):
intent_accuracy 100.0% → 100.0% (=)
surface_groundedness 46.2% → 61.5% (+15.3 pp)
term_capture_rate 33.3% → 50.0% (+16.7 pp)
versor_closure_rate 100.0% → 100.0% (=)
The non-negotiable field invariant (versor_condition(F) < 1e-6) is
preserved: this ADR touches a typed dataclass field, no algebra.