Adds a typed legality check that catches a narrow class of incoherent
finite-predicate surfaces before they ship. Scope is deliberately
narrow:
- generate/articulation_legality.py:
- SlotKind enum {VERB, NON_VERB, UNKNOWN}
- ArticulationLegality enum {LEGAL, ILLEGAL_NON_VERB_FINITE_PREDICATE}
- classify_predicate_slot_kind() — token allowlists for known verbs
and known non-verb nouns
- validate_finite_predicate_legality() — fails on negated +
NON_VERB; fail-open on UNKNOWN to preserve canary behavior
- generate/templates.py:
- _inflect_predicate: copular-aware negation
("is X" -> "is not X" instead of the default "does not be X")
- render_step: invokes the legality validator; returns
"I cannot realize that proposition coherently yet." when an
illegal shape is detected
The check is upstream of register / anchor-lens transforms (presentation
+ substantive axes both downstream of the realizer); no interaction
with R6 / ADR-0073 layering.
Tests pin:
- NON_VERB + negated -> ILLEGAL_NON_VERB_FINITE_PREDICATE
- UNKNOWN + negated -> LEGAL (fail-open preserved)
- render_step returns the disclosure string when illegal detected
- render_step still produces the fall-through surface on UNKNOWN
Validation:
- Cognition eval byte-identical (100/100/91.7/100)
- 370 realizer / lens / register / pack / lane tests pass
- anchor-lens-tour + register-tour both green
ADR-0073c shipped he_chesed_v1, he_shalom_v1, he_tzedek_v1 with lossy
EN-collapse alignment edges (he-021 → en-collapse-love @ 0.63, etc.)
but the synthetic en-collapse-* targets didn't exist in any mounted
lexicon. Result: the three lenses ratified but stayed dormant — the
runtime OOV gate fired on "What is love?" / "What is peace?" /
"What is justice?" before the lens engagement path got a chance.
This commit adds a minimal pack whose lexicon carries exactly those
three synthetic anchors:
en-collapse-love lemma="love" domain=collapse_anchor.love
en-collapse-peace lemma="peace" domain=collapse_anchor.peace
en-collapse-justice lemma="justice" domain=collapse_anchor.justice
Mounted last in DEFAULT_RESOLVABLE_PACK_IDS — cognition / relations
packs win first-match on any future collision. No real content pack
currently carries these lemmas (grep-confirmed) so the mount adds no
collision risk.
The pack-grounded surface for "What is love?" advertises its nature
honestly via the pack id (en_collapse_anchors_v1) and the domain
string (collapse_anchor.love) — the surface is intentionally minimal;
the substantive content arrives via the lens annotation
[lens(he_chesed_v1):covenant-love] / [lens(he_shalom_v1):wholeness-peace] /
[lens(he_tzedek_v1):right-order].
chat/pack_grounding.py:_en_lemma_to_entry_id() now reads both
en_core_cognition_v1 and en_collapse_anchors_v1, with cognition
winning on lemma collision.
New test file tests/test_en_collapse_anchors_v1_pack.py pins:
- each anchor lemma resolves to its synthetic entry_id
- collapse pack mounted last (precedence guarantee)
- each of the three lenses engages on its target English prompt
- baseline surface (no lens) still advertises anchor nature
Validation:
- Cognition eval byte-identical (100/100/91.7/100)
- 160 lens/pack/resolver tests pass + 8 new
- anchor-lens-tour green
- register-tour green
* feat(packs): ethics ×3, anchor-lens ×3, relations-v3, register ×2
Group 1 — Ethics domain packs (ADR-0044 sibling)
legal_ethics_v1: 6 commitments covering no-legal-advice, no-outcome-prediction,
jurisdiction-disclosure, privilege-disclosure, conflict-disclosure, refer-to-counsel
engineering_ethics_v1: 6 commitments covering safety-primacy, standard-disclosure,
no-sign-off, uncertainty-surface, public-welfare-priority, refer-to-pe
research_ethics_v1: 6 commitments covering no-fabrication, no-plagiarism,
irb-disclosure, conflict-of-interest-disclosure, data-integrity, reproducibility-hedge
ratify_ethics_pack.py: PACK_IDS extended with all three new ids
Group 2 — Anchor lens packs (grc cognition atoms, ADR-0073c)
grc_sophia_v1: atom logos.sophia.wisdom via grc-core-cog-008 (cross_lang.logos.sophia
edge weight 0.88); cognitive mode wisdom-practical
grc_epignosis_v1: atom logos.epignosis.experiential via grc-core-cog-007 (weight 0.78,
en_collapse edge documented); cognitive mode experiential-knowledge
grc_episteme_v1: atom logos.episteme.systematic via grc-core-cog-021 (weight 0.72,
en_collapse edge documented); cognitive mode systematic-knowledge
ratify_anchor_lens_packs.py: LENS_IDS extended with all three new ids
Group 3 — en_core_relations_v3 (social + part-whole extension of v2 kinship)
7 new lemmas: colleague, mentor, neighbor, component, member, instance, peer
manifest.json: new pack with checksum placeholder (operator must recompute after
ratify run — same pattern as other packs)
Group 4 — Register packs formal_v1 + socratic_v1
formal_v1: standard depth, drop_provenance_tag=true + drop_articles=true;
no markers; ratifies under known_key_overrides_invariant_grounding
socratic_v1: pedagogical depth, append_semantic_domain_clause=true; markers scaffold
question-and-response rhythm (openings×4, transitions×3, closings×4)
ratify_register_packs.py: REGISTER_IDS extended with formal_v1, socratic_v1
* fix(anchor_lens): loader v1/v2 dual-schema compat — resolves blocker 1 of #48
Refactor AnchorLens to use v2 schema fields and normalize legacy fields. Update validation and loading functions for improved clarity and functionality.
* fix(ratify): restore default_unanchored_v1 + full LENS_IDS (17) — resolves blocker 2 of #48
Added new lens IDs for the he substrate and updated the order of lens IDs.
* chore(packs): migrate 8 legacy anchor-lens packs to v2 schema [1/8 default_unanchored_v1]
Updated the default unanchored lens JSON structure with new fields and modified descriptions.
* chore(packs): migrate grc_logos_v1 to v2 schema [2/8]
Updated the description and added new fields for cognitive mode, atom, and source entry ID.
* chore(packs): migrate grc_aletheia_v1 to v2 schema [3/8]
Updated the description and added new fields related to cognitive mode and atom.
* chore(packs): migrate grc_zoe_v1 to v2 schema [4/8]
Updated the description and added new fields for cognitive mode, atom, and source entry ID.
* chore(packs): migrate grc_arche_v1 to v2 schema [5/8]
Updated the description and added new fields for cognitive mode, atom, and source entry ID.
* chore(packs): migrate he_logos_v1 to v2 schema [6/8]
Updated the Hebrew-substrate anchor lens JSON structure with new fields and modified descriptions.
* chore(packs): migrate he_dabar_v1 to v2 schema [7/8]
Updated the description and added new fields for cognitive mode and source entry.
* chore(packs): migrate he_chayyim_v1 to v2 schema [8/8] — resolves blocker 3 of #48
Updated the description and added new fields for cognitive mode and source entry ID.
* fix(anchor-lens): complete v1→v2 migration + back-compat shims
Resolves blockers B4/B5/B6/B7 left by the initial round-2 schema rewrite:
B4: restore UNANCHORED module constant, is_null_lens() alias,
and verify_anchor_lens_seal() (all were dropped from loader.py;
chat/pack_grounding.py and several tests still imported them).
AnchorLens.unanchored() returns the in-memory sentinel with
lens_id='__unanchored__' as before (distinct from disk pack).
B5: add v1 attribute properties on AnchorLens (primary_substrate,
semantic_domain_preferences, cognitive_mode_label) so consumers
not yet on v2 (chat/pack_grounding.py engagement reads, several
tests) continue to work via read-only views over the canonical
v2 fields. Zero changes needed to chat/pack_grounding.py.
B6: re-derive source_entry_id by atom-in-lexicon lookup for 6 of 8
legacy packs that were positionally mis-mapped during migration.
B7: fix two new-pack atoms that didn't exist in the lexicon
(logos.episteme.systematic -> logos.episteme.systematic_knowledge,
logos.epignosis.experiential -> logos.epignosis.knowledge).
Loader hardening (recovered from v1 rewrite):
- _validate_lens_id_for_fs: reject path-traversal / slash / empty
- companion-SHA mismatch check in load_anchor_lens when require_ratified
- atom must be non-empty when substrate != 'none'
- available_anchor_lens_packs returns summary dicts (was list[str])
Ratify script special-cases substrate='none' so the null sentinel
default_unanchored_v1 keeps its self-seal (ADR-0073b invariant).
Test suite migrated to v2 schema: dropped obsolete list-shape gates
(duplicates, too-many-preferences — v2 has scalar atom), updated error
match strings, added a v1->v2 normalisation back-compat test.
All 11 round-2 packs ratified. 102/102 anchor-lens tests pass.
Cognition eval byte-identical (100/100/91.7/100).
anchor-lens-tour + register-tour both green.
Wires observational telemetry on the composer-vs-graph atom-set
relationship. Phase 1 is strictly observational: no enforcement,
no surface mutation, no grounding-source change, no trace-hash impact.
New telemetry fields on TurnEvent + ChatResponse:
composer_graph_atom_status ∈ {equivalent, divergent,
graph_unconstrained,
composer_no_atoms,
not_applicable, ""}
composer_atom_set_hash SHA-256 over sorted unique atoms
graph_atom_set_hash SHA-256 over sorted unique atoms
composer_graph_atom_overlap_count int
Composer atoms come from existing pack candidate metadata
(pack_semantic_domains channel through _maybe_pack_grounded_surface).
Graph atoms come from build_graph_from_input + resolve_lemma on
node.subject/predicate/obj — no prose parsing. When a grounded
composer path lacks explicit atom provenance, status is
'composer_no_atoms'.
New pure helper:
chat/atom_equivalence.py — normalize_atoms, hash_atoms,
atoms_for_graph_nodes, compare_atom_sets
Tests (tests/test_composer_graph_atom_equivalence.py):
- Pack DEFINITION path produces observable equivalence
- Divergent atom sets produce distinct hashes
- Register invariance: atom hashes + status identical across
{neutral, terse, convivial}; trace_hash also constant (R5 axis)
- Anchor lens engaged case still ASCII-only on surface
- No prose-parsing helper symbols introduced in runtime.py
(extract_candidate_surface_lemmas, surface_lemma,
parse_surface_atoms) — enforces Phase 1 boundary
Performance note: build_graph_from_input now runs on every warm
English turn (previously only when forward_graph_constraint=True).
Phase 1 accepts this cost to make the telemetry universally
available; Phase 2+ can introduce a feature flag if needed.
Validation:
- Cognition eval byte-identical: 100/100/91.7/100
- Full lane: 2864 passed, 3 skipped, 0 failed (+5 over baseline)
- Targeted lane: 72 passed in tests/test_{graph_constraint,
pack_grounding,register_tour_demo,anchor_lens_tour_demo,
orthogonality_tour_demo,realizer_guard_holdout,
composer_graph_atom_equivalence}.py
R5 (ADR-0072) shipped the register *machinery*; ADR-0074's orthogonality
tour proved the axis was decoratively orthogonal to anchor-lens but
inspection of the cognition-eval surfaces revealed two structural gaps:
* On pack-grounded DEFINITION/RECALL/COMPARISON composers, the only
realizer override any register consumed was `disclosure_domain_count`
— which only fires on the no-gloss disclosure path. Under terse_v1,
every gloss-DEFINITION cell was byte-identical to default_neutral_v1.
* The register-tour's `surfaces_vary_at_least_once` gate could be
satisfied by convivial's decorative wrapper alone, masking that
regression in CI.
R6 closes both:
Layering separation (the load-bearing fix):
* New TurnEvent/ChatResponse field `register_canonical_surface` carries
the composer output BEFORE any register transformation. The pipeline
hashes this field for `trace_hash`, preserving R5's invariant that
per-prompt trace_hash is CONSTANT across registers even while
substantive transforms produce visibly different surfaces.
Substantive transforms (`chat/register_substantive.py`):
* terse_v1 gains 3 bool knobs: `drop_provenance_tag`, `compress_gloss`,
`drop_articles` — all pure regex transforms on the canonical surface.
* convivial_v1 gains `append_semantic_domain_clause` — appends a single
bounded "Related: <atom>." clause using the lemma's pack atoms.
* default_neutral_v1 leaves overrides empty; substantive transform is
byte-identical no-op (preserves `byte_identity_null_lift`).
* C1 (ADR-0075) safety preserved: drop_articles refuses to drop
articles following `not` (avoids R3 violations); no knob combination
trips R2/R3.
Strengthened tour gate (`evals/register_tour/run_tour.py`):
* Replaces `surfaces_vary_at_least_once` with two falsifiable claims:
- `terse_substantively_differs_from_neutral_on_pack_grounded_definition`
- `convivial_substantively_differs_from_neutral_on_pack_grounded_definition`
Both restrict to DEFINITION+pack-grounded cells and require
difference beyond whitespace/punctuation.
* New claim `register_canonical_surfaces_identical` directly proves
the layering separation.
* Preserves R5's `all_grounding_sources_identical` +
`all_trace_hashes_identical`.
Pack ratification:
* Loader widened to accept `bool` for closed-set R6 keys
(drop_provenance_tag / compress_gloss / drop_articles /
append_semantic_domain_clause).
* `_KNOWN_OVERRIDE_KEYS` ratify gate extended with same.
* terse_v1 + convivial_v1 reratified with new knobs; companion
mastery reports re-sealed. default_neutral_v1 unchanged.
Invariants pinned:
* `invariant_register_canonical_surface_constant_across_registers` (new)
* `invariant_terse_substantively_distinct_from_neutral` (new)
* `invariant_convivial_substantively_distinct_from_neutral` (new)
* `invariant_realizer_no_illegal_articulation` (C1, preserved)
* `invariant_realizer_guard_byte_identity_on_currently_passing_cases`
(C1, preserved)
Verification:
* `core eval cognition`: 100.0% / 91.7% / 100.0% / 100.0% — byte-
identical under default_neutral_v1.
* `core demo register-tour`: all 5 claims green, exit 0.
* `core demo anchor-lens-tour`: green (no anchor-lens code touched).
* `core demo orthogonality-tour`: green (5/5 claims).
* Full lane: 2858 passed, 1 pre-existing failure
(test_all_preamble_explains_combined_run, carried forward
unchanged from main). 56 new R6 tests across three files.
C1 coherence floor: a deterministic verifier that runs on every
candidate surface produced by the truth path, before assignment to
ChatResponse.surface. Rejects illegal articulations and routes them
to a bounded disclosure string — admission control with a
deterministic fallback, not normalization.
Active rules (R1 deferred during ratification — see ADR):
R2_aux_neg_requires_verb — "<aux> not <wrong-POS>" rejected
R3_be_neg_requires_predicate — "<be> not <verb>" rejected
Fail-open on unknown POS, fail-closed on explicit wrong POS.
Cognition eval byte-identical (100/91.7/100/100).
Original bug class — "Light reveals truth, right?" → "Right does not
thought." — now routes to "I do not have a reviewed articulation for
that yet." with grounding_source=none, walk_surface preserving the
rejected candidate, and telemetry carrying R2_aux_neg_requires_verb.
Files:
generate/realizer_guard.py NEW — pure verifier
chat/runtime.py hook on stub + main paths
chat/telemetry.py serialize guard fields
core/physics/identity.py TurnEvent +2 fields
evals/realizer_guard/run_holdout.py NEW — 6-prompt cluster
tests/test_realizer_guard_*.py NEW — 46 tests (unit/seam/holdout)
docs/decisions/ADR-0075-*.md NEW — ratified
Invariants pinned:
invariant_realizer_no_illegal_articulation
invariant_realizer_guard_byte_identity_on_currently_passing_cases
Lanes (excluding 1 pre-existing TestDemoPreambles failure unrelated
to C1, already present at 4426f38):
smoke 67/67 cognition 120/120(+1s) teaching 17/17
packs 6/6 runtime 19/19 algebra 132/132 full 2792/2793
A single demo that walks the full 3 × 3 × 2 matrix (register × lens
× prompts, 18 cells) and pins five claims simultaneously, packaging
both single-axis invariants into one composition gate.
The single-axis tours assert opposite invariants:
register-tour : per (lens, prompt), trace_hash CONSTANT across
registers (R5 / ADR-0072).
anchor-lens-tour : per (register, prompt), engaged lens diverges
in trace_hash from the unanchored baseline
(L1.4 / ADR-0073d).
Orthogonality-tour packages both claims simultaneously across the
full matrix, plus three surface-level claims that pin the markers
operators actually see.
Composed claims (all five must hold)
A) inner_register_invariant_within_lens
For each (lens, prompt) cell, the three register runs share an
identical trace_hash. (R5 register-tour, applied 6 times:
3 lenses × 2 prompts.)
B) outer_lens_distinctness_within_register
For each (register, prompt) cell where any non-unanchored lens
engages, that engaged lens's trace_hash differs from the
unanchored baseline at the same (register, prompt).
(L1.4 anchor-lens-tour, applied 6 times: 3 registers × 2 prompts.)
C) surface_carries_register_marker_under_convivial
Every convivial cell with a non-empty surface has a non-empty
register_variant_id.
D) surface_carries_lens_annotation_when_engaged
Every engaged cell carries [lens(<id>):<mode>] in surface AND
a non-empty anchor_lens_mode_label.
E) no_substrate_glyph_leak_across_grid
No cell's surface contains Greek/Hebrew/Syriac/Arabic glyphs.
(ADR-0073c gate re-asserted across the full matrix.)
CLI wiring
core demo orthogonality-tour human-readable grid + claims
core demo orthogonality-tour --json structured report
Exit code 0 iff all five claims hold.
Files
evals/orthogonality_tour/__init__.py NEW
evals/orthogonality_tour/run_tour.py NEW
core/cli.py EDIT
- cmd_demo handler wires orthogonality-tour
- demo choices + EPILOG examples updated
tests/test_orthogonality_tour_demo.py NEW (9 tests)
docs/decisions/ADR-0074-orthogonality-tour.md NEW
Sanity check baked into tests
test_engaged_cells_appear_for_both_non_trivial_lenses pins that
grc_logos_v1 engages on knowledge in all 3 registers (3 cells)
and he_logos_v1 engages on truth in all 3 registers (3 cells).
Prevents the lift claims being vacuously satisfied by a future
engagement regression.
Lane evidence
- 9 new orthogonality-tour tests pass.
- core demo register-tour → all_claims_supported: True
- core demo anchor-lens-tour → all_claims_supported: True
- core demo orthogonality-tour → all_claims_supported: True
- python -m core.cli eval cognition → byte-identical 100/100/91.7/100.
- Full lane: 2745 passed / 4 skipped / 1 pre-existing failure
(+9 over L1.4's 2736; the one failure remains
test_all_preamble_explains_combined_run, unrelated).
No runtime / composer / loader / pack / schema changes. Pure demo
consumer of existing telemetry contracts.
L1.3 of the anchor-lens inside-out rollout — first substantive
surface lift on the substantive axis. Two ratified non-trivial
lenses engage on cognition-pack lemmas via the alignment graph,
appending [lens(<id>):<mode>] annotations to the existing
pack-grounded surface.
Two ratified lenses
grc_logos_v1 (Greek substrate)
primary_substrate : "grc"
semantic_domain_preferences: ["logos.episteme.systematic_knowledge"]
cognitive_mode_label : "systematic"
Engages on en "knowledge" via grc-core-cog-021 (ἐπιστήμη) →
en-core-cog-007 alignment edge.
he_logos_v1 (Hebrew substrate)
primary_substrate : "he"
semantic_domain_preferences: ["logos.aletheia.verity"]
cognitive_mode_label : "covenant-verity"
Engages on en "truth" via he-core-cog-002 (אמת) →
en-core-cog-002 alignment edge.
Both ratified under method anchor_lens_lifts_proposition.
Engagement rule (single)
1. Resolve en_lemma → entry_id (cognition pack).
2. For each substrate pack matching lens.primary_substrate, load
alignment.jsonl; find edges where target_id == entry_id.
3. For each such substrate lemma, if any atom in its
semantic_domains ∈ lens.semantic_domain_preferences → engage.
4. No match → None (no annotation; byte-identical surface).
The pivot is shared semantic_domain atoms surfaced via the
alignment graph — exactly the language-neutral commitment from
ADR-0073. Engagement never touches non-English surface text;
entry_ids and atom strings only.
Surface lift
no-lens : "Knowledge is X. pack-grounded (en_core_cognition_v1)."
lens-on : "Knowledge is X. pack-grounded (en_core_cognition_v1) [lens(grc_logos_v1):systematic]."
Annotation between existing provenance and trailing period.
Both metadata fields are ASCII-bounded ≤64 chars at the loader
level, so the annotation can never carry non-ASCII.
Scope deliberately narrow
L1.3 wiring restricted to pack_grounded_surface /
build_pack_surface_candidate (DEFINITION/RECALL only). Other
composers (COMPARISON / CORRECTION / PROCEDURE / NARRATIVE /
EXAMPLE / CAUSE / VERIFICATION) accept the anchor_lens kwarg via
forward-compat default UNANCHORED but do not yet consume it.
L1.3b or later broadens to those intent shapes.
Ratify gate widening
Non-null lenses must:
- have primary_substrate ∈ {grc, he, en}
- have a non-empty cognitive_mode_label
- every preferred atom must exist in at least one lemma of the
named substrate (trust boundary: operators cannot ship a lens
pointing at atoms not on disk).
Method: anchor_lens_lifts_proposition. Null lenses still ratify
under byte_identity_null_lift (L1.2 method).
Seam allow-list widening
Truth-path modules (cognition / trace / pipeline / intent /
propagation / vault / algebra) still refused. Composer-side
imports from chat/pack_grounding.py now permitted — the same way
ADR-0069's R2 widened the register seam.
New invariants pinned (3)
tests/test_anchor_lens_engagement_unit.py (14 tests) — resolver
returns mode label only on intended substrate × en lemma pair;
case-insensitive; engagement None under null lens; synthetic
lens with unmatched atom returns None; annotation is pure ASCII.
tests/test_anchor_lens_lifts_proposition.py (17 tests) — grc
engages on knowledge only, he engages on truth only,
cross-lens isolation, three-way distinctness, replay determinism
per (lens × prompt), register-tour seam holds within each lens
scope (orthogonality CI-pinned, parametrized over 4 lens
choices).
tests/test_anchor_lens_no_glyph_leak.py (5 tests) — hard
block-scoped gate: Greek (U+0370..03FF, U+1F00..1FFF), Hebrew
(U+0590..05FF), Syriac, Arabic. Stylistic punctuation
(em-dash etc.) explicitly allowed; em-dash predates L1.3 by a
wide margin and is not a substrate-leak risk. Tested per-lens
across every cognition case + direct lens-metadata ASCII check.
Lane evidence
74 anchor-lens tests pass (37 from L1.2 + 37 new).
python -m core.cli eval cognition → public 100/100/91.7/100
byte-identical (lens=None / default_unanchored_v1).
core demo register-tour --json → all_claims_supported: True
(R5 seam still holds; L1.3 doesn't perturb presentation axis).
Full lane: 2706 passed / 4 skipped / 1 pre-existing failure
(+37 over L1.2's 2669; the one failure remains
test_all_preamble_explains_combined_run, unrelated).
Files
packs/anchor_lens/grc_logos_v1.json NEW
packs/anchor_lens/grc_logos_v1.mastery_report.json NEW
packs/anchor_lens/he_logos_v1.json NEW
packs/anchor_lens/he_logos_v1.mastery_report.json NEW
scripts/ratify_anchor_lens_packs.py EDIT
LENS_IDS adds grc_logos_v1 / he_logos_v1; gate widened.
chat/pack_grounding.py EDIT
_resolve_anchor_lens_mode, _maybe_append_anchor_lens_annotation,
_substrate_lexicon_by_entry_id, _en_lemma_to_entry_id.
build_pack_surface_candidate + pack_grounded_surface gain
anchor_lens kwarg (default UNANCHORED).
chat/runtime.py EDIT
Thread self.anchor_lens into pack_grounded_surface() call.
tests/test_anchor_lens_pack_seam.py EDIT
Doc-comment updated for L1.3 allow-list.
tests/test_anchor_lens_* NEW (3 files)
docs/decisions/ADR-0073c-anchor-lens-composer-wiring.md NEW
The conversation demo's Scene 4 was emitting CORE's raw production
teaching-grounded surface, which reads engineer-y for a layperson:
narrative — teaching-grounded (cognition_chains_v1):
rhetoric.narrative; language.discourse. narrative reveals
meaning (cognition.meaning). No session evidence yet.
The production format is the trust-boundary contract (12+ tests + eval
byte-equivalence + several ADRs depend on it), so it stays unchanged.
This change adds a demo-only display layer that rewrites the same
surface to put the propositional sentence first, with provenance as a
trailing parenthetical:
Narrative reveals meaning. (teaching-grounded from
cognition_chains_v1 — narrative: rhetoric.narrative;
language.discourse; final term: cognition.meaning.
No session evidence yet.)
Trust-boundary preserving:
- Only fires when grounding_source == "teaching" AND surface matches
the production format.
- Every load-bearing token preserved (subject, connective, object,
corpus_id, semantic_domains, "No session evidence yet").
- Pack-grounded surfaces + discourse-planner surfaces pass through
unchanged.
- JSON report's `surface` field still carries the raw production
surface — only the chat-style print is humanised.
Test gate: 2 new tests pin the rewrite contract (proposition-first,
all load-bearing tokens preserved, passthrough for non-teaching).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A live walkthrough that shows CORE actually being used. Four scenes,
five turns, rendered as a chat transcript ('You: …' / 'CORE: …') with
plain-English captions between turns.
Streamed by default (per-character prompt, per-word response, brief
"thinking" pause) so the layperson sees the answer arriving live.
--no-stream disables delays for CI / tests / fast capture.
Scenes:
1. Pack lookup — "What is truth?"
Shows deterministic lexicon-grounded answer.
2. Teaching-chain — "Walk me through recall."
Shows CORE chaining reviewed facts.
3. Compound prompt — "What is truth, and why does it matter?"
Shows compound decomposition + composition.
4. Cold turn → learn — "Why does narrative exist?"
Shows CORE refusing to fabricate, an operator
teaching it one new chain (real propose →
replay-gate → accept), then re-asking the same
prompt and getting a grounded answer.
The learning-loop scene reuses the production learning_loop demo so
the underlying machinery is exactly what ships — active corpus is
byte-identical pre/post.
Test gate: tests/test_conversation_demo.py (9 tests — per-scene
grounding source + content checks, learning loop closes,
active-corpus byte-identical, stable JSON shape).
Usage:
core demo conversation # live streamed transcript
core demo conversation --no-stream # instant rendering
core demo conversation --json # structured report (no chat output)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Four-scene investor/operator-facing walkthrough proving the discourse-
planner spine is load-bearing. Each scene runs the same prompt under
flag-off (BRIEF baseline) and flag-on (RuntimeConfig.discourse_planner)
and pins a falsifiable lift assertion.
S1. EXPLAIN — Explain truth.
Flag-on: pack→teaching upgrade + 2 chain
continuation sentences over baseline.
S2. COMPOUND — What is truth, and why does it matter?
Flag-on: 9 grounded sentences across two sub-
plans; flag-off routes to OOV.
S3. WALKTHROUGH — Walk me through recall.
Flag-on emits the CLOSURE chain hop
'Recall reveals memory.'; flag-off
does not.
S4. Determinism — N=3 reruns × 3 prompts, unique(surface)=1.
Read-only against live packs + active corpus. Demo is test-gated
(7 tests, all green) and ships a stable JSON contract for downstream
consumers.
Wired into CLI as `core demo articulation [--json]` alongside the
existing trilogy (audit-tour / anti-regression / learning-loop).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sharpens the measurement layer to match the runtime spine landed in
07fefb9 / 7af7892 / 4e3ddee. Pure eval/benchmark/holdout work —
no runtime or planner code changed.
New isolation lanes
-------------------
* ``evals/compound_intent_decomposition/`` — single-purpose lane for
the new ``classify_compound_intent`` decomposer. Metrics:
``decomposition_accuracy``, ``atom_precision``, ``subject_accuracy``.
Public: ``decomposition=1.0`` on 4e3ddee.
* ``evals/walkthrough_chain/`` — single-purpose lane for the new
WALKTHROUGH sequential teaching-chain walk. Metrics:
``path_exact_rate``, ``anchor_rate``, ``min_hop_rate``, ``bounded_rate``.
Public: ``path_exact=1.0`` on 4e3ddee.
Without these, regressions in compound decomposition or the
walkthrough walk would show up as noise in ``multi_sentence_response``.
Each capability now has a single load-bearing metric on its own lane.
Cold-start lane sharpened
-------------------------
* ``evals/cold_start_grounding/public/v1/cases.jsonl`` extended with
expository, compound, and walkthrough cases (48 total cases across
19 categories including new ``expository_definition``,
``compound_definition_cause``, ``walkthrough_definition``).
* ``evals/cold_start_grounding/runner.py`` uses
``classify_compound_intent(...).primary`` for compound subject
scoring — previously misattributed subjects on multi-part prompts.
Holdouts for the long-span lanes
--------------------------------
Until now only the cognition lane had a holdout split. Adding
holdouts to the long-span lanes gives the planner work somewhere to
fail honestly when we widen:
* ``evals/cold_start_grounding/holdouts/v1/cases.jsonl`` (5 cases)
* ``evals/multi_sentence_response/holdouts/v1/cases.jsonl`` (5 cases)
* ``evals/conversational_thread_coherence/holdouts/v1/cases.jsonl`` (3 cases)
* ``evals/warmed_session_consistency/holdouts/v1/cases.jsonl`` (2 cases)
Discourse-planner-on bench sub-bench
------------------------------------
* ``benchmarks/articulation.py`` adds a planner-on sub-bench that
reports ``articulate_sentence_rate`` alongside the existing
throughput metrics. Baselines articulation under load before any
follow-up touches ``compute_trace_hash``.
Test coverage
-------------
* ``tests/test_compound_walkthrough_eval_lanes.py`` — new file pinning
the two new lane runners.
* ``tests/test_articulation_bench.py``, ``tests/test_cold_start_grounding_lane.py``,
``tests/test_intent_explain_paragraph.py``,
``tests/test_response_mode_classifier.py`` — updated for new cases
and assertions.
Validation
----------
* 152/152 active tests pass on the listed surfaces (2 skipped).
* smoke suite 67/67.
* cognition eval byte-identical: public 100/100/91.7/100.
* multi_sentence flag_on: articulate=1.0, disclosure=0.0, unarticulate=0.0
* compound_intent_decomp public: decomposition=1.0
* walkthrough_chain public: path_exact=1.0
* cold_start_grounding public (48 cases): intent=1.0, grounding=1.0, subject=1.0
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
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.
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.
Pre-cleanup before extending intent classification. Extracts
``ChatRuntime._maybe_apply_discourse_planner(text, source_tag) ->
str | None`` and replaces the two duplicated blocks (cold-start
pack-grounded branch + warm post-walk branch) with single-line
``planned = ...; if planned is not None: assign`` call sites.
Signature locked: takes only the prompt and the already-classified
grounding source tag; returns the replacement surface or None.
Callers own assignment — the helper neither reads nor writes any
surface or articulation state. The warm site additionally does the
``articulation = replace(articulation, surface=planned)`` follow-up
which the cold site does not need.
Gating discipline unchanged (re-pinned in 9 new tests):
* Returns None when ``self.config.discourse_planner`` is False.
* Returns None unless source_tag ∈ {"pack", "teaching"}.
* Returns None when the classified intent has no subject.
* Returns None on single-move plans (BRIEF mode / empty bundle).
* Returns None on empty rendered string.
Behavior is byte-identical to the pre-dedup state — same metrics:
flag off: multi=0.1429, primed_multi=0.0000, conn=0.0769
flag on : multi=0.5238, primed_multi=0.5000, conn=0.2308
cognition eval byte-identical: public 100/100/91.7/100.
smoke suite 67/67.
The two paths now cannot drift; the upcoming intent classifier
extension lifts both branches in lockstep.
Option 1 of the lane-isolation work after the 8d1aeec predicate
refinement. Adds optional ``priming_prompts: [str, ...]`` to each
case in ``multi_sentence_response``. The runner runs priming prompts
on the same ``ChatRuntime`` instance before the scored prompt and
discards their responses; only the scored prompt is measured.
This isolates code paths (notably the discourse planner hook) that
engage only on the warm pack/teaching path from cold-start one-shot
paths. Cold-start measurement is preserved: cases without
``priming_prompts`` (or with an empty list) keep the old behavior.
New metric ``primed_multi_sentence_rate`` reports only on primed
cases. ``primed`` is also exposed per-case in case_details.
Six primed cases added to ``public/v1/cases.jsonl`` (Explain truth /
Tell about truth / Explain knowledge / Tell about light / Tell about
parent / Write a short paragraph about truth). Each is the cold-
start variant of an existing case plus a single "What is X?"
priming prompt.
3 new tests:
* Priming prompts run in order on the same runtime before the
scored prompt; primed=True on the result.
* Default cold-start behavior: no priming key OR empty list ⇒
primed=False; aggregate untouched.
* ``primed_multi_sentence_rate`` separates from aggregate so
cold cases never inflate/depress the warm-path metric.
A/B measurement on the live runtime (21 cases):
flag off: multi=0.1429, primed_multi=0.0000, primed_cases=6
flag on : multi=0.2857, primed_multi=0.5000, primed_cases=6
Lift is real and exclusively on the substrate the planner can
actually serve (teaching-grounded narrative). The three primed
"Explain X" and "Write a short paragraph about X" cases stay
vault-grounded (Explain / Write are not DEFINITION / NARRATIVE
intents and so don't fire pack-grounded warm), so they don't lift.
That gap is what option 2 will close.
contract.md updated to document priming and the new metric.
Step 5 of the discourse-planner sequencing. Closes the chain:
classify_intent + classify_response_mode
-> grounding_bundle_for(subject)
-> plan_discourse(intent, mode, bundle)
-> render_plan(plan)
-> response_surface
Adds RuntimeConfig.discourse_planner (default False). When True, the
runtime — after the warm pack/teaching-grounded surface is set —
classifies the response mode, assembles a GroundingBundle from the
ADR-style accessors, builds a DiscoursePlan, and replaces the warm
surface with the deterministic multi-clause rendering whenever the
plan has more than one move.
Gating discipline:
* Engages only on warm_grounding_source in {"pack", "teaching"} so
vault/none turns and the discovery-signal CAUSE/VERIFICATION
disclosure are preserved exactly.
* BRIEF mode always collapses to a single ANCHOR move, so flag-on
with BRIEF intent is byte-identical to flag-off.
* Empty bundles produce empty plans; the runtime falls through to
the existing warm surface untouched.
Adds render_plan(plan) to generate/discourse_planner.py — a pure,
deterministic multi-clause renderer with fixed canonical connectives:
ANCHOR : capitalized opening sentence
SUPPORT : "Furthermore, ..."
RELATION : "In turn, ..."
TRANSITION: "Consequently, ..."
CLOSURE : skipped when fact is None
Every visible token is a verbatim pack lexicon entry, gloss, or
reviewed teaching chain string — no synthesis.
13 new tests pin:
* render_plan empty/brief/paragraph shape
* canonical connectives present in paragraph rendering
* deterministic + verbatim-fact invariants
* RuntimeConfig.discourse_planner defaults False
* Flag-off surface has no planner connectives
* Flag-on lifts produce structurally well-formed multi-sentence
output on grounded substrate
Lift measurement (multi_sentence_response public/v1, 15 cases):
* flag off: multi=0.40, connective=0.50, grounded=0.40
* flag on : multi=0.40, connective=0.60, grounded=0.40
-> connective_present_rate +10pp; multi-sentence count flat
because the existing narrative composer's literal "." chars in
tags like "cognition.truth" already trigger sentence splits in
the lane regex. Real lift is form quality: e.g. "Tell me about
truth" now renders as "Truth is a claim or state grounded by
evidence and coherent judgment. Furthermore, truth belongs to
cognition.truth. In turn, truth grounds knowledge." instead of
the prior provenance-laden narrative surface.
Critical gates (all green):
* flag off: cognition eval byte-identical
- public 100/100/91.7/100, holdout 100/100/83.3/100
* smoke suite 67/67
* conversational_thread_coherence: 3 unwanted placeholders flag off
and flag on (no regression)
* planner JSON byte-stable across calls (contract tests)
* grounding source order preserved (sidecar tests)
Step 4 of the discourse-planner sequencing. Replaces the contract-only
NotImplementedError with deterministic move-selection rules per
ResponseMode:
* BRIEF → 1 move (ANCHOR)
* EXPLAIN → up to 3 (ANCHOR + SUPPORT + RELATION)
* PARAGRAPH → up to 5 (ANCHOR + SUPPORT + RELATION + TRANSITION + CLOSURE)
* EXAMPLE → up to 3 (ANCHOR + RELATION + CLOSURE)
* WALKTHROUGH→ deferred, falls back to BRIEF shape so planner is total
Move selectors:
* ANCHOR — pack is_defined_as on intent.subject if available, else
first canonical pack fact on subject, else first
canonical fact of any source
* SUPPORT — pack belongs_to on anchor's subject
* RELATION — teaching/cross-pack chain rooted on anchor's subject
* TRANSITION — chain rooted on the relation's object (topic shifts)
* CLOSURE — no new fact; carries given lemmas forward
Empty bundles produce empty plans (planner is total — callers fall
through to the existing single-sentence composer path safely).
Updated contract test test_plan_discourse_is_contract_only ->
test_plan_discourse_handles_empty_bundle to reflect the implementation.
26 new behavior tests pin: per-mode shape (BRIEF/EXPLAIN/PARAGRAPH/
EXAMPLE/WALKTHROUGH), anchor preference for is_defined_as, support
preference for belongs_to, relation preference for teaching source,
paragraph transition topic shift, closure semantics (no new content,
carries given forward), fact uniqueness across moves, anchor fallback
when no pack subject match, and full determinism (byte-stable JSON
across all five modes, pure function equality).
Verification:
* 49/49 planner tests pass (23 contract + 26 behavior).
* smoke suite 67/67.
* cognition eval byte-identical:
public 100/100/91.7/100, holdout 100/100/83.3/100.
Step 3 of the discourse-planner sequencing. Adds
generate/grounding_accessors.py:
* pack_grounded_facts(lemma) -> tuple[GroundedFact, ...]
* teaching_grounded_chains(lemma) -> tuple[GroundedFact, ...]
* cross_pack_grounded_chains(lemma) -> tuple[GroundedFact, ...]
* grounding_bundle_for(lemma) -> GroundingBundle
All four reuse the existing data substrate (chat.pack_resolver,
chat.teaching_grounding._all_chains_index, chat.cross_pack_grounding
chain accessors) — no new loader, no new I/O, no string composer
touched. Pack facts emit one `is_defined_as` per gloss + one
`belongs_to` per semantic_domain; teaching/cross-pack chains emit
verbatim (subject, connective, object) triples; everything sorted by
GroundedFact.sort_key for canonical determinism.
21 new tests pin: pack/teaching/cross-pack accessor shape, canonical
sort order, verbatim object invariant (no synthesis), source_id
points back into real artifact, bundle composition combines all three
sources with pack-first priority, and doctrine invariants (no
*_grounded_surface composer imported, no chat.runtime imported).
Verification:
* 21/21 new accessor tests pass.
* smoke suite 67/67.
* cognition eval byte-identical:
public 100/100/91.7/100, holdout 100/100/83.3/100.
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.
Sidecar characterization that freezes the deterministic source ordering
of the existing aggregated teaching index, cross-pack chains, and
narrative/example composer outputs. No dependency on the discourse
planner contract — this is the bridge that protects the next two
phases (ResponseMode classification + structured GroundedFact
accessors) from source-order drift.
5 tests pin: aggregated teaching index key order, cross-pack subject
and object views, narrative composer source ordering, example composer
source ordering.
Authored in worktree 3721; landed here so the main-line sequencing
(characterization -> ResponseMode -> accessors -> planner -> wiring)
can proceed against a stable substrate.
Contract-only landing for the typed multi-move discourse layer that
will sit between grounding and graph construction:
DialogueIntent + ResponseMode + GroundingBundle
-> DiscoursePlan
-> PropositionGraph
-> ArticulationTarget
-> RealizedPlan
Adds frozen dataclasses (ResponseMode, FactSource, GroundedFact,
GroundingBundle, DiscourseMoveKind, DiscourseMove, DiscoursePlan),
canonical sort + as_dict + to_json serialization (sorted keys,
no-whitespace separators), and the pure plan_discourse signature
(raises NotImplementedError; move-selection rules deferred).
23 contract tests pin the determinism invariants required before
DiscoursePlan can be folded into compute_trace_hash in a follow-up
ADR: frozen-dataclass equality, canonical pack<teaching<vault<operator
ordering, byte-stable to_json across calls and equal plans, JSON
round-trip stability, and signature purity (no chat.* imports, no
clock/env/filesystem reads).
No runtime wiring; smoke suite 67/67; cognition eval byte-identical
(public 100/100/91.7/100, holdout 100/100/83.3/100).
The Phase B1 pipeline-override usefulness gate (c3e2a22) and the
Phase C gloss-backed pack surfaces (07da601) changed the surface
string format in three orthogonal ways:
1. Lemmas are now capitalized at sentence start when the pack
ships a gloss ("Truth is ..." vs "truth — ...").
2. The "No session evidence yet." trailer only appears on the
dotted-disclosure fallback; gloss-backed surfaces end with
"pack-grounded ({pack_id})." instead.
3. The pipeline no longer overrides runtime surfaces with
placeholder-bearing realizer prose, so a small set of tests
that asserted "Truth is defined as ..." appeared in warmed
sessions now see the underlying runtime/walk surface instead.
Fixes by category:
Case-insensitive lemma assertions (4 tests):
tests/test_intent_subject_extraction.py
tests/test_oov_surface.py
tests/test_anaphora.py (× 2)
All four assertions changed from
assert "X" in resp.surface
to
assert "X" in resp.surface.lower()
with a comment noting the gloss-frame capitalization.
Provenance-marker substring (1 test):
tests/test_pack_grounded_correction.py — the DEFINITION-vs-
CORRECTION distinctness assertion replaced its
"No session evidence yet." check with the common-substring
"pack-grounded" marker. Both forms emit the marker; only the
dotted-disclosure form emits the old trailer.
Realizer-template marker list (1 test):
tests/test_semantic_realizer_integration.py — marker list
extended to include "truth is" and "pack-grounded" to match
the gloss-backed NOUN frame.
One test deliberately skipped:
tests/test_semantic_realizer_integration.py::
test_pipeline_result_uses_semantic_surface
This test was passing because the realizer's placeholder prose
("Truth is defined as ...") would override the runtime surface
on warmed sessions. The Phase B1 gate correctly rejects that
placeholder; the pipeline then falls through to the runtime's
warmed result, which today is a walk fragment ("Truth thought.")
because runtime pack-grounding only fires on empty_vault.
That second bug — the warm-grounding-stability gap — is the
target of the deferred SurfaceSelector RFC
(notes/surface_selector_design_2026-05-19.md). When that RFC
lands, this test should be unskipped and pass on the gloss-
backed NOUN frame. The skip carries an explicit link to the
RFC so the connection is preserved.
Verification:
99/100 affected tests green (1 deliberately skipped with
documented rationale). No new failures introduced.
Phase C of the gloss feature. Lands the natural-language gloss
content that the resolver (Phase B2) and the runtime composer
(Phase B3) were prepared for. This is the user-visible payoff:
cold-start DEFINITION / RECALL prompts on pack-resident lemmas now
emit fluent grounded sentences instead of dotted-domain disclosure.
Authoring: five parallel subagents in ONE message block (a single
parallel dispatch, ~20s wall-clock vs ~95s sequential). Each
subagent received its pack's complete lemma + POS list and a strict
JSON-shape exemplar. Total returned: 326 raw gloss entries.
Assembly (this commit): the raw entries were partitioned by
lexicon-residency lookup (the resolve_gloss invariant enforced at
storage time), deduplicated within pack, sorted by lemma, written
to ``language_packs/data/<pack>/glosses.jsonl``, and each pack's
manifest received a new ``glosses_checksum`` field. 323 glosses
landed clean; 0 rejected.
Per-pack distribution:
en_core_cognition_v1 78 glosses
en_core_meta_v1 72 glosses
en_core_attitude_v1 40 glosses
en_core_temporal_v1 28 glosses
en_core_action_v1 26 glosses
en_core_quantitative_v1 24 glosses
en_core_spatial_v1 24 glosses
en_core_polarity_v1 16 glosses
en_core_causation_v1 15 glosses
Live-probe lift (fresh ChatRuntime per prompt):
BEFORE:
truth — pack-grounded (en_core_cognition_v1):
cognition.truth; logos.core; epistemic.ground.
No session evidence yet.
AFTER:
Truth is a claim or state grounded by evidence and coherent
judgment. pack-grounded (en_core_cognition_v1).
Same provenance. Same audit-trail content (the dotted domains are
still in lexicon.jsonl, the resolver can still read them, the
candidate object carries them verbatim). But the user-facing
surface is a sentence the user can actually read.
Eval-lane lift:
deterministic_fluency BEFORE AFTER
no_dotted_inventory_rate 0.3333 → 1.0000
no_provenance_only_rate 1.0000 → 1.0000 (held)
no_placeholder_rate 1.0000 → 1.0000 (held)
complete_punctuation_rate 1.0000 → 1.0000 (held)
finite_predicate_shape 1.0000 → 1.0000 (held)
surface_provenance_match 1.0000 → 1.0000 (held)
cold_start_grounding all metrics held at 1.0
warmed_session_consistency no_placeholder + telemetry_match held at 1.0
(warm_grounding_stability still 0 — separate fix)
cognition eval public 100 / 100 / 91.7 / 100 (BYTE-IDENTICAL)
cognition eval holdout 100 / 100 / 83.3 / 100 (BYTE-IDENTICAL)
The cognition eval bytes-identity holds because the eval checks
substring containment (case-insensitive after the format change).
Every lemma still appears in its fluent surface.
Hardening this commit enforces:
Lexicon-residency at storage time
tests/test_pack_glosses_content.py::test_every_gloss_lemma_is_lexicon_resident
walks every glosses.jsonl and asserts every lemma is present in
the same pack's lexicon.jsonl. Drift in glosses (an unratified
lemma sneaking in) fails the lane immediately.
Dual-checksum discipline
tests/test_pack_glosses_content.py::test_every_glossed_pack_has_matching_checksum
re-hashes glosses.jsonl bytes-on-disk and compares against the
manifest's glosses_checksum. Any tampering fails.
Immutable-lexicon invariant
tests/test_pack_glosses_content.py::test_lexicon_checksum_unchanged_by_gloss_landing
re-hashes lexicon.jsonl and compares against the manifest's
(original) checksum. Proves that adding glosses did NOT perturb
the lexicon seal.
High-freq lemma resolution
32 of the most-common conversational lemmas (truth, doubt,
fact, idea, self, true, important, now, place, make, effect,
always, ...) all resolve to a fluent surface end-to-end.
Test-suite drift this commit absorbed:
- tests/test_pack_grounding.py — three substring assertions
updated to be case-insensitive (gloss-backed surfaces capitalize
lemmas at sentence start, dotted-disclosure surfaces don't).
"No session evidence yet" assertion replaced with the
common-substring "pack-grounded" marker that BOTH forms emit.
- tests/test_pack_resolver_glosses.py — the back-compat test
pivots from en_core_cognition_v1 (now glossed) to en_minimal_v1
(deliberately unglossed). A new test pins the glossed case.
Files added:
language_packs/data/<pack>/glosses.jsonl (9 files, 323 entries)
tests/test_pack_glosses_content.py (9 contract tests)
Files modified:
language_packs/data/<pack>/manifest.json (9 files, glosses_checksum field)
chat/pack_grounding.py (lowercase "pack-grounded" tag)
tests/test_pack_grounding.py (3 substring assertions relaxed)
tests/test_pack_resolver_glosses.py (back-compat test pivoted)
Verification:
127/127 affected tests green.
9/9 new gloss-content tests green.
All three eval lanes report the lift documented above.
Cognition eval byte-identical.
Lands the gloss-loader scaffolding from feat/pack-glosses-wip onto
main, with every hardening item from the 2026-05-19 design review
built in from the start. No glosses ship in this commit — only the
infrastructure that will consume them safely.
Hardening items (each pinned by a test):
1. Lexicon-residency check in resolve_gloss()
chat/pack_resolver.py — resolve_gloss now requires the lemma to be
present in the same pack's lexicon.jsonl BEFORE consulting
glosses.jsonl. Without this, glosses.jsonl would become a parallel
surface-authoring channel that bypasses the lexicon's checksum
seal: someone could ship a gloss for a lemma the pack never
ratified, and the runtime would emit it as if it were pack content.
Test: TestLexiconResidencyEnforced::test_gloss_for_unratified_lemma_is_rejected
authors a gloss for ``gamma`` (a lemma not in the lexicon) and
asserts resolve_gloss returns None.
2. Dual-checksum manifest support
language_packs/schema.py — LanguagePackManifest gains an OPTIONAL
``glosses_checksum: str | None`` field. Glosses are an additive
overlay; bumping the glosses_checksum does NOT perturb the
immutable lexicon checksum.
language_packs/compiler.py — _load_pack_cached now verifies
bytes-on-disk of glosses.jsonl against the manifest's
glosses_checksum when present. Missing field on legacy packs is
back-compat (no verification, no raise). Mismatch raises
ValueError exactly like the lexicon checksum gate.
Tests:
test_matching_glosses_checksum_loads_clean — happy path
test_checksum_mismatch_raises — tampered file rejected
test_missing_glosses_checksum_is_back_compat — legacy packs OK
3. clear_resolver_cache() clears BOTH lexicon AND glosses LRU caches
Previously only cleared _pack_lexicon_for, so test fixtures that
wrote glosses.jsonl mid-process would see stale (empty) gloss data
on subsequent resolve_gloss calls.
Test: TestClearResolverCacheClearsBoth proves the issue exists
without the clear, then proves the new code fixes it.
4. Malformed JSONL lines silently skipped
A single bad line in glosses.jsonl must not break resolution for
the rest of the pack. Same defensive parsing as _pack_lexicon_for.
Entries missing required fields (lemma, gloss, or empty values)
are also skipped.
Tests:
test_malformed_line_skipped — invalid JSON between valid lines
test_entry_missing_required_field_skipped — 4 bad shapes filtered
5. Missing glosses.jsonl is back-compat
_pack_glosses_for returns an empty dict when the file is absent.
resolve_gloss returns None. No exception. All 9 currently-
ratified English packs ship with no glosses.jsonl — they must
continue to load cleanly.
Tests:
test_pack_with_no_glosses_returns_empty
test_resolve_gloss_on_lemma_without_gloss_file_returns_none
Files:
chat/pack_resolver.py
+ _pack_glosses_for (cached loader)
+ resolve_gloss (lexicon-residency-gated lookup)
* clear_resolver_cache now clears both caches
language_packs/schema.py
+ LanguagePackManifest.glosses_checksum field (optional)
language_packs/compiler.py
+ dual-checksum verification block in _load_pack_cached
+ glosses_checksum field passed through to the manifest dataclass
tests/test_pack_resolver_glosses.py
11 tests covering all five hardening items
Verification:
11/11 new tests green.
Full cognition eval byte-identical.
All currently-ratified packs continue to load without glosses.
The 2026-05-19 design review's P0 #1 finding:
> CognitiveTurnPipeline can replace a useful runtime surface with
> placeholder prose.
Evidence at core/cognition/pipeline.py:147-149 (pre-fix):
if realized_plan.surface and not gate_fired:
surface = realized_plan.surface
articulation_surface = realized_plan.surface
The override gate was JUST "non-empty + gate didn't fire". No
usefulness check. Result: a realizer output of
"Truth is defined as ..." (with <pending> rendered as ...) silently
overrode a perfectly-grounded runtime pack surface, and the runtime
audit log still held a third surface.
Fix: gate the override through ``_is_useful_surface`` from
generate/intent_bridge.py — the same predicate that already gates
the bridge's articulate_with_intent fallback path. An ungrounded
realizer surface cannot honestly override a grounded runtime
surface. When the realizer cannot produce a useful surface, we
keep the runtime answer the user sees.
Measured lift on the warmed_session_consistency lane (3 of its 4
metrics):
BEFORE AFTER
no_placeholder_rate 0.4444 → 1.0000
telemetry_consistency_rate 0.4444 → 1.0000
warm_grounding_stability 0.0000 → 0.0000 (separate bug — see below)
The two metrics that flipped to 1.00 are now CI-pinned in
tests/test_warmed_session_lane.py:
TestPipelineOverrideGateInvariants — any future weakening of the
override gate fails the suite immediately.
Cognition eval byte-identical:
public: 100 / 100 / 91.7 / 100
holdout: 100 / 100 / 83.3 / 100
KNOWN FOLLOW-UP — not in this commit:
warm_grounding_stability remains 0.0 because of a SEPARATE bug
the warmed lane surfaces:
Turn 1: "What is truth?" -> pack-grounded ("truth — pack-grounded
(en_core_cognition_v1): cognition.truth; ...")
Turn 2: "What is truth?" -> vault-grounded ("Truth infer.")
After turn 1 ingests pack content into the vault, turn 2's gate
source flips from ``empty_vault`` to ``vault``, so the runtime's
``_maybe_pack_grounded_surface`` dispatcher is bypassed entirely
and the field-walk path produces gibberish ("Truth infer.").
This is the SurfaceSelector-shaped problem from the design review:
pack-grounding should fire by intent shape and lemma residency, not
by vault gate state. Fix scope crosses runtime.py:chat() + the
vault gate logic; deferred to its own commit / design proposal
rather than absorbed here.
The warmed lane already records the metric (0.0 baseline) so when
the fix lands it shows up as a measurable lift.
Closes the gap the 2026-05-19 design review flagged:
> Some evals are too permissive to protect fluency; they accept
> fragments or ungrammatical strings.
This lane defines fluency as six DETERMINISTIC predicates over the
user-facing surface — no LLM judge, no embedding similarity, no
aesthetics. Each predicate is a testable bool.
The six predicates:
no_placeholder — no ..., <pending>, <prior>, <empty>
no_provenance_only — surface is not bare structured disclosure
complete_punctuation — ends with . / ? / ! / ;
finite_predicate_shape — at least one finite-verb token present
no_dotted_inventory — no 3+ dotted-paths joined by ;
surface_provenance_match — grounding_source agrees with surface text
Each is a regex / substring check. Subjective fluency (rhythm,
idiom, register) is deliberately out of scope — that would require
an LLM judge (doctrine violation) or human review (not CI-pinnable).
Baseline measured on current main (this commit, all v1 public cases):
cases: 15
no_placeholder_rate: 1.0000 (hard floor — pinned)
complete_punctuation_rate: 1.0000 (hard floor — pinned)
finite_predicate_shape_rate: 1.0000 (>= 0.90 — pinned)
no_provenance_only_rate: 1.0000 (varies — lift target)
no_dotted_inventory_rate: 0.3333 (varies — lift target)
surface_provenance_match_rate: 1.0000
expected_predicates_pass_rate: 1.0000 (per-case contracts hold)
The dotted-inventory rate at 33% is the exact gap the gloss feature
is designed to close. Today 10 of 15 cases emit surfaces like
doubt — pack-grounded (en_core_meta_v1):
meta.mental_state.uncertainty; meta.mental_state; cognition.epistemic.
No session evidence yet.
After glosses land:
Doubt is a mental state of uncertainty about a claim.
Pack-grounded (en_core_meta_v1).
The lane records both metrics today; thresholds are extended in the
gloss-wiring commit so the rates DROP if the lift fails to land.
Files:
evals/deterministic_fluency/contract.md
The six predicates with implementation notes and pass thresholds.
Documents which thresholds are pinned today vs. which are gloss-
landing lift targets.
evals/deterministic_fluency/public/v1/cases.jsonl
15 cases across four categories: pack_definition (10),
oov_invitation (2), cause_no_chain_unknown_domain (2),
teaching_grounded (1). Each case declares its own
``expected_predicates`` — the subset of the six it must satisfy
today; e.g. OOV cases don't assert finite_predicate_shape because
the invitation surface is intentionally explanatory.
evals/deterministic_fluency/dev/cases.jsonl
2 representative cases for fast iteration.
evals/deterministic_fluency/runner.py
Six predicate functions + framework-compliant run_lane. Returns
per-predicate rates + per-case predicate dicts so debugging a
regression is one read of case_details away.
tests/test_deterministic_fluency_lane.py
14 contract tests covering: case-set integrity, valid predicate
names, lane discovery, every predicate rate emitted, per-case
predicates dict carries every signal, the three hard invariants
(no_placeholder == 1, complete_punctuation == 1,
finite_predicate_shape >= 0.90), expected_predicates_pass_rate
== 1 (every case satisfies its own contract), lift-target
metrics are recorded for the gloss-feature substrate.
Verification: 14/14 lane tests green on current main.
Asymmetric counterpart to cold_start_grounding. Builds the
measurement substrate for the Phase B1 pipeline-override usefulness
gate. Lane is committed now (red baseline measured) so the fix is
landed against a fixed regression target.
The 2026-05-19 design review surfaced the bug this lane catches:
> pipeline overrode a runtime surface with a placeholder realizer
> surface because realized_plan.surface was non-empty, even though
> it contained '...'. The runtime audit log still held a different
> surface. This is the central fluency/design fault: the system
> can be "green" while user-facing selection, pipeline selection,
> and telemetry selection disagree.
The lane reproduces this exactly on the current main:
Surface "Soon is defined as ..." emitted on turn 2 of "What does
soon mean?" (where turn 1 grounded as pack correctly). Telemetry
recorded a different surface than the pipeline returned.
Initial red baseline (THIS commit):
no_placeholder_rate = 0.4444 (target after Phase B1: 1.00)
telemetry_consistency_rate = 0.4444 (target after Phase B1: 1.00)
warm_grounding_stability = 0.0000 (target after Phase B1: >=0.95)
Cold-start-grounding stays at 1.00 on its own metrics. The cold lane
measures routing, the warmed lane measures override discipline; they
are deliberately not the same.
Files:
evals/warmed_session_consistency/contract.md
What is measured, why, and the asymmetry with cold_start_grounding.
Documents the four binary per-turn signals (no_placeholder,
pipeline_match_telemetry, pipeline_match_walk, grounded_holds_on_warm)
and the per-case warm_grounding_stable invariant.
evals/warmed_session_consistency/public/v1/cases.jsonl
8 cases / 18 turns. Mix of:
- replay-the-same-prompt (catches override drift)
- mixed-intent sequences (catches OOV / pack interaction)
- cause-no-chain (must stay none across replays)
- what-does-x-mean (the warmed variant of the cold-start test)
evals/warmed_session_consistency/dev/cases.jsonl
2 representative cases for fast iteration.
evals/warmed_session_consistency/runner.py
Framework-compliant run_lane(cases, config=None) -> LaneReport.
Constructs ONE ChatRuntime + CognitiveTurnPipeline per case,
plays the turn sequence through them. Per-turn signals:
no_placeholder — surface free of ..., <pending>, <prior>
telemetry_match — pipeline result.surface == turn_log[-1].surface
grounding_match — actual_grounding == expected_grounding
Per-case signal:
warm_grounding_stable — every replayed prompt produces the same
grounding across turns
tests/test_warmed_session_lane.py
8 contract tests covering: case-set integrity, replay-pattern
presence, lane discovery, runner emits every required metric,
per-turn details carry all signals, and the warmed-runtime
invariant (static check that ChatRuntime is constructed
per-case, not per-turn and not module-scope).
NOT pinned in this commit (deliberate):
Threshold assertions are NOT in the test file. They will land in
Phase B1 alongside the pipeline-override usefulness gate. This
lane's role at present is to PROVIDE the regression target, not
to enforce it before the fix.
Verification: 8/8 lane tests green; the lane itself runs and emits
the red metrics documented above.
Three independent hygiene fixes named in the 2026-05-19 design review.
All small, all observable, none architectural.
1. ``RuntimeConfig`` flag drop on pack_id / frame_pack override
chat/runtime.py:306-320 used to enumerate fields by hand when
reconstructing RuntimeConfig under the pack_id / frame_pack
override path. The list stopped at ``admissibility_margin`` and
silently dropped FIVE newer flags: identity_pack, ethics_pack,
forward_graph_constraint, composed_surface, thread_anaphora.
Caller side-effect:
ChatRuntime(pack_id="x", config=RuntimeConfig(composed_surface=True))
.config.composed_surface == False # silently lost
Fix: ``dataclasses.replace(config, input_packs=..., frame_pack=...)``.
Every field on the dataclass survives by construction; future
additions never need a synchronized edit on this path.
2. Stale CAUSE / VERIFICATION docstring
tests/test_intent_classification_extensions.py described a sixth
runtime-side fix (pack_grounded_surface fallback for
CAUSE/VERIFICATION) that was considered, reverted, and the file's
own test classes pin the opposite contract. Docstring now states
the doctrine correctly: no fallback, deliberately, so the discovery
layer can log the teaching-gap signal.
3. Thin convenience wrappers: respond / achat / arespond
tests/test_achat.py and tests/test_language_pack_runtime.py
referenced these public methods since 2026-05-14, but they were
never implemented on ChatRuntime — those 12 tests had been red on
every full-lane run since the rebase. Added as thin wrappers:
respond(text) -> ChatResponse.surface
achat(text) -> async wrapper around chat()
arespond(text)-> async wrapper around respond()
The async wrappers are deliberately NOT genuinely non-blocking —
the underlying CPU-bound walk/recall/composition remains sync.
Docstrings say so explicitly. Callers needing real concurrency
should wrap in asyncio.to_thread at the call site; promoting the
wrappers to true async event-loop integration is a future change
gated by an actual concurrent caller.
Regression coverage:
tests/test_runtime_config_passthrough.py — 4 tests
- all 19 RuntimeConfig fields survive a pack_id override
- all five newer flags survive a frame_pack override
- no-override path preserves caller config by identity (no rebuild)
- the four public methods exist and are callable
Verification:
44/44 affected tests green (was 12 red pre-fix).
Cognition eval byte-identical on both splits.
No surface-format change; this commit is pure plumbing.
Commits the 2026-05-19 probe as a durable, replayable eval lane.
This is *step 1* of the gloss-feature rollout sequence agreed
upstream: establish a stable measurement substrate before any
further intent/grounding changes, so the 52%→0% lift (and any
future regression) is reproducible and CI-pinned.
The lane is deliberately named ``cold_start_grounding`` rather than
``fluency``:
- It measures **routing** (intent → grounding source), not
sentence quality, morphology, or surface diversity.
- The cold-start qualifier reflects the fresh-``ChatRuntime()``-
per-case design. Re-using a runtime across cases would
contaminate the vault from earlier turns and was the exact bug
observed during the probe before the per-case-runtime fix.
Files:
evals/cold_start_grounding/contract.md
Lane contract: what is measured, scoring rubric, pass thresholds
(intent ≥ 0.95 / grounding ≥ 0.95 / subject ≥ 0.90), and the
rationale for the deliberate non-fallback on CAUSE/VERIFICATION
without teaching chains.
evals/cold_start_grounding/public/v1/cases.jsonl
44 cases across 16 categories. Each case carries id, prompt,
category, expected_intent, expected_grounding_source, and an
optional expected_subject. Categories cover every intent
pattern fixed in b52e04a (Define, What-does-X-mean, infinitive,
How-does-X-work, What-causes-X) plus OOV controls and CAUSE
cases with/without teaching chains.
evals/cold_start_grounding/dev/cases.jsonl
5 representative cases for fast local iteration.
evals/cold_start_grounding/runner.py
Framework-compliant ``run_lane(cases, config=None) -> LaneReport``.
Constructs a fresh ChatRuntime() inside ``_run_case`` (cold-start
invariant). Emits intent_accuracy, grounding_accuracy,
subject_accuracy, full grounding distributions, and a per-
category breakdown for regression attribution.
tests/test_cold_start_grounding_lane.py
16 contract tests covering: case-set integrity, valid enum
values, unique ids, lane discovery, pass thresholds, expected-
vs-actual distribution match (drift detection), the architectural
invariants on oov_control and cause_no_teaching_chain cases, the
cold-start invariant (static check that the runner constructs
ChatRuntime() inside the per-case helper, not at module scope),
and result JSON-serialization round-trip.
Baseline metrics (this commit, all v1 public cases):
intent_accuracy: 1.0000 (44/44)
grounding_accuracy: 1.0000 (44/44)
subject_accuracy: 1.0000 (44/44)
grounding distribution (actual == expected exactly):
pack: 37
oov: 4
teaching: 1
none: 2 (deliberate — CAUSE without teaching chain)
Why "none" cases are *expected* to ground as none:
CAUSE / VERIFICATION on a pack-resident lemma WITHOUT an active
teaching chain stays grounding_source='none' on purpose. Falling
through to pack_grounded_surface here would mask the discovery-
candidate signal the teaching pipeline uses to identify chains
worth authoring. The contract test in
TestArchitecturalInvariants::test_cause_no_chain_cases_route_to_none
pins this doctrine.
Verification: 16/16 lane tests green; full lane run via
``core eval cold_start_grounding`` reports 100% on every metric.
Subsequent steps in the agreed sequence (NOT in this commit):
2. Hygiene: runtime API wrappers (achat/arespond/respond) + the
stale CAUSE/VERIFICATION docstring in
tests/test_intent_classification_extensions.py.
3. Harden gloss resolver in feat/pack-glosses-wip
(lexicon-residency check, dual checksum, cache clearing,
malformed-JSONL skip tests).
4. Wire gloss-backed pack_grounded_surface().
5. Author starter glosses with checksum discipline.
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
Workstream 1 eighth pack. Closes the polarity-marker + frequency-
adverb gap. Common conversational markers (yes/no/maybe/always/never)
had zero coverage in any prior pack.
Pack composition (16 entries — 2 INTJ / 14 ADV):
polarity.affirm.* yes indeed surely definitely
polarity.negate.* no hardly
polarity.uncertain.* maybe perhaps
polarity.frequency.* always sometimes often rarely never
usually occasionally frequently
``certain``/``certainly``/``uncertain`` deliberately excluded — those
remain in en_core_attitude_v1 (epistemic.certainty/uncertainty).
Regression test pins the invariant.
tests/test_correction_topic_lemma.py:
Three fixtures swapped from "No that is wrong" to "Nope that is
wrong". ``no`` is now correctly pack-resident in en_core_polarity_v1
(polarity.negate.dissent), so the "no pack-resident lemma" contract
these tests pin needed a fixture where every content token is
genuinely OOV. ``nope`` is OOV across all 10 mounted packs; ``wrong``
remains OOV (collision with attitude's ``right`` blocked spatial-
direction ``right`` but did not add ``wrong``).
Authoring:
Three parallel subagents — affirm / negate+uncertain / frequency.
Workstream 1 sixth pack. Closes the spatial-vocabulary gap. Prior
packs had zero coverage of here/there, location nouns, or spatial
prepositions.
Pack composition (24 entries — 7 ADV / 8 ADP / 9 NOUN):
spatial.deictic.* here there (2 ADV)
spatial.direction.* forward backward left up down (5 ADV)
spatial.relation.* near far above below inside outside
between beyond (8 ADP)
spatial.noun.* place location area region space
end top bottom side (9 NOUN)
``right`` was deliberately omitted — en_core_attitude_v1 already owns
it as evaluative.positive, and first-match-wins resolution preserves
that claim. A regression test pins this invariant explicitly.
Files: lexicon.jsonl / manifest.json + 12 contract tests.
Verification: full lane 2204 passed / 2 skipped / 0 failed.
Cognition eval byte-identical both splits.
Workstream 1 fifth pack. Closes the quantifier + basic-numeric gap.
Prior packs had zero coverage of universal / existential / comparative
quantifiers — queries about *all*, *some*, *many*, *more*, *most* all
fell through to OOV.
Pack composition (24 entries — mixed POS, 18 DET / 3 NUM / 2 ADJ / 1 NOUN):
quantitative.universal.* (6 DET) all every each both none neither
quantitative.existential.* (6 DET) some any several few many much
quantitative.comparative.* (6 DET) more less fewer most least enough
quantitative.numeric.* (3 NUM) one two three
quantitative.unit.* (3 mix) single (ADJ) half (NOUN) whole (ADJ)
The composer is POS-agnostic; surface composition uses
``semantic_domains`` rather than POS, so DET/NUM/ADJ/NOUN entries all
surface identically.
Files:
language_packs/data/en_core_quantitative_v1/
lexicon.jsonl — 24 entries, SHA-256 checksum-sealed
manifest.json — operational_base / D0
chat/pack_resolver.py
Appended to DEFAULT_RESOLVABLE_PACK_IDS after action.
core/config.py
Added to RuntimeConfig.input_packs default mount.
tests/test_en_core_quantitative_v1_pack.py
11 contract tests (load / POS-dist / namespace / no-collision /
contiguous-ids / mount / resolver-order / routing / invariance).
Authoring:
Three parallel subagents — universal+existential / comparative /
numeric. Strict exemplar + forbidden-lemma list against all 7
prior packs.
Verification:
Full lane: 2192 passed, 2 skipped, 0 failed.
Cognition eval byte-identical on both splits.
Workstream 1 fourth pack. Closes the common-action verb gap. Prior
packs covered reasoning (cognition), speech/perception (meta), and
adjectives (attitude); this pack covers what an agent *does*.
Pack composition (26 VERB entries):
action.doing.perform do perform execute carry conduct
action.doing.make make
action.doing.achieve achieve accomplish
action.creating.originate create build form produce generate develop
action.changing.transform change transform
action.moving.translate move
action.moving.depart_arrive go come
action.moving.transfer send receive
action.possessing.acquire get take
action.possessing.transfer give
action.possessing.retain keep
action.possessing.deploy use
Files:
language_packs/data/en_core_action_v1/
lexicon.jsonl — 26 entries, SHA-256 checksum-sealed
manifest.json — operational_base / D0
chat/pack_resolver.py
Appended to DEFAULT_RESOLVABLE_PACK_IDS after temporal.
core/config.py
Added to RuntimeConfig.input_packs default mount.
tests/test_en_core_action_v1_pack.py
11 contract tests covering load / POS / namespace / no-collision /
contiguous-ids / mounted-by-default / resolver-order / routing /
prior-pack invariance.
tests/test_procedure_surface.py
Swapped two test fixtures from "do stuff" to "fix bugs". ``do``
is now correctly pack-resident in en_core_action_v1 (semantically
correct — "How do I do stuff?" should ground on ``do``), so the
"no pack lemma exists" contract needed a fixture where both verb
and noun are genuinely OOV. ``fix bugs`` satisfies this across
all 7 mounted packs.
Authoring:
Three parallel subagents — doing / creating / moving+possessing.
Strict exemplar + forbidden-lemma list against all 6 prior packs.
Verification:
Cognition eval byte-identical on both splits (100/100/91.7/100 and
100/100/83.3/100).
All 70 pack tests pass (cognition + meta + attitude + temporal +
action + quant tests run together).
Live composer probes confirm every action lemma surfaces
deterministically from en_core_action_v1.
Workstream 1 third pack. Closes the temporal-vocabulary gap — prior
to this pack zero time/sequence/aspect terms existed in any mounted
English pack, so queries about *when*, *before*, *after*, *now*,
*future*, *past* all fell through to OOV.
Pack composition (28 entries, mixed POS — 12 ADV / 9 NOUN / 5 ADP /
1 SCONJ / 1 ADJ):
temporal.deictic.* (10 ADV) now today tomorrow yesterday soon
later recently eventually currently
formerly
temporal.relative.* (9 mix) before after during while until since
ago prior henceforth
temporal.noun.* (9 NOUN) moment period duration instant era
future past present time
The pack composer is POS-agnostic — surface composition uses the
ratified ``semantic_domains`` list rather than the POS tag. Mixed-POS
entries surface identically to noun/verb entries.
Files:
language_packs/data/en_core_temporal_v1/
lexicon.jsonl — 28 entries, SHA-256 checksum-sealed
manifest.json — operational_base / D0 / checksum-verified
chat/pack_resolver.py
Appended to DEFAULT_RESOLVABLE_PACK_IDS after attitude.
core/config.py
Added to RuntimeConfig.input_packs default mount.
tests/test_en_core_temporal_v1_pack.py
11 contract tests: checksum, POS-distribution invariant, primary-
domain namespace, no-collision regression gate against all 5 prior
packs, contiguous entry_ids, mounted-by-default, resolver-order
invariant, routing correctness, and prior-pack resolution unchanged.
Authoring:
Three parallel subagents — deictic / relative / nouns. Strict
exemplar + forbidden-lemma list against all 5 prior packs.
Verification:
Full lane: 2170 passed, 2 skipped, 0 failed (+11 new tests).
Cognition eval byte-identical on both splits.
Live composer probes confirm every temporal lemma surfaces
deterministically from en_core_temporal_v1.
Workstream 1 second pack. Closes the ADJ POS gap — prior to this pack
zero adjectives existed in any mounted English content pack, so the
runtime could not emit grounded surfaces for predicative queries like
"What is true?" or "What is important?".
Pack composition (40 ADJ entries):
attitude.truth_value.* (8) true false valid invalid accurate
inaccurate factual sound
attitude.evaluative.* (6) good bad right better worse best
attitude.epistemic.* (10) certain uncertain possible impossible
likely unlikely probable clear obscure
evident
attitude.modal.* (4) necessary sufficient required optional
attitude.importance.* (6) important essential relevant central
primary useful
attitude.scope.* (6) general specific broad narrow universal
particular
Files:
language_packs/data/en_core_attitude_v1/
lexicon.jsonl — 40 entries, SHA-256 checksum-sealed
manifest.json — operational_base / D0 / checksum-verified
chat/pack_resolver.py
Appended to DEFAULT_RESOLVABLE_PACK_IDS after cognition + meta.
core/config.py
Added to RuntimeConfig.input_packs default mount.
tests/test_en_core_attitude_v1_pack.py
11 contract tests: checksum, POS=ADJ uniformity, primary-domain
namespace, no-collision regression gate against all 4 prior packs,
contiguous entry_ids, mounted-by-default, resolver-order invariant,
routing correctness, and cognition+meta resolution unchanged.
Authoring:
Three parallel subagents (1 per cluster) — truth/eval, epistemic/modal,
importance/scope. Strict exemplar + forbidden-lemma list against all
prior packs. Main pass assembled, validated, sealed.
Verification:
Full lane: 2159 passed, 2 skipped, 0 failed (+11 new tests over the
previous 2148 baseline).
Cognition eval byte-identical on both splits:
public 100 / 100 / 91.7 / 100
holdout 100 / 100 / 83.3 / 100
Live composer probes: every ADJ lemma emits a deterministic
pack-grounded surface from en_core_attitude_v1.
Workstream 1 (pack content scale-up) first load-bearing step.
Adds a new ratified content pack covering the conversational vocabulary
en_core_cognition_v1 deliberately omits — speech acts, mental states,
perception, self-reference, and discourse-object nouns. These are the
lemmas that show up in nearly every model response and that previously
fell through to the OOV invitation surface.
Pack composition (73 entries, 49 VERB + 24 NOUN):
meta.speech_act.* (20 verbs) say tell speak reply claim state
describe express name mention note
observe declare assert deny confirm
suggest propose articulate respond
meta.mental_state.* (18 verbs) know believe think suppose assume
expect hope want prefer doubt wonder
guess recognize realize consider intend
decide hold
meta.perception.* (11 verbs) see hear feel sense perceive watch
look listen find detect notice
meta.self_reference.* (10 nouns) self mind view perspective position
role agent model system speaker
meta.discourse.* (14 nouns) response reply statement fact idea
point argument proposal suggestion
case instance example kind type
Files:
language_packs/data/en_core_meta_v1/
lexicon.jsonl — 73 entries, SHA-256 checksum-sealed
manifest.json — operational_base / D0 / checksum-verified
chat/pack_resolver.py
Appended en_core_meta_v1 to DEFAULT_RESOLVABLE_PACK_IDS after
en_core_cognition_v1 so cognition lemma resolution stays first-
match-wins on any future collision (preserves cognition-lane
byte-identity invariant).
core/config.py
Added en_core_meta_v1 to RuntimeConfig.input_packs default mount.
tests/test_en_core_meta_v1_pack.py
11 contract tests: checksum-verified load, POS split, primary-
domain namespace, no-collision-with-cognition-v1 regression gate,
pack registration order, resolver routing, and cognition-lemma
resolution unchanged.
tests/test_procedure_surface.py
Swapped two test fixtures from "claim" to "hypothesis". ``claim``
is now correctly pack-resident (meta.speech_act.claim) so the
procedure composer's object-first selector picks it over the verb
— the new behavior is semantically correct. ``hypothesis`` is
genuinely OOV across all mounted packs and preserves the verb-
fallback contract these tests pin.
Authoring methodology:
Four parallel subagents authored one cluster each from a strict
exemplar + word list + forbidden-lemma list (every en_core_cognition_v1
lemma listed explicitly to prevent collision). Each subagent wrote
only its cluster JSONL; the main pass assembled, validated, computed
the SHA-256 over bytes-on-disk, and wrote the manifest.
Verification:
Full lane: 2148 passed, 2 skipped, 0 failed (+11 new tests).
Cognition eval byte-identical on both splits:
public 100 / 100 / 91.7 / 100
holdout 100 / 100 / 83.3 / 100
Live runtime probes: fresh ChatRuntime() for "What is X?" with
X ∈ {fact, doubt, statement, model, self} all emit a
pack-grounded sentence from en_core_meta_v1.
OOV path still honest for genuinely-unknown terms (e.g. hypothesis).
Scope note:
This is one pack of ~70 lemmas, not "the model now articulates
open-domain English." The architecturally-honest articulation
story still requires more pack and teaching-chain content; this
pack moves the conversational-substrate boundary forward by ~70
lemmas in one ratifiable, replay-stable step.
Phase 5 (ADR-0067 follow-up):
teaching/cross_pack_supersede.py — supersede_cross_pack_chain()
CLI: core teaching supersede ... --cross-pack
--subject-pack-id ... --object-pack-id ...
Strict per-chain residency, anti-leakage, byte-identical rollback
on any post-append re-load failure. 9 new tests.
Articulation benchmark suite (Phase 4 capability proof):
benchmarks/articulation.py — 5 sub-benches
[1] breadth — every intent shape (9 + OOV + cross-pack)
[2] determinism — N reruns / unique-surface count
[3] footprint — psutil RSS profile across T turns
[4] cross-topic — thread context across mixed subjects
[5] ollama-compare — opt-in side-by-side with local Ollama
CLI: core bench --suite articulation
--runs N (det rerun count)
--turns N (footprint sample window)
--ollama-model MODEL --ollama-reruns N
Full operator preamble + JSON report path.
10 new tests cover the bench shape (psutil import-skipped).
Documentation:
benchmarks/README.md — full operator manual: catalogue of every
bench suite, how to read good/neutral/bad results for each sub-
bench, why CORE vs Ollama comparisons are valid on the
determinism axis and not on linguistic quality, workflow guide.
README.md — articulation bench listed in the live-demo grid and
quick-start examples.
Reference run (llama3:8b, 100 turns, 5 reruns):
determinism_all_identical=True
per-turn ΔRSS ≈ 23 KiB
CORE byte_identical_on_every_prompt=True
Ollama unique_surfaces≥2 on every prompt
Verification:
18 new tests pass
Full lane: 2116 passed, 2 skipped, 0 failed in 2:38