Closes the last unarticulate cases on the multi_sentence_response
lane. Two complementary changes:
1. ``generate/discourse_planner.py``
* ``ResponseMode.WALKTHROUGH`` budget lifted from (1, 1) to
(1, 4): 1 anchor + up to 3 hops along the teaching-chain graph,
final hop becomes CLOSURE.
* New ``_plan_walkthrough`` selector walks (subject, *, object) →
(object, *, *) starting from the anchor; cycle-safe via the
existing used-fact set; bounded by ``_WALKTHROUGH_MAX_HOPS=3``.
* New ``_plan_walkthrough_fallback`` — when no teaching chain is
rooted on the anchor, emit ANCHOR + (SUPPORT) rather than
fabricating walk steps. Plan retains ``mode=WALKTHROUGH`` so
callers detect "attempted walkthrough, degraded honestly".
2. ``generate/intent.py``
* New classifier rule: ``^walk\s+(?:me\s+)?through\s+`` →
``IntentTag.DEFINITION``. Same orthogonality discipline as the
``Explain X`` rule: ``ResponseMode.WALKTHROUGH`` carries the
walk depth on its own axis.
13 new tests pin: walk shape (ANCHOR + RELATION* + CLOSURE), the
walk invariant (each teaching hop's subject = prior hop's object),
the 4-move cap, the fallback shape on absent chains, fallback mode
retention, cycle-safety against (A→B→A) cycles, and determinism.
Lane re-measurement (24 cases, multi_sentence_response public/v1):
flag off: articulate=0.0833, disclosure=0.1667, unarticulate=0.7500
flag on : articulate=1.0000, disclosure=0.0000, unarticulate=0.0000
The two previously-unarticulate WALKTHROUGH cases ("Walk me through
inference.", "Walk me through recall.") now engage the planner and
render as deterministic teaching-chain walks:
"Inference is a conclusion drawn from premises by reasoning.
Inference requires evidence."
"Recall is to retrieve a stored state from memory.
Recall reveals memory."
Each surface is grounded entirely in pack glosses and reviewed
teaching chains — no fabricated walk steps.
Critical gates all green:
* flag off cognition byte-identical:
public 100/100/91.7/100, holdout 100/100/83.3/100
* smoke suite 67/67
* 91/91 planner tests pass (contract / behavior / compound / helper
/ render / walkthrough)
The 0.875 connective_present_rate remaining flag-on (3 cases without
expected connectives) is the only gap left, and it's now a render-
template question rather than a planner gap.
Adds compound-intent decomposition for prompts that ask multiple
things in one turn ("What is X, and why does it matter?",
"Explain X, but how does it work?", "What is X, and what is Y?").
Three landings in one PR (rule says additive; the three pieces
are inseparable for the runtime hook to do anything useful):
1. generate/intent.py
* New ``CompoundIntent`` frozen dataclass — ordered tuple of
``DialogueIntent`` parts + raw_text + ``.primary`` back-compat
accessor + ``.is_compound()`` helper.
* New ``classify_compound_intent(prompt)`` sibling to
``classify_intent``. Pure, deterministic, byte-stable. Splits
on closed connector list (``,\s+(and|but|because|while)\s+``);
anaphoric tails ("why does it matter") get the prior part's
subject substituted ("why does truth matter") then are
classified independently.
* ``classify_intent`` return shape is untouched — every existing
caller still receives ``DialogueIntent``.
* No new ``IntentTag`` introduced. v1 semantic approximation:
"why does X matter" routes to ``CAUSE(X)``; "matter" means
causal/relevance support, not metaphysical importance.
2. generate/discourse_planner.py
* New ``plan_compound_discourse(compound, mode, bundles)`` —
concatenates per-part sub-plans in source order with a
``TRANSITION`` bridge (fact=None) between consecutive parts.
No cross-part re-sorting.
* New private kw-only ``_exclude_facts`` parameter on
``plan_discourse`` so subsequent sub-plans can avoid emitting
the same facts the prior sub-plans already used (prevents
"Truth is X. Truth is X." duplicates on shared-subject
compounds). Public signature ``(intent, mode, bundle)`` is
unchanged.
3. chat/runtime.py
* Helper ``_maybe_apply_discourse_planner`` now consults the
compound classifier first. When the prompt is multi-part it
builds per-part bundles and calls ``plan_compound_discourse``;
otherwise it follows the previous single-intent path.
* Compound bypass: when upstream tagged the surface ``oov`` /
``none`` because the flat classifier saw a polluted subject
(e.g. ``"truth, and why does it matter"``), but the compound
decomposition reveals a pack-resident primary subject, the
planner engages on the decomposed parts. This narrowly widens
the gate exclusively for compound prompts with substrate.
* BRIEF mode upgrades to EXPLAIN for compound prompts —
single-anchor sub-plans on shared subjects would emit duplicate
anchor sentences in BRIEF.
* Return shape widened to ``tuple[str, str] | None`` —
``(rendered_surface, new_source_tag)``. ``new_source_tag`` is
``"teaching"`` when the plan uses any teaching fact, else
``"pack"`` — so downstream labels reflect actual provenance
even on the compound bypass. Both cold and warm call sites
updated to apply both fields.
24 new tests pin: compound decomposition correctness, source-order
preservation across sub-plans, anaphoric-followup rewriting,
deterministic byte-stable plans, no new IntentTag introduced,
fact-dedup across sub-plans, compound-bypass engagement, and
source-tag correction on planner-engaged surfaces.
Lane re-measurement after 3 compound cases added to cases.jsonl
(24 total cases):
flag off: articulate=0.0833, disclosure=0.1667, unarticulate=0.7500
flag on : articulate=0.9167, disclosure=0.0000, unarticulate=0.0833
Note: disclosure flag-on dropped to 0.0 because the source-tag
correction now correctly labels compound-bypass surfaces as
``pack/teaching`` instead of letting the upstream ``oov`` label
inflate disclosure. The two remaining unarticulate cases flag-on
are the walkthrough prompts targeted by the next landing.
Critical gates all green:
* flag off cognition byte-identical: public 100/100/91.7/100
* smoke suite 67/67
* 32/32 planner tests pass (helper + render + compound)
* 18/18 compound classifier tests pass
Extends ``generate/intent.py:_RULES`` with three new expository
patterns so the upstream subject-extraction gap that the dedup
revealed is closed:
* ``^explain\s+`` → DEFINITION
* ``^(write|compose|draft) (a )?(short|brief)?
paragraph (about|on)\s+`` → DEFINITION
* ``^paragraph (about|on)\s+`` → DEFINITION
Rules placed AFTER the NARRATIVE family so ``Tell me about X`` and
``Describe X`` continue to route to NARRATIVE. Subject extraction
re-uses ``_normalize_subject`` so articles and trailing punctuation
are stripped: ``Explain the parent.`` → subject ``parent``.
``ResponseMode`` is untouched and remains orthogonal: the same prompts
still classify as ``EXPLAIN`` / ``PARAGRAPH`` independently.
20 new tests pin: each rule's expected subject, response-mode
preservation, NARRATIVE/EXAMPLE/existing-DEFINITION rules unchanged.
Lane re-measurement (multi_sentence_response, 21 cases):
flag off: multi=0.1429, primed_multi=0.0000, conn=0.5385, grounded=0.8571
flag on : multi=0.9048, primed_multi=1.0000, conn=0.8462, grounded=0.8571
Combined lift over the original (pre-wiring) baseline:
* multi_sentence_rate: +70pp on the substantive predicate
* primed_multi_sentence_rate: +50pp (0.5 → 1.0 post-classifier)
* connective_present_rate: +74pp (0.10 → 0.85)
* grounded_rate: +39pp (0.47 → 0.86)
Cognition eval byte-identical: public 100/100/91.7/100, holdout
100/100/83.3/100 — these prompts aren't in cognition cases, and the
new rules don't perturb any rule that fires for cognition prompts.
Conversational thread coherence unchanged.
docs/evals/discourse_runtime_baseline_2026-05-19.md updated with the
full delta table; the planner is now load-bearing across the warm
and cold pack/teaching paths and the lane measures real capability
rather than punctuation artifacts.
Step 2 of the discourse-planner sequencing: add the presentation-depth
axis ResponseMode (brief / explain / walkthrough / paragraph / example)
as a sibling to IntentTag in generate/intent.py, with a deterministic
rule-based classify_response_mode classifier next to classify_intent.
ResponseMode previously lived in generate/discourse_planner.py; moved
to generate/intent.py so the dependency is one-way (planner imports
from intent, never reverse). discourse_planner.py now re-exports.
Additive-only invariant preserved:
* DialogueIntent fields unchanged (tag/subject/secondary_subject/
relation/frame). No equality breakage anywhere downstream.
* classify_intent branches untouched.
* Callers compose (classify_intent(t), classify_response_mode(t))
rather than threading mode through DialogueIntent.
41 new tests pin: placement (canonical home + re-export identity),
classifier behavior (parametrized over 25 prompts), priority ordering
(paragraph > explain, walkthrough > explain), purity (no clock/env/
filesystem), classify_intent invariance (definition / narrative /
example / cause / verification representative cases), and orthogonality
(intent and mode compose, neither shadows the other).
Verification:
* 96/96 existing intent tests pass.
* 69/69 new contract + characterization + classifier tests pass.
* smoke suite 67/67.
* cognition eval byte-identical: public 100/100/91.7/100,
holdout 100/100/83.3/100.
The 2026-05-19 cumulative live probe surfaced a stark gap: ~52% of
realistic conversational definition prompts ("Define X", "What does
X mean?", "What is to V?", "How does X work?", "What causes X?")
returned ``grounding_source="none"`` *even though every subject
lemma was pack-resident* across the 9 mounted English packs.
Root cause: the bottleneck was intent classification + subject
extraction, not lexicon coverage. Five patterns either had no rule
or routed to an intent the runtime dispatcher couldn't handle. The
fluency assessment at
``/Users/kaizenpro/.codex/worktrees/6533/core/notes/fluency_assessment_2026-05-19.md``
named these as Root Cause #1 ("public chat path does not use the
cognitive spine") and Root Cause #3 ("proposition graphs are too
thin"). This commit closes the surface-level half of that gap;
the deeper answer-plan layer (gloss propositions, P3 in the
assessment) is the next step.
Patterns fixed in ``generate/intent.py``:
1. ``Define X`` — added ``^define\s+`` rule mapping to
DEFINITION (placed after ``^what is/are``
so multi-word DEFINITION patterns still
prefer the question form).
2. ``What does X mean?`` — was matching TRANSITIVE_QUERY with
relation=``mean``. Now re-routes to
DEFINITION inside ``classify_intent`` so
``pack_grounded_surface`` fires on X.
Other transitive relations (precede,
ground, etc.) remain TRANSITIVE_QUERY.
3. ``What is to V?`` — added infinitive-marker strip to
``_normalize_subject`` for DEFINITION /
RECALL. ``to`` is gated on intent tag so
it never strips a transfer preposition
from CAUSE / VERIFICATION.
4. ``How does X work?`` — added ``_HOW_DOES_X_RE`` (third-person
mechanistic-cause). Distinct from the
first-person PROCEDURE rule ("How do I
X?"). Verbs: work / function / operate /
happen / exist / behave / act / emerge.
5. ``What causes X?`` — added causative-verb rule (causes /
triggers / enables / prevents / drives /
produces / induces / yields) routing to
CAUSE with X as subject.
Deliberate NON-fix: I considered adding a ``pack_grounded_surface``
fallback in the CAUSE / VERIFICATION dispatcher when no teaching
chain matches the subject. Reverted on review — that masks the
"would_have_grounded" discovery-candidate signal the teaching
pipeline uses to identify teaching-content gaps (see
``tests/test_discovery_candidates``). CAUSE on a pack-resident
lemma without a teaching chain stays ``grounding_source=='none'``
so the discovery layer can log the gap honestly.
``chat/pack_grounding.py``:
Extended ``_CORRECTION_TOPIC_STOPWORDS`` to include polarity
markers (no / yes / maybe / perhaps / hardly / indeed / surely /
definitely). Without this the CORRECTION composer would
short-circuit on ``no`` from "No, my parent disagrees" and miss
the topical lemma ``parent``.
Cumulative probe lift (44 realistic conversational prompts):
BEFORE: pack=16 none=23 oov=4 teaching=1 (52% NONE)
AFTER: pack=37 none=2 oov=4 teaching=1 ( 5% NONE)
The remaining 2 NONE responses are CAUSE-shaped prompts with no
teaching chain — deliberately preserved as the discovery-gap
signal described above.
Tests: tests/test_intent_classification_extensions.py — 23 new
tests covering each pattern + the lift invariant.
Verification:
Cognition eval byte-identical on both splits (100/100/91.7/100
public, 100/100/83.3/100 holdout).
All 111 intent-affected tests green:
test_intent_classification_extensions.py (23)
test_intent_proposition_graph.py / test_intent_ratifier.py /
test_intent_subject_extraction.py / test_narrative_example_intents.py
test_procedure_surface.py
test_correction_topic_lemma.py
test_cross_pack_grounding.py (including the polarity-stopword fix)
test_discovery_candidates.py
test_contemplation_wiring.py
test_en_core_polarity_v1_pack.py
Two new intent shapes + composers turn the runtime's corpus
density into operator-visible articulation. Both consult the
cross-corpus aggregator from ADR-0064; no new ratification needed.
P3.3 — chat/narrative_surface.py + IntentTag.NARRATIVE.
Classifier patterns (registered BEFORE generic DEFINITION):
^tell\s+me\s+about\s+
^describe\s+
^what\s+(?:can|do)\s+you\s+(?:say|know)\s+about\s+
narrative_grounded_surface(subject, max_clauses=4) walks every
reviewed chain rooted on subject across all registered teaching
corpora. Dedupes by (connective, object) — cause + verification
carrying the same predicate emit one clause, not two. Sorts by
(intent, connective, object) for replay stability.
Surface format:
"{X} — narrative-grounded ({corpus_ids}): {dX1}; {dX2}.
{X} {conn1} {O1} ({dO1}); {X} {conn2} {O2} ({dO2}).
No session evidence yet."
Cross-corpus subjects (e.g. mother in relations_v2) emit
narrative-grounded (relations_chains_v2) tag; cognition subjects
emit cognition_chains_v1 tag. Multi-corpus subjects (when
applicable) emit composite "corpus_a + corpus_b" tag.
P3.4 — chat/example_surface.py + IntentTag.EXAMPLE.
Classifier patterns:
^(?:give|show)\s+(?:me\s+)?an?\s+(?:example|instance)\s+of\s+
^example\s+of\s+
example_grounded_surface(object_lemma, max_examples=3) walks chains
where the lemma is the OBJECT — inverts the typical subject-keyed
access pattern. Dedupes by subject; sorts by (intent, subject,
connective).
Surface format:
"{X} — example-grounded ({corpus_ids}): {dX1}.
Example: {subj1} {conn1} {X}; {subj2} {conn2} {X}.
No session evidence yet."
Cross-cutting:
- Both intents added to _OOV_INTENT_TAGS — fall through to OOV
invitation when subject is unknown (Phase 2 gradient discipline).
- Both tagged grounding_source="teaching" (same provenance tier
as the existing teaching_grounded_surface).
- No prose generation, no new mutation surface.
Live verification:
> Tell me about truth.
[teaching] truth — narrative-grounded (cognition_chains_v1):
cognition.truth; logos.core. truth grounds knowledge
(cognition.knowledge); truth requires evidence (cognition.evidence).
> Give me an example of knowledge.
[teaching] knowledge — example-grounded (cognition_chains_v1):
cognition.knowledge. Example: truth grounds knowledge;
understanding requires knowledge; evidence grounds knowledge.
> Tell me about mother.
[teaching] mother — narrative-grounded (relations_chains_v2):
kinship.parent.female. mother precedes daughter (kinship.child.female).
> Describe photosynthesis.
[oov] I haven't learned 'photosynthesis' yet (intent: narrative). ...
ADR-0066 (this commit completes the ADR). 30 new tests passed.
Full lane: 2067 passed, 2 skipped, 0 failed in 2:32.
Add a deterministic, pack-agnostic post-processor in `generate/intent.py`
that runs after the `_RULES` table fires:
- DEFINITION / RECALL / PROCEDURE: strip trailing punctuation + leading
articles; preserve multi-word noun phrases
- CAUSE / VERIFICATION: additionally strip leading aux verbs; return
the head noun
Closed-set frozen sets (`_ARTICLES`, `_AUX_VERBS`) make the transform
inspectable. No pack load, no algebra change — touches only
`DialogueIntent.subject`.
Cognition eval (13-case public split):
surface_groundedness 46.2% → 61.5% (+15.3 pp)
term_capture_rate 33.3% → 50.0% (+16.7 pp)
intent_accuracy 100.0% (=)
versor_closure_rate 100.0% (=)
Two cases lift through the ADR-0048 pack path
(definition_procedure_023, definition_relation_026 — both
"What is a X?" → subject=X via article stripping). CAUSE / VERIFICATION
subjects are now clean head nouns, foundational for future COMPARISON
pack path / teaching-store inference.
Tests: tests/test_intent_subject_extraction.py (30 tests).
Lanes green: smoke (67), cognition (121), runtime (19), algebra (132),
teaching (17), packs (6).
Closes the mixed_relation_* (multi-step-reasoning) and composed_predicate
(compositionality) residuals with a single new operator plus a small
intent-classifier loosening. Both residuals shared an underlying shape:
walk any outgoing relation edge from the head, regardless of which
relation predicate appears at each step.
generate/operators.py:
multi_relation_walk(triples, head, *, max_hops=5) -> WalkResult
Walks any outgoing edge from head, accumulating a path across
mixed relation types. Returns WalkResult with relation="<mixed>"
so trace_hash records the cross-relation provenance explicitly.
Deterministic, cycle-safe, first-write-wins on duplicate heads
(across any relation).
generate/intent.py:
_TRANSITIVE_QUERY_RE relaxed from a closed verb enumeration to any
single verb-like word. "What does X (any verb)?" now routes to
TRANSITIVE_QUERY consistently; unrecognised relations are handled
by the pipeline's multi_relation_walk fallback rather than falling
through to UNKNOWN. Verified no regression on 30 intent / realizer
tests.
core/cognition/pipeline.py:
_maybe_transitive_walk now does precision-first dispatch on
TRANSITIVE_QUERY: try transitive_walk(relation) literal-match
first, fall back to multi_relation_walk only when the literal
walk returns a singleton. DEFINITION intents do not fall back
(would be too permissive for "What is X?").
tests/test_inference_operators.py: 6 new TestMultiRelationWalk
tests covering single-relation pass-through, cross-relation walks,
cycle termination, max_hops truncation, and determinism.
Phase 3 v1 re-score:
lane split v1 v2 v3 (now)
inference-closure public 0.0 1.0 1.0 pass
inference-closure holdouts 0.0 1.0 1.0 pass
multi-step-reasoning public 0.0 0.73 1.0 pass
multi-step-reasoning holdouts 0.0 0.80 1.0 pass
compositionality public 0.06 0.31 0.69 pass
compositionality holdouts 0.0 0.30 0.80 pass
cross-domain-transfer public 0.0 1.0 1.0 pass
cross-domain-transfer holdouts 0.0 1.0 1.0 pass
introspection public 0.0 1.0 1.0 pass
introspection holdouts 0.0 1.0 1.0 pass
PHASE 3 v1 IS COMPLETE: 10 of 10 splits passing. Phase 3 exit gate
(>= 2 lanes passing v1 by phase exit) is satisfied five times over.
Foundation guarantees (premises_stored_rate, replay_determinism)
remain 1.0 across all lanes. Trace_hash bit-stability preserved
with operator invocation records folded in per ADR-0018.
Compositionality public at 0.69 / holdouts at 0.80 - the residual
failures are the novel_pair_under_seen_relation / novel_relation_on_seen_pair
cases whose contract authoring is itself ambiguous (the leakage
check in the v1 contract fires by design on those patterns). Those
are contract-refinement candidates for v2 of that lane, not
engineering work. Overall_pass threshold (>= 0.50) is comfortably
met on both splits.
CLI suites smoke / cognition / teaching / packs all pass; 53
operator+teaching+pipeline tests green; no regression.
Implements the Phase 3 v2 inference-depth bundle per ADR-0018:
typed deterministic operators over CORE's typed state. Closes the
inference-closure / multi-step-reasoning / cross-domain-transfer
v1 gaps; partial close on compositionality.
New modules:
teaching/relation_parse.py - parse_triple(correction_text) lifts
a correction utterance into a typed (head, relation, tail) over
the en_core_cognition_v1 relation vocabulary. Pure regex,
deterministic, no learned classifier.
generate/operators.py - transitive_walk(triples, head, relation,
*, max_hops=5) walks single-relation chains. path_recall walks
a relation-chain tuple (e.g. ("is", "precedes")). Both bounded,
cycle-safe, case-insensitive, first-write-wins on duplicates.
Schema extensions:
teaching.store.PackMutationProposal gains optional triple field,
populated by TeachingStore.add via parse_triple. Plus new
TeachingStore.triples() helper returning all parsed triples.
generate.intent.IntentTag gains TRANSITIVE_QUERY plus a relation
field on DialogueIntent. New regex rules for "What does X R?"
and "Where does X belong?" forms with relation normalisation.
core.cognition.result.CognitiveTurnResult gains operator_invocation
field (deterministic serialisation of any operator that ran).
core.cognition.trace.compute_trace_hash gains operator_invocation
kwarg; trace_hash_from_result threads it through. Operator
invocation is now load-bearing for replay equality.
Pipeline wiring:
CognitiveTurnPipeline.run dispatches transitive_walk after
runtime.chat() when the intent is TRANSITIVE_QUERY (with the
parsed relation) or DEFINITION (implicit "is"). Non-trivial walks
fold the chain endpoint into surface and articulation_surface.
Verification:
tests/test_inference_operators.py - 27 unit tests covering
parser, transitive_walk (cycles, max_hops, case-insensitivity,
determinism, first-write-wins), path_recall, and WalkResult shape.
Re-score on Phase 3 v1 case sets:
lane split v1 after bundle
inference-closure public/v1 0.0 1.0 pass
inference-closure holdouts/v1 0.0 1.0 pass
multi-step-reasoning public/v1 0.0 0.7333 pass
multi-step-reasoning holdouts/v1 0.0 0.8 pass
cross-domain-transfer public/v1 0.0 1.0 pass
cross-domain-transfer holdouts/v1 0.0 1.0 pass
compositionality public/v1 0.0625 0.3125 partial
compositionality holdouts/v1 0.0 0.3 partial
Six of eight splits now pass v1. Foundation guarantees
(premises_stored, replay_determinism) remain 1.0 across all lanes.
Trace_hash determinism preserved (operator records fold in
deterministically).
Residuals (filed as Phase 3 v2 follow-up):
- multi-step-reasoning mixed_relation_3/4 patterns need path_recall
wired into the pipeline for multi-relation probes; the operator
exists but the pipeline only invokes transitive_walk today.
- compositionality novel-combination patterns need a genuinely
new operator shape (composed_relation_walk) - the literal
transitive walk does not synthesise novel pairs by construction.
CLI suites smoke / cognition / teaching pass; no regression. 47
pipeline + teaching + operator tests all green.