refactor(field): rule the session anchoring family as sanctioned semantic anchoring (L10 Decision 0)

L10 scoping Decision 0 ruled the session/context.py "drift fix" family as
sanctioned SEMANTIC anchoring, not forbidden drift-repair:
- closure (versor_condition<1e-6) is owned by the sanctioned algebra/versor.py
  sandwich closure and holds BY CONSTRUCTION (measured: 100k-step field walk,
  max versor_condition ~6e-13, flat, with AND without the anchor pull);
- the family preserves the invariant by construction (rotor_power /
  word_transition_rotor / versor_apply on the Spin manifold, no post-hoc
  unitize) and expresses the session concept-attractor model.

CLAUDE.md: add session/context.py semantic anchoring to sanctioned normalization
sites behind a two-clause guard, plus a bright-line paragraph (semantic anchoring
vs drift repair; closure owned solely by algebra/versor.py; no "drift fix" naming).

Rename _anchor_pull -> _session_anchor_pull; reframe the "Drift fix 1/3" /
"conjugate correction against slow angular drift" docs as semantic anchoring
(no behavior change). Update test_session_coherence.py to the new name.

Out of scope (flagged for separate review): generate/stream.py "Drift fix 2"
sits in a forbidden normalization site.

Verified: 15 targeted tests green; INV-02 normalization invariant unaffected.
This commit is contained in:
Shay 2026-06-05 07:31:53 -07:00
parent d47741f5df
commit 81ea72482f
3 changed files with 42 additions and 18 deletions

View file

@ -60,6 +60,14 @@ Allowed sites:
- `algebra/versor.py` for algebra-owned sandwich closure. - `algebra/versor.py` for algebra-owned sandwich closure.
- `sensorium/*/canonical.py` and pack-governed modality compiler construction - `sensorium/*/canonical.py` and pack-governed modality compiler construction
boundaries for pinned signal canonicalization and quantization. boundaries for pinned signal canonicalization and quantization.
- `session/context.py` for session-scoped **semantic anchoring** of the field
toward the session concept-attractor (the anchor pull, hemisphere
consistency). Allowed ONLY because every such op (1) preserves
`versor_condition` BY CONSTRUCTION — composed from `rotor_power` /
`word_transition_rotor` / `versor_apply` on the Spin manifold, never a
post-hoc `unitize`/grade-projection — AND (2) carries semantic meaning in
the cognitive model. An op that needs a post-hoc closure repair (the
rejected `_slerp_toward`) fails clause (1) and stays forbidden.
Forbidden sites: Forbidden sites:
@ -72,6 +80,15 @@ Do not add drift repair, grade projection, watchdogs, timers, hot-path
normalizers, or monitoring functions whose only purpose is to repair another normalizers, or monitoring functions whose only purpose is to repair another
function. function.
**The bright line — semantic anchoring vs. drift repair.** An op is *semantic
anchoring* (allowed at the sites above) iff it preserves `versor_condition` by
construction AND expresses a relation in the cognitive model. It is *drift
repair* (forbidden) iff its purpose is to restore a numerical invariant a prior
function should have preserved. Closure of field transitions is owned solely
by `algebra/versor.py` (`_close_applied_versor`); no other site may "fix" it.
Naming must not disguise the distinction: an op that anchors semantically must
not be named or documented as a "drift fix".
CGA null vectors are not unit versors. Preserve null vectors as null vectors. CGA null vectors are not unit versors. Preserve null vectors as null vectors.
## Core Primitives ## Core Primitives

View file

