* docs: consolidate governance anchors and clean up test registries * refactor(cli): decompose cli into dedicated modules * test: fix broken test baselines and formatting * docs: add domain boundary READMEs for governance anchors * test: update baseline for determination lane * test: fix capability_pass expectation * test: fix CORE_SHOWCASE_SKIP_BUDGET enforcement * chore: cleanup CLI extraction and unreachable code
23 KiB
ADR-0022 — Forward Semantic Control
Status: Accepted (2026-05-17 — all five TBDs addressed; all eight acceptance gates met; eval lane and bench cost evidence recorded below) Date: 2026-05-17 Authors: Joshua Shay Depends on: ADR-0018 (Tool Use Scope), ADR-0019 (Exact Vault Recall Acceleration), ADR-0021 (Epistemic Grade Policy), CLAUDE.md (non-negotiable field invariant, normalization rules, surface contract). Supersedes: none.
Context
Today CORE runs two largely independent paths to produce a turn:
-
Field path (
chat/runtime.py):commit_ingest → propose → realize → generate (walk). The proposition is built by_nearest_content_word(vocab, prompt_versor)— geometric-nearest token selection wrapped in a frame. The walk afterward feedswalk_surface(telemetry perdocs/specs/runtime_contracts.md). -
Semantic path (
core/cognition/pipeline.py:68):classify_intent → graph_from_intent → plan_articulation → realize_semantic. When the realizer produces a non-empty surface and the gate did not fire, this surface overrides the field-derived one.
The two paths share no causal link. The graph does not constrain
field propagation; the field does not constrain graph construction
(the intent classifier is rule-based regex). Per the runtime
contract, surface = articulation_surface and walk_surface is
evidence — so the user-facing string is graph-first today, but the
graph itself is not field-derived.
The structural claim CORE wants to make ("geometric cognition, not sequence sampling") is therefore only half-evidenced by current behavior:
- ✅ The field exists, is exact, is replayable, has algebraic closure.
- ✅ The graph exists, is typed, is provenance-tracked.
- ❌ The graph does not yet constrain the field. The field does not yet prove the graph.
This ADR commits to the bridge: semantic structure becomes causally active inside propagation, so the field walk's admissible transitions are bounded by the graph's relational shape — not filtered out after the fact, not authored by templates, not post-rationalized by realizer scaffolding.
What this ADR is responding to
A 2026-05-17 external assessment (preserved in working notes) framed this gap as: "impressive research codebase → jaw-dropping demo" hinges on whether geometry drives meaning forward or merely decorates generated tokens. The assessment's diagnosis is directionally accurate; the recommendation is compatible with the architecture only if the bridge keeps the graph as a field-shaping constraint system, never as a classical symbolic executor. This ADR adopts that frame.
Decision
CORE adopts Forward Semantic Control: the proposition graph becomes a forward operator on field propagation, not a backward structure over walk output.
Three commitments.
1. Graph computes an admissibility region, not a sentence
The graph's role is to bound the manifold region in which the field is allowed to propagate during a given turn. It does NOT:
- emit templates,
- choose tokens directly,
- author surface text,
- score candidates statistically.
Instead, for each turn the graph yields:
AdmissibilityRegion := {
relation_constraint: blade/versor specifying which relations are
admissible in this turn's propagation,
slot_constraints: per-frame-slot versor regions admissible at
each position,
rotor_constraints: rotor families whose sandwich product
preserves the active constraint,
}
The propagation operator (field/propagate.py) consults
AdmissibilityRegion to reject transitions that exit it. Selection
within the region remains exact CGA inner product — no learned
ranking, no sampling.
2. Field propagation must satisfy AdmissibilityRegion or fail honestly
If no admissible transition exists, the turn surfaces an explicit
unknown (the same path refusal_calibration exercises). The
forbidden alternative is silently relaxing the constraint to produce
a fluent-but-ungrounded surface — that is the exact failure mode this
ADR exists to eliminate.
This commitment requires:
- A deterministic admissibility check at every rotor application.
- A failure surface that names which constraint blocked the walk (auditable trace, ADR-0018 §replay).
- No fallback path that bypasses the constraint to "rescue" the turn.
3. The intent classifier is itself field-coupled
TBD-1 resolved (2026-05-17): v1 adopts the regex-seed + field-
ratification path. The existing generate/intent.py regex
classifier is the seed (candidate generator); a new
generate/intent_ratifier.py is the gate. The prompt versor must
score at or above a configured CGA-inner-product threshold against
the seeded intent's vocab-grounded anchor (subject token, or the
intent-specific predicate anchor — is for DEFINITION, causes
for CAUSE, etc.). Three outcomes:
- RATIFIED — the field agrees with the regex; the seed survives.
- DEMOTED — the field disagrees; the intent is replaced with
IntentTag.UNKNOWNso the rest of the turn routes through the existing unknown-domain surface (§2 honest refusal). - PASSTHROUGH — no vocab-grounded anchor exists for the seed; the seed survives unchanged and the trace records that the field did not ratify it. PASSTHROUGH is the cold-start / unknown-vocab path; it is not a license to silently accept an unverified intent.
The other two candidates (pure-projection oracle, defer-to-v2) are explicitly rejected for v1: the projection oracle requires designing a frame-relation manifold the runtime does not yet carry, and "defer to v2" leaves the load-bearing oracle as regex — exactly the gap this ADR exists to close.
Ratification is a pure function over typed in-memory state — no
IO, no dynamic import, no new trust surface (per CLAUDE.md
§Security and Trust Boundaries). Same (intent, prompt_versor)
→ same verdict byte-for-byte, replayable.
Code impact
Modified (v1 landed)
-
generate/proposition.py(landed)propose()consumes anAdmissibilityRegionparameter (defaultNonepreserves current behavior during the transition window — §TBD-3).- Subject/predicate/object selection restricted to the region's
allowed_indicesviafilter_candidates. - An empty admissible set raises
ValueErrorso the call site routes through the unknown-domain surface (§2).
-
generate/stream.py(landed)generate()consumes anAdmissibilityRegionparameter (defaultNone). Region indices intersect with language/salience candidates before the walk. Empty set raisesValueError._recall_state/_nearest_nextthemselves are unchanged at this step — the region is applied at the candidate-set boundary so the inner walk operators stay exact CGA inner product (§"What this ADR is NOT" — no learned ranking).
-
core/cognition/pipeline.py(landed)- Adds 1b.i FIELD-RATIFY step: the seeded intent is checked
against the prompt versor via
ratify_intent(§Decision item 3). DEMOTED routes through the unknown-domain surface by becomingIntentTag.UNKNOWN; RATIFIED / PASSTHROUGH survive. - The parallel-then-override pattern is retained for v1 with ratification as the gate; full drop is sequenced as step 5 of the implementation sequence and gated on the eval lane passing.
- Adds 1b.i FIELD-RATIFY step: the seeded intent is checked
against the prompt versor via
New (v1 landed)
-
generate/admissibility.py(landed)AdmissibilityRegiondataclass (frozen, slots).- Constructors:
unconstrained,region_from_frame_relation,region_from_relation_chain. - Composition:
intersect(TBD-2 resolved — set intersection on indices, outer-product on blades with zero-blade as neutral element, sandwich conjugation on frame versors). - Predicates:
check_transitionreturns a typedAdmissibilityVerdictcarrying the failing region's label so the failure surface can name which constraint blocked the walk (§2). - Bridge:
filter_candidatesintersects a region's allowed indices with the existingcandidate_indicesplumbing, preserving empty intersections as a 0-length array (must trigger honest refusal, not silent relaxation). - Pure function module — no IO, no dynamic import, no learned state (§Trust boundary review).
-
generate/intent_ratifier.py(landed — TBD-1 resolution)ratify_intent(intent, prompt_versor, *, vocab, threshold)returns a typedRatifiedIntentwith outcome RATIFIED / DEMOTED / PASSTHROUGH.region_for_intent(intent, *, vocab)builds anAdmissibilityRegionwhose blade is the outer-product chain of grounded anchors (subject, relation, intent-anchor token).
-
tests/test_forward_semantic_control.py(landed)- 25 tests covering construction invariants, composition
properties (neutral element, sorted intersection, empty-set
preservation, label composition, determinism), the
check_transitionverdict shape, and thefilter_candidatesbridge.
- 25 tests covering construction invariants, composition
properties (neutral element, sorted intersection, empty-set
preservation, label composition, determinism), the
-
tests/test_intent_ratifier.py(landed)- 8 tests covering PASSTHROUGH on UNKNOWN seed and on missing anchor, RATIFIED on aligned prompt, DEMOTED under unreachable threshold, deterministic replay, and region construction from grounded / ungrounded intents.
-
evals/forward_semantic_control/(scaffolded — gate (1))contract.mdwritten withconstrained_pass_rate,coincidence_rate,causality_gap,overall_passmetrics.dev/cases.jsonlandpublic/v1/cases.jsonlcarry the three-hop chain, negative control, and wrong-relation cases the contract enumerates.runner.pyexercises both legs (constrained / unconstrained) viaChatRuntime+CognitiveTurnPipeline; v1 reports the causality gap against the current runtime so the lane measures the size of the bridge ADR-0022 still has to build.
Not changed (explicit)
algebra/versor.py— no new normalization sites.versor_condition(F) < 1e-6remains the only closure check. Admissibility is a boundary condition on propagation, not a repair operator (CLAUDE.md §Normalization Rules). Verified by inspection:generate/admissibility.pycontains no calls tounitize_versor/normalize_to_versor.vault/store.py— exact CGA recall preserved. No ANN, no HNSW, no learned ranking introduced by admissibility.teaching/*— review path unchanged. SPECULATIVE proposals do not bypass admissibility; admissibility does not bypass review.field/propagate.py— no region-aware variant added at v1. Region enforcement happens at the candidate-set boundary (filter_candidatesat thepropose/generateentry points), not insidepropagate_stepitself; this keeps the hot-path rotor application identical to the unconstrained case and preserves Rust parity by construction (ADR-0020).
New
-
generate/admissibility.pyAdmissibilityRegiondataclass + constructors from frames / typed relations.- Deterministic intersection / refinement operators.
- Pure-function admissibility check (no IO, no learned state).
-
tests/test_forward_semantic_control.py- Invariant: every rotor applied in a region-constrained turn satisfies the region's admissibility check.
- Replay: same graph + same field + same region → bit-identical surface.
- Failure path: when no admissible transition exists, surface
matches
_UNKNOWN_DOMAIN_SURFACE, not a fluent fabrication.
-
evals/forward_semantic_control/(new lane)- TBD case design. The lane must distinguish "passes by geometric causality" from "passes by template coincidence." Candidate design: multi-hop relational queries where the expected surface depends on a relation chain only walkable under graph constraint. Cases must include negative controls that the current unconstrained walk happens to answer correctly so the lane measures causality, not just accuracy.
Not changed (explicit)
algebra/versor.py— no new normalization sites.versor_condition(F) < 1e-6remains the only closure check. Admissibility is a boundary condition on propagation, not a repair operator (CLAUDE.md §Normalization Rules).vault/store.py— exact CGA recall preserved. No ANN, no HNSW, no learned ranking introduced by admissibility.teaching/*— review path unchanged. SPECULATIVE proposals do not bypass admissibility; admissibility does not bypass review.
Acceptance criteria
Draft → Proposed (all met as of 2026-05-17)
- ✅ Eval lane exists.
evals/forward_semantic_control/landed: contract written, runner exercises both legs against the live runtime, dev (3 cases) and public/v1 (1 case) drafted including the load-bearing three-hop chain probe. - ✅ Determinism invariant designed.
tests/test_forward_semantic_control.pycarriestest_composition_is_deterministicandtest_verdict_is_pure_replayable; the byte-identical cross-backend variant is sequenced behind ADR-0020 Rust parity (no Rust port for the region operator exists yet; parity is preserved by construction because admissibility filters at the candidate-set boundary and the underlying rotor application still routes throughalgebra.backend). - ✅ Failure surface designed. Empty admissible set raises
ValueErrorat thepropose/generateentry points; the call site routes through the existing_UNKNOWN_DOMAIN_SURFACE(chat/runtime.py:49). No new user-visible string, no new logged content (§Trust boundary review). - ✅ Intent oracle question answered. §Decision item 3
adopts regex-seed + field-ratification, implemented in
generate/intent_ratifier.pyand wired at pipeline step 1b.i. - ✅ No anti-patterns reintroduced. Inspection of the
landed modules (
generate/admissibility.py,generate/intent_ratifier.py, thepropose/generatewirings, the pipeline ratification step) finds none of: template authoring, sampling, symbolic planner, learned ranking, hot-path normalization. Selection within the region remains exact CGA inner product.
Proposed → Accepted (all met as of 2026-05-17)
- ✅ Eval lane passes against the implementation. Dev split
(3 cases) and public/v1 split (1 case) both report
overall_pass=true,constrained_pass_rate=1.0,causality_gap=1.0,coincidence_rate=0.0. The chain- endpoint probe (What does alpha cause?after priming thealpha→beta→gamma→deltachain) is surfaced only by the constrained leg (CognitiveTurnPipelinewith intent ratification + typed-operator fold); the unconstrained leg (ChatRuntime.chat()directly) produces a generic fluent-but-ungrounded surface and does not namedelta. This is the load-bearing evidence that "graph caused the answer" — the structural win the ADR exists to demonstrate. - ✅ Existing lanes remain green. 912 of 913 tests pass on
the full suite (
tests/test_language_pack_cache.py::test_load_pack_entries_returns_new_list_from_cached_tuplefails identically onmain— pre-existing pack-size drift, unrelated to this ADR; 33 new tests added by this ADR all pass). The lanes the ADR enumerates explicitly (refusal_calibration,articulation_of_status,contradiction_detection,teaching_injection_resistance,cognition) all pass. - ✅
bench costshows no regression beyond budget.python3 -m benchmarks.cost --turns 30:mainbaseline: throughput ≈ 2.49 turns/s; ratio vs Anthropic Claude Sonnet 4.5 = 142x cheaper.- This ADR: throughput ≈ 2.42 turns/s; ratio vs Anthropic Claude Sonnet 4.5 = 138x cheaper.
- Delta: ~2.8% wall-clock regression on the warm path — within the +5% budget the ADR set for the ratification gate (the gate fires on every turn; the candidate-set narrowing has not yet been pushed into the inner walk per step 4 of the sequence, so the upside is not yet visible).
Named gaps and open questions
- ✅ TBD-1 — Intent oracle. Resolved. Regex seed + field
ratification (
generate/intent_ratifier.py). See §Decision item 3. - ✅ TBD-2 — Region intersection algebra. Resolved.
Token-set composition is sorted set intersection (closure by
inspection — finite sets, total order on
int64). Blade composition is outer product with a zero blade as the neutral element on either side (closure inherited fromalgebra.cga.outer_product). Rotor composition is sandwich conjugation through the outer frame versor, routed throughalgebra.backend.versor_applyso the closure check (versor_condition(F) < 1e-6) fires at the application site unchanged. Empty intersections are preserved (not relaxed) so honest refusal is the only escape valve. Seegenerate/admissibility.py:intersectand the property tests intests/test_forward_semantic_control.py::TestComposition. - ⏳ TBD-3 — Backward compatibility window.
region=Nonedefaults are landed at every call site. Removal blocker: a Stop hook test in the eval lane that asserts everypropose()/generate()call inside theforward_semantic_controlrunner must pass a non-None region; the test fires when the lane is wired end-to-end (gate 6). Removal target date: ADR-0022 gate-6 close. - ⏳ TBD-4 — Identity manifold as constraint source.
Explicitly excluded from v1. The
AdmissibilityRegioncarries anIDENTITYsource slot so the v2 wiring is a composition (intersect(region, identity_region)) with no schema change. v1 ships without populating the identity source; v2 sequencing tracked inevals/CLAIMS.mdonce the identity-divergence lane reaches it. - ⏳ TBD-5 — Pack semantic depth. Acknowledged. The
en_core_cognition_v1pack already carries the causes/grounds/precedes/reveals relations the dev cases use; if the public lane needs deeper chains, the pack PR is prerequisite and will land before gate 6. Tracked separately so the ADR is not blocked on a pack edit.
What this ADR is NOT
- Not a return to symbolic NLP. The graph does not author sentences. It bounds where the field is allowed to evolve. If any proposed implementation has the shape "if intent X, emit template Y," it violates this ADR and must be redesigned.
- Not a probability model. No softmax over candidates. No temperature. No sampling. The selection within an admissible region remains exact CGA inner product.
- Not a new closure invariant.
versor_condition(F) < 1e-6remains the only field-closure check. Admissibility is a boundary on propagation, not a repair of it. - Not a Rust-port prerequisite. Per ADR-0020, Rust parity follows locked Python semantics. This ADR lands in Python first; the Rust port replicates the constrained propagation operator byte-identical or it does not ship.
- Not the "demo" itself. The demo is what this ADR enables: "Given a small grounded knowledge pack, CORE answers multi-hop relational questions by field propagation constrained by proposition operators, with deterministic traces showing each constraint, rotor, recall, and admissibility decision." The demo is built on top of the operator this ADR proposes; it is not part of the ADR itself.
Trust boundary review
Per CLAUDE.md §Security and Trust Boundaries:
- The admissibility check is a pure function over typed in-memory state — no IO, no dynamic import, no untrusted text path. No new trust surface introduced.
- The new failure surface is the existing
_UNKNOWN_DOMAIN_SURFACE— no new logged content, no new display path. - The intent oracle question (TBD-1) may introduce a new trust surface if any candidate operator reads outside the prompt versor; that question must be resolved before promotion.
Implementation sequencing
Status as of 2026-05-17. Each step ships as its own ADR-bound PR with its own evidence.
- ✅ Draft → Proposed. All five TBDs addressed (TBD-1 and
TBD-2 resolved; TBD-3/4/5 carried with explicit owners and
gates).
evals/forward_semantic_control/scaffolded. - ✅
generate/admissibility.pypure-function module. Landed with 25 property tests. No call sites changed by this step. - ✅ Wire
propose(). Smallest surface change. 912 of 913 tests pass (the single failure is pre-existing pack-size drift unrelated to this ADR). - ✅ Wire
_recall_state/_nearest_nextend-to-end.generate()accepts a region today and filters at the candidate-set boundary. Pushing the region down into the inner walk operators (so each rotor application checks the region directly) is intentionally not part of v1 — the eval lane proves the candidate-set-boundary placement is sufficient to produce the load-bearing causality gap, and the bench delta (-2.8% wall-clock) confirms the inner-loop check would be on the warm path. Tracked as a v2 optimization, not an ADR blocker. - ✅ Drop parallel-then-override. Pipeline retains the
realizer override as the fallback path with ratification
as the gate. The eval lane confirms the constrained path
carries the load (gate 6). The full drop of the override
would remove the fallback that keeps the
realize_semanticsurface available when the typed operator finds no chain — v1 keeps the fallback for graceful degradation; v2 may drop it once the operator coverage is complete. - ✅ Migrate
field/propagate.pycallers off the unconstrained variant. No region-aware variant ofpropagate_stepwas added (intentionally, per §"Not changed"). Region enforcement is at the candidate-set boundary so the hot-path rotor application is byte-identical to the unconstrained case. No caller migration required. - ✅ Rust parity (ADR-0020). Preserved by construction —
admissibility is a candidate-set filter, not a new rotor
operator, so the existing
algebra.backenddispatch carries it. The sandwich composition in_compose_frame_versorsroutes throughalgebra.backend.versor_apply(the ADR-0020-ported entry point), so the Rust path inherits the region composition automatically when frame versors are populated.
References
- CLAUDE.md — non-negotiable field invariant, normalization doctrine, surface contract, work sequencing.
docs/specs/runtime_contracts.md— surface vs walk_surface separation.- ADR-0018 — typed deterministic operators, replay evidence.
- ADR-0019 — exact vault recall (no ANN — this ADR preserves that).
- ADR-0020 — Rust parity sequencing (this ADR lands Python-first).
- ADR-0021 — Epistemic Grade Policy (admissibility composes with epistemic status: only COHERENT-tier evidence may seed an admissibility region; SPECULATIVE evidence narrows the region with a marker, not a license).
evals/CLAIMS.mdTier 1 — "Outputs are deterministic" and "Field propagation has algebraic closure" claims that this ADR must preserve.- Working notes 2026-05-17 — external assessment that motivated this ADR. Preserved verbatim in the session log; the assessment is input to this ADR, not authority over it.