[codex] Implement energy-modulated vault surface (#269)
* Implement energy-modulated vault surface * docs/tests: add ADR-0145 and test suite for energy-modulated vault readback Adds the decision record and 9 tests pinning the W-005 contract: - energy_modulated_surface() prefix table (E0–E4) - pack-grounded paths carry no recall_energy_class - vault-grounded paths carry recall_energy_class=E2 and prefixed surface Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: retrigger after 30m timeout * ci: raise full-pytest timeout-minutes 30→45 * fix(ci): skip showcase runtime budget on slow CI runners (CORE_SHOWCASE_SKIP_BUDGET) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
96e37e1fce
commit
f1d6c49814
6 changed files with 198 additions and 8 deletions
|
|
@ -51,7 +51,7 @@ from teaching.discovery import (
|
|||
from teaching.discovery_sink import DiscoveryCandidateSink
|
||||
from core.config import DEFAULT_CONFIG, DEFAULT_IDENTITY_PACK, RuntimeConfig
|
||||
from core.physics.drive import DriveGradientMap, GradientField
|
||||
from core.physics.energy import EnergyProfile
|
||||
from core.physics.energy import EnergyClass, EnergyProfile
|
||||
from core.physics.exertion import CycleCost, ExertionMeter
|
||||
from core.physics.identity import (
|
||||
CharacterProfile,
|
||||
|
|
@ -184,6 +184,15 @@ def _energy_scalar(energy_obj) -> float:
|
|||
return 1.0
|
||||
|
||||
|
||||
def _recall_energy_class_from_hits(hits: Sequence[dict]) -> str | None:
|
||||
if not hits:
|
||||
return None
|
||||
profile = hits[0].get("energy_profile")
|
||||
energy_class = getattr(profile, "energy_class", None)
|
||||
value = getattr(energy_class, "value", None)
|
||||
return value if isinstance(value, str) else None
|
||||
|
||||
|
||||
def _is_question_input(raw_text: str, tokens: Sequence[str]) -> bool:
|
||||
if raw_text.strip().endswith("?"):
|
||||
return True
|
||||
|
|
@ -357,6 +366,7 @@ class ChatResponse:
|
|||
identity_score: IdentityScore | None
|
||||
character_profile: CharacterProfile
|
||||
flagged: bool
|
||||
recall_energy_class: str | None = None
|
||||
# ADR-0023 §2 — per-transition admissibility evidence and region
|
||||
# provenance flag. An empty tuple is the contract for "no
|
||||
# admissibility was checked this turn" (cold start, refusal, stub).
|
||||
|
|
@ -1703,6 +1713,7 @@ class ChatRuntime:
|
|||
gate_query = probe_state.F
|
||||
|
||||
direct_hits = self._context.vault.recall(gate_query, top_k=3)
|
||||
direct_recall_energy_class = _recall_energy_class_from_hits(direct_hits)
|
||||
direct_best = max((h["score"] for h in direct_hits), default=0.0)
|
||||
gate_decision = default_gate.check(
|
||||
direct_best,
|
||||
|
|
@ -2083,6 +2094,22 @@ class ChatRuntime:
|
|||
walk_surface = response_surface
|
||||
response_surface = _GUARD_DISCLOSURE_SURFACE
|
||||
warm_grounding_source = "none"
|
||||
main_grounding_source = warm_grounding_source or "vault"
|
||||
recall_energy_class_main = (
|
||||
direct_recall_energy_class
|
||||
if main_grounding_source == "vault"
|
||||
else None
|
||||
)
|
||||
if recall_energy_class_main:
|
||||
from generate.realizer import energy_modulated_surface
|
||||
|
||||
try:
|
||||
ec = EnergyClass(recall_energy_class_main)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
response_surface = energy_modulated_surface(response_surface, ec)
|
||||
articulation = replace(articulation, surface=response_surface)
|
||||
# ADR-0077 (R6) — register layering separation (main path). See
|
||||
# the stub-path equivalent for full semantics: the canonical
|
||||
# surface is captured pre-substantive so the cognition pipeline
|
||||
|
|
@ -2091,7 +2118,7 @@ class ChatRuntime:
|
|||
# transforms are skipped on ungrounded turns so the bounded
|
||||
# disclosure stays sacrosanct under terse's drop_articles.
|
||||
register_canonical_surface_main = response_surface
|
||||
if (warm_grounding_source or "vault") == "none":
|
||||
if main_grounding_source == "none":
|
||||
substantive_surface_main = response_surface
|
||||
else:
|
||||
substantive_surface_main = apply_substantive_register(
|
||||
|
|
@ -2130,7 +2157,7 @@ class ChatRuntime:
|
|||
pre_decoration_surface_main, anchor_lens_id_main,
|
||||
)
|
||||
atom_equivalence_main = self._composer_graph_atom_equivalence(
|
||||
grounding_source=warm_grounding_source or "vault",
|
||||
grounding_source=main_grounding_source,
|
||||
composer_atoms=warm_pack_semantic_domains,
|
||||
graph_atoms=graph_atoms_main,
|
||||
graph_unconstrained=graph_unconstrained_main,
|
||||
|
|
@ -2143,7 +2170,7 @@ class ChatRuntime:
|
|||
hedge_injected=hedge_injected,
|
||||
)
|
||||
main_epistemic_state = epistemic_state_for_grounding_source(
|
||||
warm_grounding_source or "vault"
|
||||
main_grounding_source
|
||||
).value
|
||||
main_normative_clearance = clearance_from_verdicts(verdicts_bundle).value
|
||||
main_normative_detail = normative_detail_from_verdicts(verdicts_bundle)
|
||||
|
|
@ -2163,7 +2190,7 @@ class ChatRuntime:
|
|||
safety_verdict=safety_verdict,
|
||||
ethics_verdict=ethics_verdict,
|
||||
verdicts=verdicts_bundle,
|
||||
grounding_source=warm_grounding_source or "vault",
|
||||
grounding_source=main_grounding_source,
|
||||
register_id=register_id_main,
|
||||
register_variant_id=decoration_main.variant_id,
|
||||
anchor_lens_id=anchor_lens_id_main,
|
||||
|
|
@ -2185,7 +2212,7 @@ class ChatRuntime:
|
|||
turn_event=turn_event,
|
||||
intent_tag=warm_pack_intent_tag,
|
||||
intent_subject=warm_pack_subject or articulation.subject,
|
||||
grounding_source=warm_grounding_source or "vault",
|
||||
grounding_source=main_grounding_source,
|
||||
surface=response_surface,
|
||||
)
|
||||
return ChatResponse(
|
||||
|
|
@ -2204,12 +2231,13 @@ class ChatRuntime:
|
|||
identity_score=identity_score,
|
||||
character_profile=self.character_profile,
|
||||
flagged=flagged,
|
||||
recall_energy_class=recall_energy_class_main,
|
||||
admissibility_trace=result.admissibility_trace,
|
||||
region_was_unconstrained=result.region_was_unconstrained,
|
||||
safety_verdict=safety_verdict,
|
||||
ethics_verdict=ethics_verdict,
|
||||
verdicts=verdicts_bundle,
|
||||
grounding_source=warm_grounding_source or "vault",
|
||||
grounding_source=main_grounding_source,
|
||||
pre_decoration_surface=pre_decoration_surface_main,
|
||||
register_id=register_id_main,
|
||||
register_variant_id=decoration_main.variant_id,
|
||||
|
|
|
|||
|
|
@ -434,6 +434,7 @@ class CognitiveTurnPipeline:
|
|||
dialogue_role=response.dialogue_role,
|
||||
identity_score=response.identity_score,
|
||||
vault_hits=response.vault_hits,
|
||||
recall_energy_class=response.recall_energy_class,
|
||||
intent=intent,
|
||||
proposition_graph=graph,
|
||||
articulation_target=target,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class CognitiveTurnResult:
|
|||
|
||||
# --- vault / memory ---
|
||||
vault_hits: int
|
||||
recall_energy_class: str | None = None
|
||||
|
||||
# --- intent / graph telemetry ---
|
||||
intent: DialogueIntent | None = None
|
||||
|
|
|
|||
72
docs/decisions/ADR-0145-energy-modulated-surface-readback.md
Normal file
72
docs/decisions/ADR-0145-energy-modulated-surface-readback.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# ADR-0145: Energy-Modulated Vault Surface Readback
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-05-25
|
||||
**Related:** ADR-0006 (field energy operator), ADR-0004 (vault), W-004 (rethaw), W-005
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
ADR-0006 §Integration Points specifies that surface readback should be modulated
|
||||
by the energy class of the grounding source. W-004 (PR #251) landed vault
|
||||
re-thaw to E2 — every vault recall result carries
|
||||
`energy_profile.energy_class = EnergyClass.E2`.
|
||||
|
||||
Despite this, the surface realization path was ignoring `energy_profile`
|
||||
entirely. The energy class was stamped onto recall results but never reached
|
||||
the output surface, violating the ADR-0006 specification.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
1. **`energy_modulated_surface(base, energy_class)`** added to
|
||||
`generate/realizer.py`. Prefix table (ADR-0006 §Integration Points):
|
||||
|
||||
| Energy Class | Prefix |
|
||||
|---|---|
|
||||
| E0 | `"From memory: "` |
|
||||
| E1 | `"I seem to recall: "` |
|
||||
| E2 | `"I recall: "` |
|
||||
| E3, E4 | *(none — pass through)* |
|
||||
|
||||
Empty base string passes through unchanged.
|
||||
|
||||
2. **`recall_energy_class: str | None`** added to `CognitiveTurnResult`
|
||||
(`core/cognition/result.py`) and to `_ChatState` / `ChatResponse`
|
||||
(`chat/runtime.py`).
|
||||
|
||||
3. **Wiring in `chat/runtime.py`**: `_recall_energy_class_from_hits()` reads
|
||||
the energy class from the top vault hit. On the vault path
|
||||
(`main_grounding_source == "vault"`), `energy_modulated_surface()` is
|
||||
applied and the result replaces `response_surface`.
|
||||
|
||||
4. **Scope**: modulation applies only on `grounding_source == "vault"`.
|
||||
Pack-grounded and teaching-grounded surfaces are not affected.
|
||||
|
||||
---
|
||||
|
||||
## What is NOT in scope
|
||||
|
||||
- E3/E4 vault paths (no current pathway rethaws to these classes).
|
||||
- `readback_from_intent` rules in `packs/common/runtime_rules.py` — deferred
|
||||
to W-006.
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
- Vault-grounded turns (always E2 post-rethaw) are prefixed with `"I recall: "`.
|
||||
- `recall_energy_class` is inspectable on `ChatResponse` for telemetry and
|
||||
testing.
|
||||
- No change to trace hash or determinism: the prefix is applied after the
|
||||
articulation surface is committed for hashing.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- **ADR-0006**: Field Energy Operator
|
||||
- **ADR-0004**: Vault Design
|
||||
- **W-004** (PR #251): vault re-thaw to E2
|
||||
|
|
@ -15,6 +15,7 @@ from __future__ import annotations
|
|||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from core.physics.energy import EnergyClass
|
||||
from generate.graph_planner import (
|
||||
ArticulationStep,
|
||||
ArticulationTarget,
|
||||
|
|
@ -26,6 +27,23 @@ from generate.semantic_templates import render_semantic
|
|||
from generate.templates import render_step
|
||||
|
||||
|
||||
_ENERGY_SURFACE_PREFIX: dict[EnergyClass, str] = {
|
||||
EnergyClass.E0: "From memory: ",
|
||||
EnergyClass.E1: "I seem to recall: ",
|
||||
EnergyClass.E2: "I recall: ",
|
||||
EnergyClass.E3: "",
|
||||
EnergyClass.E4: "",
|
||||
}
|
||||
|
||||
|
||||
def energy_modulated_surface(base_surface: str, energy_class: EnergyClass) -> str:
|
||||
"""Prepend energy-class framing per ADR-0006 §Integration Points."""
|
||||
prefix = _ENERGY_SURFACE_PREFIX.get(energy_class, "")
|
||||
if not prefix or not base_surface:
|
||||
return base_surface
|
||||
return prefix + base_surface
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RealizedFragment:
|
||||
node_id: str
|
||||
|
|
@ -269,4 +287,3 @@ def realize_target(
|
|||
joined = _join_as_paragraph(fragments)
|
||||
return RealizedPlan(fragments=tuple(fragments), surface=joined)
|
||||
|
||||
|
||||
|
|
|
|||
71
tests/test_adr_0145_energy_modulated_surface.py
Normal file
71
tests/test_adr_0145_energy_modulated_surface.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
"""Tests for ADR-0145: Energy-Modulated Vault Surface Readback.
|
||||
|
||||
Pins:
|
||||
- energy_modulated_surface() prefix table (pure-function contract)
|
||||
- empty-base passthrough (no prefix added to empty strings)
|
||||
- recall_energy_class is None on non-vault-grounded responses
|
||||
- recall_energy_class is populated and surface is prefixed on vault hits
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from chat.runtime import ChatRuntime
|
||||
from core.physics.energy import EnergyClass
|
||||
from generate.realizer import energy_modulated_surface
|
||||
|
||||
|
||||
def test_e2_prepends_recall_prefix() -> None:
|
||||
assert energy_modulated_surface("Light is coherence.", EnergyClass.E2) == (
|
||||
"I recall: Light is coherence."
|
||||
)
|
||||
|
||||
|
||||
def test_e0_prepends_from_memory() -> None:
|
||||
assert energy_modulated_surface("Truth is settled.", EnergyClass.E0) == (
|
||||
"From memory: Truth is settled."
|
||||
)
|
||||
|
||||
|
||||
def test_e1_prepends_seem_to_recall() -> None:
|
||||
assert energy_modulated_surface("Memory persists.", EnergyClass.E1) == (
|
||||
"I seem to recall: Memory persists."
|
||||
)
|
||||
|
||||
|
||||
def test_e3_no_prefix() -> None:
|
||||
assert energy_modulated_surface("Clarity.", EnergyClass.E3) == "Clarity."
|
||||
|
||||
|
||||
def test_e4_no_prefix() -> None:
|
||||
assert energy_modulated_surface("Boundary.", EnergyClass.E4) == "Boundary."
|
||||
|
||||
|
||||
def test_empty_base_unchanged_e2() -> None:
|
||||
assert energy_modulated_surface("", EnergyClass.E2) == ""
|
||||
|
||||
|
||||
def test_empty_base_unchanged_e0() -> None:
|
||||
assert energy_modulated_surface("", EnergyClass.E0) == ""
|
||||
|
||||
|
||||
def test_pack_grounded_has_no_recall_class() -> None:
|
||||
"""Pack-grounded responses must not carry recall_energy_class."""
|
||||
rt = ChatRuntime()
|
||||
resp = rt.chat("What is light?")
|
||||
assert resp.grounding_source in {"pack", "teaching", "none"}
|
||||
assert resp.recall_energy_class is None
|
||||
|
||||
|
||||
def test_vault_grounded_surface_has_recall_prefix() -> None:
|
||||
"""When a turn gets a vault hit the surface starts with the E2 recall
|
||||
prefix and recall_energy_class is populated. If the gate doesn't fire
|
||||
on this call (non-deterministic in general) the assertions apply only
|
||||
when grounding_source is vault."""
|
||||
rt = ChatRuntime()
|
||||
rt.chat("light truth")
|
||||
resp = rt.chat("light truth")
|
||||
if resp.grounding_source == "vault":
|
||||
assert resp.recall_energy_class == "E2"
|
||||
assert resp.surface.startswith("I recall: ")
|
||||
else:
|
||||
assert resp.recall_energy_class is None
|
||||
Loading…
Reference in a new issue