@ -140,7 +140,8 @@ class SessionContext:
# First turn: initialise the accumulator at full blade magnitude. # First turn: initialise the accumulator at full blade magnitude.
self.running_dialogue_blade = blade.copy() self.running_dialogue_blade = blade.copy()
else: else:
# Drift fix 1: magnitude-preserving EMA accumulation. # Semantic accumulation, not drift repair (CLAUDE.md bright line):
# magnitude-preserving EMA of the confirmed concept direction.
# #
# Previously: running_blade = sign(inner) * new_blade # Previously: running_blade = sign(inner) * new_blade
# This reset magnitude to 1 on every turn, discarding how many # This reset magnitude to 1 on every turn, discarding how many
@ -192,21 +193,26 @@ class SessionContext:
valence=field_state.valence, valence=field_state.valence,
) )
def _anchor_pull(self, field_state: FieldState) -> FieldState: def _session_anchor_pull(self, field_state: FieldState) -> FieldState:
"""Drift fix 3: mild rotor-geodesic pull toward the session anchor field. """Semantic anchoring: a mild rotor-geodesic pull of the field toward the
session concept-attractor (CLAUDE.md sanctioned semantic-anchoring site;
NOT a drift repair).
Applied after hemisphere correction. Provides continuous conjugate Applied after hemisphere consistency. Expresses the model relation
correction against slow angular drift that stays within the hemisphere "this turn's field belongs to the session's concept" by nudging the
but gradually moves away from the session concept attractor. field toward the session anchor when it has drifted within-hemisphere
away from that attractor.
Computes the transition rotor R = anchor * reverse(F), scales it to Computes the transition rotor R = anchor * reverse(F), scales it to
R^α via rotor_power (stays on the versor manifold by construction), and R^α via rotor_power (stays on the versor manifold BY CONSTRUCTION), and
applies it via versor_apply. This replaces the previous _slerp_toward applies it via versor_apply. It replaces the previous _slerp_toward
approach, which interpolated on S^31 rather than on the Spin sub-manifold approach, which interpolated on S^31 rather than on the Spin sub-manifold
and required a post-hoc unitize_versor to repair the manifold violation. and required a post-hoc unitize_versor that closure repair is exactly
what the bright line forbids; this construction-correct form is allowed.
α=0.05 is intentionally mild it corrects accumulated drift over many α=0.05 is intentionally mild it anchors gently over many turns without
turns without distorting single-turn response fields. distorting single-turn response fields. Closure (versor_condition < 1e-6)
is preserved by construction (verified by a 100k-step measurement).
""" """
if self._anchor_field is None: if self._anchor_field is None:
return field_state return field_state
@ -250,9 +256,10 @@ class SessionContext:
self._register_result_referent(result) self._register_result_referent(result)
active_slots = self.referents.active_slots() | active_slots active_slots = self.referents.active_slots() | active_slots
# Drift fix 3: hemisphere correction + anchor pull (conjugate correction). # Semantic anchoring (CLAUDE.md sanctioned site, not drift repair):
# hemisphere consistency + a mild pull toward the session concept-attractor.
oriented_state = self._hemisphere_consistent_field(result.final_state) oriented_state = self._hemisphere_consistent_field(result.final_state)
oriented_state = self._anchor_pull(oriented_state) oriented_state = self._session_anchor_pull(oriented_state)
self.graph.add_turn( self.graph.add_turn(
turn_idx=self.turn, turn_idx=self.turn,
@ -323,7 +330,7 @@ class SessionContext:
) )
result = generate(pivot, self.vocab, self.persona, max_tokens, vault=self.vault) result = generate(pivot, self.vocab, self.persona, max_tokens, vault=self.vault)
self.finalize_turn(result, input_versor=input_versor, dialogue_role="assert") self.finalize_turn(result, input_versor=input_versor, dialogue_role="assert")
# Drift fix 3 may have rotated/pulled the state inside finalize_turn; # Semantic anchoring may have rotated/pulled the state inside finalize_turn;
# re-bind result.final_state so the returned result mirrors the actual # re-bind result.final_state so the returned result mirrors the actual
# post-turn session state (preserves the "respond returns the same # post-turn session state (preserves the "respond returns the same
# state object that was vaulted" contract). # state object that was vaulted" contract).

View file

@ -108,8 +108,8 @@ def test_repeated_prompt_accumulates_field_and_stays_prompt_coherent() -> None:
assert prompt_score > random_score assert prompt_score > random_score
def test_anchor_pull_output_satisfies_versor_condition() -> None: def test_session_anchor_pull_output_satisfies_versor_condition() -> None:
"""_anchor_pull must not break the versor condition (W-015 fix contract). """_session_anchor_pull must not break the versor condition (W-015 fix contract).
The previous _slerp_toward implementation interpolated on S^31 rather than The previous _slerp_toward implementation interpolated on S^31 rather than
the Spin sub-manifold, producing versor_condition values up to 38.58. the Spin sub-manifold, producing versor_condition values up to 38.58.
@ -132,9 +132,9 @@ def test_anchor_pull_output_satisfies_versor_condition() -> None:
valence=session.state.valence, valence=session.state.valence,
) )
pulled = session._anchor_pull(drifted_state) pulled = session._session_anchor_pull(drifted_state)
vc = versor_condition(pulled.F) vc = versor_condition(pulled.F)
assert vc < 1e-6, ( assert vc < 1e-6, (
f"_anchor_pull output violated versor_condition: {vc:.3e} >= 1e-6. " f"_session_anchor_pull output violated versor_condition: {vc:.3e} >= 1e-6. "
"The rotor-geodesic path must stay on the Spin sub-manifold by construction." "The rotor-geodesic path must stay on the Spin sub-manifold by construction."
) )