docs(adr): draft ADR-0022 — Forward Semantic Control

Skeleton (Status: Draft) for the bridge between graph and field.
Today the proposition graph and the field walk run in parallel —
the graph does not constrain field propagation, and the field
does not prove the graph. This ADR commits to making semantic
structure causally active inside propagation: graph computes an
admissibility region, propagation must satisfy it or fail
honestly, no template authoring, no sampling, no symbolic
planner regression.

Explicitly marked Draft because 5 TBD items must close before
promotion to Proposed:
  TBD-1 intent oracle (regex classifier as load-bearing oracle
        recreates the gap one level up)
  TBD-2 region intersection algebra (frame × relation × identity
        composition — closed rotor operator not yet proven)
  TBD-3 backward-compat window removal mechanism
  TBD-4 identity manifold as constraint source (v1 scope decision)
  TBD-5 pack semantic depth — cognition pack may need targeted
        extensions before lane can pass

Acceptance criteria for Draft → Proposed → Accepted explicit.
Trust-boundary review (CLAUDE.md §Security) included.
Implementation sequencing sketched (7 steps, each its own PR).

Motivated by 2026-05-17 external assessment. The assessment is
input to the ADR, not authority over it.

Verified: no code changes; ADR is documentation only.
This commit is contained in:
Shay 2026-05-17 11:02:25 -07:00
parent 79a4125d24
commit 16baa51368

View file

@ -0,0 +1,342 @@
# ADR-0022 — Forward Semantic Control
**Status:** Draft (skeleton — sections marked **TBD** require design work
before promotion to Proposed)
**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:
1. **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 feeds
`walk_surface` (telemetry per `docs/runtime_contracts.md`).
2. **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:
```text
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)
The current `generate/intent.py` is rule-based regex over raw text.
Forward semantic control on top of a non-geometric classifier
recreates the same gap one level up — the classifier becomes the
oracle the field defers to.
**TBD design question:** what is the smallest deterministic
field-grounded intent operator that can replace or supplement the
regex classifier without re-importing sampling? Candidates to
evaluate:
- Construct intent from the prompt versor's projection onto a
frame-relation manifold (deterministic, exact).
- Treat the regex classifier as a *seed* that the field must
ratify; reject the intent if the prompt versor lies outside the
intent's admissible region.
- Defer to v2; ship v1 with regex classifier explicitly marked as
the load-bearing oracle and a `bench intent_field_coupling` lane
that measures the gap.
The decision must be made before this ADR is promoted from Draft to
Proposed.
## Code impact (planned, not yet implemented)
### Modified
- `generate/proposition.py`
- `propose()` consumes an `AdmissibilityRegion` parameter (default
`None` preserves current behavior during transition).
- Subject/predicate/object selection restricted to region.
- `generate/stream.py`
- `_recall_state` accepts an `AdmissibilityRegion`; rejects rotor
transitions that exit it.
- `_nearest_next` accepts admissible-node candidate set.
- `generate/graph_planner.py`
- `plan_articulation` returns both the existing `ArticulationTarget`
and a new `AdmissibilityRegion`.
- `field/propagate.py`
- Adds a region-aware propagation variant. Existing
`propagate_step` retained for paths that do not yet pass a
region (deprecation roadmap TBD).
- `core/cognition/pipeline.py`
- Drops the parallel-then-override pattern in favor of single
region-constrained generation.
- Failure surface routes through the existing unknown-domain
path (ADR-0021 §Articulation alignment).
### New
- `generate/admissibility.py`
- `AdmissibilityRegion` dataclass + 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-6` remains 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
This ADR is promoted from Draft to Proposed only when ALL hold:
1. **Eval lane exists.** `evals/forward_semantic_control/` with at
least one case that *only the constrained walk passes*. Lane
contract written, runner skeletonised, dev cases drafted.
2. **Determinism invariant designed.** Test fixture that proves
same `(graph, field, region) → same surface` byte-for-byte
across runs and across the two backend implementations
(Python + Rust, when parity lands per ADR-0020).
3. **Failure surface designed.** Specified what the user sees when
no admissible transition exists. Must reuse the existing
refusal surface from `refusal_calibration` for honesty
consistency.
4. **Intent oracle question answered.** §Decision item 3 has a
concrete v1 path written, not deferred.
5. **No anti-patterns reintroduced.** A code-reviewer pass
verifies none of the forbidden shapes (template authoring,
sampling, symbolic planner, learned ranking) appears in any
proposed module.
This ADR is promoted from Proposed to Accepted only when ALL hold:
6. The eval lane in (1) passes against the implementation.
7. Existing lanes (`refusal_calibration`,
`articulation_of_status`, `contradiction_detection`,
`teaching_injection_resistance`, `cognition`) remain green
under the change.
8. `bench cost` and `bench footprint` show no regression beyond
a budget stated in this ADR before promotion (the constraint
layer should narrow candidate space earlier; a *speedup* is
expected, a slowdown is the surprise to investigate).
## Named gaps and open questions
- **TBD-1 — Intent oracle.** See §Decision item 3.
- **TBD-2 — Region intersection algebra.** When the frame, the
active typed relation, and the identity manifold each impose
constraints, how do they compose? Set intersection on candidate
sets is the obvious answer for tokens; for *rotors* the
composition needs a closed operator. Likely candidate:
conjugation under the frame versor, but the closure proof is
not yet written.
- **TBD-3 — Backward compatibility window.** The constrained and
unconstrained paths must coexist while the eval lane is built
and while existing lanes are migrated. Default `region=None`
preserving current behavior is the obvious bridge but creates
the temptation to leave it on permanently. A removal date or
removal-blocker test is needed.
- **TBD-4 — Identity manifold as constraint source.** The
external assessment correctly notes identity can feed
admissibility (`same graph, different identity manifold →
different admissible transitions → different articulation
trajectory`). The mechanism is plausible but the operator is
not specified. v1 may exclude this; v1 must say so explicitly.
- **TBD-5 — Pack semantic depth.** Forward control over a thin
pack will look like over-constraint ("nothing is admissible →
refuse everything"). The cognition pack
(`en_core_cognition_v1`) may need targeted extensions before
the lane can pass. Required pack work to be enumerated in the
v1 implementation PR.
## 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-6`
remains 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 (proposed)
This is a roadmap sketch, not a commitment. Each step blocks the next.
1. Draft → Proposed: close all TBD items above. Land
`evals/forward_semantic_control/` skeleton + dev cases.
2. Proposed → in-progress: implement `generate/admissibility.py`
pure-function module. No call sites changed yet.
3. Wire `propose()` first (smallest surface change). Run all
existing lanes; no regressions allowed.
4. Wire `_recall_state` and `_nearest_next`. Run eval lane and
`bench cost`. Cost regression is a stop-the-line signal.
5. Drop the pipeline parallel-then-override pattern. Single
region-constrained generation path.
6. Migrate `field/propagate.py` callers off the unconstrained
variant. Mark TBD-3 removal-blocker test.
7. Rust parity (ADR-0020 sequence): port the constrained
propagation operator. Byte-identical or no ship.
Each step ships as its own ADR-bound PR with its own evidence.
## References
- CLAUDE.md — non-negotiable field invariant, normalization
doctrine, surface contract, work sequencing.
- `docs/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.md` Tier 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.