Tightens the multi_sentence_response lane predicates so OOV
invitations and refusal disclosures can no longer be counted as
articulate capability. Three new metrics partition the case space:
articulate_sentence_rate - >=2 sentences AND grounded in
{pack, teaching}. Real capability.
disclosure_sentence_rate - >=2 sentences AND grounded in
{oov, refusal, none}. Structural
multi-sentence from disclosure templates.
unarticulate_rate - <2 sentences regardless of source.
The three sum to 1.0 (modulo rounding) by construction. The
doctrine-correct headline is now ``articulate_sentence_rate``;
``multi_sentence_rate`` is kept as a continuity metric only.
2 new tests pin: (a) the three-way partition is total and disjoint
(articulate + disclosure + unarticulate == 1.0); (b) OOV/refusal
disclosure surfaces contribute to disclosure_sentence_rate but
never to articulate_sentence_rate.
Live A/B on 21 cases under the new partition:
flag off: articulate=0.0952, disclosure=0.0476, unarticulate=0.8571
flag on : articulate=0.8571, disclosure=0.0476, unarticulate=0.0952
Planner lift is +76pp on articulate. Disclosure stays flat across
the flag (the planner gate correctly leaves disclosure surfaces
alone). The remaining 9.5pp unarticulate flag-on is the genuine
miss list (walkthrough + compound prompts) that the next two
landings will target.
contract.md updated to make articulate_sentence_rate the headline
and to document the partition explicitly.
cognition eval byte-identical: public 100/100/91.7/100.
smoke suite 67/67.
3.8 KiB
Multi-Sentence Response Eval Lane — Contract
Lane: multi_sentence_response
Version: v1
Created: 2026-05-19
Status: Red on creation — measurement substrate for compositional surface.
What this lane measures
Whether ChatRuntime can emit a response that is more than a single
sentence when the prompt structurally calls for elaboration
("Explain X", "Tell me about X", "Describe X", "Walk me through X").
Currently every pack-grounded surface is a single sentence emitted
by _frame_gloss. NARRATIVE and EXAMPLE intents already compose
multi-clause output via teaching chains, so they are tested here too
as the only multi-sentence-capable code path.
Per-case predicates
| Predicate | Definition |
|---|---|
sentence_count_>=_2 |
the substantive surface contains at least 2 terminated sentences (., ?, !) |
each_sentence_>=_4_tokens |
every sentence has ≥ 4 alphabetic tokens (no fragments) |
connective_present |
the surface contains at least one connective (and, because, therefore, which, since, also, furthermore, however, consequently) — only enforced when expects_connective=true |
not_just_provenance_tag |
sentence_count counts BEFORE trailing provenance / trust-boundary tails (pack-grounded (…)., No session evidence yet.) are treated as real sentences |
grounded |
grounding_source ∈ {pack, teaching} |
subject_named |
the prompt's subject lemma appears in the surface |
Scoring rubric
articulate_sentence_rate = cases with >=2 sentences AND grounded in {pack, teaching} / total
disclosure_sentence_rate = cases with >=2 sentences AND grounded in {oov, refusal, none} / total
unarticulate_rate = cases with <2 sentences / total
multi_sentence_rate = cases_with_>=2_sentences / total_cases # continuity metric
non_fragment_rate = cases_where_every_sentence_>=4_tokens / total_cases
connective_present_rate = cases_with_connective / cases_expecting_connective
primed_cases = cases_where_priming_prompts_engaged
primed_multi_sentence_rate = primed_cases_with_>=2_sentences / primed_cases
Doctrine-correct headline: articulate_sentence_rate.
multi_sentence_rate is kept for continuity but is misleading on its own:
OOV teaching-invitation surfaces ("I don't know that yet — can you teach
me?") and refusal disclosures ("I don't know — insufficient grounding
for that yet.") are categorically multi-sentence by template, not by
articulation. They count toward disclosure_sentence_rate, never
articulate_sentence_rate.
The decomposition is total:
articulate + disclosure + unarticulate = 1.0 (modulo rounding).
Priming (warm-path measurement)
A case may carry an optional priming_prompts: [str, ...] array. The
runner runs each priming prompt on the same ChatRuntime instance
before the scored prompt, discards their responses, and then measures
the scored prompt. This isolates code paths that engage only on the
warm vault/pack/teaching path (e.g. the discourse planner hook at
chat/runtime.py) from cold-start one-shot paths.
primed_multi_sentence_rate reports only on primed cases, so cold
cases never inflate or depress it. The aggregate
multi_sentence_rate includes both.
Doctrine constraints
- The trailing provenance / trust-boundary tail is structural, not a real sentence — predicate logic strips it before counting.
- Dotted semantic-domain atoms (
cognition.truth,logos.core) are not sentence boundaries by themselves. A terminal mark counts as a boundary only when it is followed by a new uppercase/digit sentence opener or the end of the substantive surface. - No LLM judge. Pure structural counting.
- Red-on-creation expected: only NARRATIVE / EXAMPLE / cross-pack /
composed_surface code paths can possibly satisfy
sentence_count_>=_2today.