[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:
Shay 2026-05-25 11:33:32 -07:00 committed by GitHub
parent 96e37e1fce
commit f1d6c49814
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 198 additions and 8 deletions

View file

@ -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,

View file

@ -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,

View file

@ -54,6 +54,7 @@ class CognitiveTurnResult:
# --- vault / memory ---
vault_hits: int
recall_energy_class: str | None = None
# --- intent / graph telemetry ---
intent: DialogueIntent | None = None

View 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

View file

@ -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)

View 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