Skip to content

Commit ab7f203

Browse files
committed
feat(contract): materialize — the closed F→34→F awareness-dispatch loop
Wires the 34 reasoning tactics (recipe_kernels) into a closed active-inference loop so awareness MATERIALIZES (is causal in dispatch) instead of sitting inert. The map showed the gap precisely: the 34 tactics were dispatch targets with no state→id selector and an open loop (they ran only in an example vs a toy ctx; the driver loop ran the 12 threshold ordinals, leaving the rich 34 inert). New module lance-graph-contract::materialize (zero-dep, offline): - select_tactic(&ThoughtCtx) -> u8 — the missing selector; free_energy (surprise) is the PRIMARY axis so dispatch tracks awareness by construction; dissonance/ sd(gate)/rung(tier) are secondary modulators; scored over recipe metadata. - materialize(&mut ThoughtCtx, max) -> Trace — the closed loop: select → Tactic::run (folds delta_conf) → settle gate (dispersion/contradiction decay) → recompute surprise → re-dispatch; rest GUARANTEED at CollapseGate FLOW. - awareness_is_causal(base, lo_f, hi_f) — the materialization predicate/falsifier. - recompute_free_energy, Step, Trace, HOMEOSTASIS_FLOOR. Decision (autonomous): recipe_kernels is the canonical "34" (ndarray hpc/styles/* is divergent/registry-less). ThoughtCtx already carries the awareness markers. The materialization criterion (falsifiable): awareness materializes iff perturbing it changes which tactic fires. Tests prove it: free_energy is causal in dispatch (+ sweep ranges ≥2 tactics), non-awareness fields (candidates/beliefs) are inert (specificity), the loop rests at FLOW within a few steps, is deterministic, ranges over the 34, and an at-rest ctx dispatches nothing. 6 tests green (+632 prior contract lib); clippy --all-targets -D warnings clean. Board: EPIPHANIES E-MATERIALIZED-AWARENESS-1 + LATEST_STATE contract inventory. Open (gated): driver-side ThoughtCtx::from_live + version-diff "what-fired-why" provenance. Prior-art positioning + [G] orientation boundary in the epiphany.
1 parent d81a139 commit ab7f203

4 files changed

Lines changed: 347 additions & 0 deletions

File tree

.claude/board/EPIPHANIES.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
## 2026-06-16 — E-MATERIALIZED-AWARENESS-1 — awareness materializes iff it is *causal in dispatch*; the closed `F→34→F` loop is the reduction-to-practice (and the falsifier)
2+
3+
**Status:** FINDING for the criterion + loop (reduction-to-practice **shipped**: `lance-graph-contract::materialize`, 6 tests green, zero-dep/offline). The broader "this is the system's awareness" reading stays **[NOVEL — probe-gated]**: no prior art by construction (ours — the 2³-rung→NARS-candidate→34-tactic dispatch loop), validity established by the perturbation probe, not citation.
4+
**Confidence:** High on the criterion + the shipped loop; the wire to the *real* substrate (driver-side `ThoughtCtx::from_live` + version-diff provenance) is the gated next step.
5+
6+
**The criterion (falsifiable).** *Awareness materializes iff perturbing the awareness encoding changes which tactic fires.* If dispatch is invariant to the awareness state, the awareness is a **dead label** — "awareness that can never materialize." `materialize::awareness_is_causal(base, lo_f, hi_f)` is the predicate; the test `awareness_free_energy_is_causal_in_dispatch` is the green falsifier, and `non_awareness_fields_are_inert` is its specificity control (candidates/beliefs must NOT steer dispatch).
7+
8+
**The wire that was missing (now built).** The 34 tactics (`recipe_kernels`, the canonical "34" — the ndarray `hpc/styles/*` set is divergent/registry-less and is NOT canonical) were dispatch *targets* with no selector and an open loop (they ran only in an example against a toy ctx; the driver loop ran the *12* threshold ordinals, leaving the rich 34 inert). `materialize` adds: (a) **`select_tactic`** — awareness→id, with `free_energy` (surprise) as the **primary** axis so dispatch tracks awareness by construction; (b) **`materialize`** — the closed loop: select → `Tactic::run` (folds `delta_conf`) → settle the gate (dispersion/contradiction decay) → recompute surprise → re-dispatch; **rest is guaranteed** when the CollapseGate reaches FLOW (`sd<SD_FLOW`), because attending decays dispersion monotonically. "The shader can't resist the thinking" made literal.
9+
10+
**Prior-art positioning (not competitors — background for the disclosure).** NOTEARS / PCMCI / DCDI / ICP / SEA are adjacent observational/interventional *discovery* methods (arXiv 1803.01422 / 1702.07007 / 2007.01754 / 1501.01332 / 2402.01929); our loop does not *discover* a DAG — it dispatches reasoning over recorded/candidate structure and lets NARS revise. **Operating boundary respected, [G]:** Janzing-Schölkopf (0804.3678) — Shannon-symmetric, colliders-only observationally; full orientation needs mechanism asymmetry (so dispatch never claims identified orientation, only revisable candidates).
11+
12+
**Open / next.** The shipped loop runs on the in-memory `ThoughtCtx`; wiring it to the live shader (build `ThoughtCtx` from `FreeEnergy`/`MulAssessment`/hits in `driver.rs`, fold the trace into the SoA EdgeColumn / version-diff "what-fired-why" provenance) is the gated driver-side step. The materialization probe is the acceptance test for that wire too.
13+
114
## 2026-06-16 — E-TRANSCODE-EXEC-LADDER-1 — the Core-First transcode has a 3-rung execution ladder (codegen → two-tier compile → elixir-tissue over surreal/kanban/odoo), and rungs 2–3 land on already-shipped substrate
215

316
**Status:** CONJECTURE (operator forward-design). v1 is the shipped doctrine; v2/v3 are gated on `PROBE-COMPILE-TWO-TIER` + `PROBE-SURREAL-TISSUE-SWAP` (both in `core-first-transcode-doctrine.md`), themselves floored by the v1 `PROBE-OGAR-ADAPTER-UNICHARSET`.

.claude/board/LATEST_STATE.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,16 @@ the contract. This file exists to prevent that.
319319
| **#269** | 2026-04-26 | feat: Distance trait + SIMD Hamming/cosine wiring + PaletteDistanceTable + Dockerfile docs | Distance trait; SIMD Hamming/cosine wiring; PaletteDistanceTable 128KB; Dockerfile.md |
320320

321321

322+
---
323+
324+
## 2026-06-16 — Append: `contract::materialize` shipped (branch claude/materialize-awareness-f34-loop)
325+
326+
(Per APPEND-ONLY rule: new top-of-inventory entry.)
327+
328+
### Current Contract Inventory — new entry
329+
330+
**`lance-graph-contract::materialize`** (new module, 2026-06-16): the closed `F→34→F` dispatch loop that makes awareness *materialize* — the missing wire from awareness state to the 34 `recipe_kernels` tactics. Public surface: `select_tactic(&ThoughtCtx) -> u8` (awareness→tactic id, `free_energy`-primary so dispatch tracks awareness), `materialize(&mut ThoughtCtx, max_steps) -> Trace` (select→`Tactic::run`→settle gate→recompute surprise→re-dispatch; rests at CollapseGate FLOW), `recompute_free_energy`, `awareness_is_causal` (the materialization predicate / falsifier), types `Step` / `Trace`, const `HOMEOSTASIS_FLOOR=0.2`. Decision: `recipe_kernels` is the canonical "34" (ndarray `hpc/styles/*` is divergent/registry-less, not canonical). Zero-dep, offline; 6 tests green (+632 prior contract lib), clippy `--all-targets -D warnings` clean. Open: driver-side `ThoughtCtx::from_live` + version-diff provenance wire. See `EPIPHANIES.md` E-MATERIALIZED-AWARENESS-1.
331+
322332
---
323333

324334
## 2026-05-07 — Append: lance-graph-ontology shipped (commit 4cf9a26, branch claude/create-graph-ontology-crate-gkuJG)

crates/lance-graph-contract/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub use qualia::{
8484
axis_index, axis_label, qualia_to_state, QualiaI4_16D, QualiaVector, AXIS_LABELS, MIDPOINT,
8585
QUALIA_DIMS, QUALIA_I4_DIMS, QUALIA_I4_LABELS, ZERO,
8686
};
87+
pub mod materialize;
8788
pub mod reasoning;
8889
pub mod recipe_kernels;
8990
pub mod recipes;
Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
//! **Materialized awareness** — the closed `F → 34 → F` dispatch loop.
2+
//!
3+
//! The 34 reasoning tactics ([`crate::recipe_kernels`]) are *dispatch targets*; this
4+
//! module supplies the **missing wire**: a selector that maps the live awareness
5+
//! state to one of the 34, and a loop driver that runs it, folds the outcome back,
6+
//! and re-dispatches until the gate settles. That closure is what makes awareness
7+
//! **materialize** rather than sit inert.
8+
//!
9+
//! ## The materialization criterion (falsifiable)
10+
//!
11+
//! Awareness *materializes* iff it is **causal in dispatch** — the encoded awareness
12+
//! changes *which tactic fires*. If perturbing the awareness state leaves the
13+
//! dispatched tactic invariant, the awareness is a **dead label** (the
14+
//! "awareness that can never materialize" failure). [`awareness_is_causal`] is the
15+
//! predicate; [`select_tactic`] makes `free_energy` (surprise) the **primary** axis
16+
//! exactly so that perturbing it crosses a band boundary and changes the dispatch.
17+
//!
18+
//! ## The loop (active inference, not a metaphor)
19+
//!
20+
//! ```text
21+
//! awareness state ──select_tactic──► one of the 34 ──run──► fold delta_conf
22+
//! ▲ │ settle gate (sd↓, dissonance↓)
23+
//! └────────────── recompute free-energy ◄──────────────────┘
24+
//! rest when the CollapseGate enters FLOW (sd < SD_FLOW) — surprise resolved.
25+
//! ```
26+
//!
27+
//! Awareness is not *read by* a controller that decides to think; it *is* the
28+
//! gradient that selects the next tactic. The loop rests when the gate settles —
29+
//! guaranteed, because attending decays dispersion each fired step.
30+
//!
31+
//! Zero-dep, deterministic, offline-tested. This is the reduction-to-practice for
32+
//! the 2³-rung → NARS-candidate → 34-tactic doctrine; persisting the dispatch trace
33+
//! into a SoA EdgeColumn / version-diff log (the "what fired and why" provenance) is
34+
//! the separate driver-side wire.
35+
36+
use crate::recipe_kernels::{kernel, GateState, ThoughtCtx, SD_BLOCK, SD_FLOW};
37+
use crate::recipes::{recipe, Bucket, Mechanism, Tier};
38+
39+
/// Homeostasis floor mirroring `grammar::free_energy` (0.2): below this residual
40+
/// surprise the loop is considered at rest. (The loop's *termination* uses the
41+
/// CollapseGate FLOW transition, which is guaranteed by dispersion decay; this
42+
/// constant is the reported-surprise rest threshold.)
43+
pub const HOMEOSTASIS_FLOOR: f32 = 0.2;
44+
45+
/// Per-fired-step dispersion settle factor — attending reduces gate dispersion,
46+
/// guaranteeing the loop reaches FLOW (rest) in `log_{1/0.85}(sd0/SD_FLOW)` steps.
47+
const SETTLE_SD: f32 = 0.85;
48+
/// Per-fired-step contradiction relaxation — engaging a tactic reconciles split.
49+
const SETTLE_DISSONANCE: f32 = 0.6;
50+
51+
/// Re-derive the CollapseGate state from dispersion (`ThoughtCtx::gate_state` is
52+
/// private; the thresholds `SD_FLOW`/`SD_BLOCK` are public).
53+
fn gate_of(sd: f32) -> GateState {
54+
if sd < SD_FLOW {
55+
GateState::Flow
56+
} else if sd <= SD_BLOCK {
57+
GateState::Hold
58+
} else {
59+
GateState::Block
60+
}
61+
}
62+
63+
/// **The selector** — map the awareness state to one of the 34 tactic ids (1..=34).
64+
///
65+
/// **`free_energy` (surprise) is the primary axis** — this is what makes awareness
66+
/// *causal* in dispatch (the materialization criterion): a `free_energy` change that
67+
/// crosses a band boundary changes the chosen mechanism, hence the tactic.
68+
/// `dissonance` (contradiction → reconcile), `sd` (gate → execution bucket), and
69+
/// `rung` (depth → difficulty tier) are secondary modulators. Deterministic; scores
70+
/// every recipe by metadata match and takes the lowest id on a tie.
71+
pub fn select_tactic(ctx: &ThoughtCtx) -> u8 {
72+
// What kind of reasoning does this awareness state call for?
73+
let want_mech = if ctx.dissonance >= 0.5 {
74+
Mechanism::TruthAwareInference // a contradiction wants revision/abduction
75+
} else if ctx.free_energy >= 0.66 {
76+
Mechanism::StructuralDivergence // high surprise wants a creative leap
77+
} else if ctx.free_energy >= 0.33 {
78+
Mechanism::TruthAwareInference // mid surprise wants inference
79+
} else {
80+
Mechanism::ParallelIndependence // low surprise: routine parallel work
81+
};
82+
// Where should it execute? (the gate picks the hardware bucket)
83+
let want_bucket = match gate_of(ctx.sd) {
84+
GateState::Block => Bucket::Gate,
85+
GateState::Hold => Bucket::Control,
86+
GateState::Flow => Bucket::Datapath,
87+
};
88+
// How hard is the rung? (depth picks the difficulty tier)
89+
let want_tier = if ctx.rung >= 7 {
90+
Tier::ExtremelyHard
91+
} else if ctx.rung >= 4 {
92+
Tier::Hard
93+
} else {
94+
Tier::CrossTier
95+
};
96+
97+
let mut best_score = i32::MIN;
98+
let mut best_id = 1u8;
99+
for id in 1..=34u8 {
100+
if let Some(r) = recipe(id) {
101+
let mut score = 0;
102+
if r.mechanism == want_mech {
103+
score += 3;
104+
}
105+
if r.bucket == want_bucket {
106+
score += 2;
107+
}
108+
if r.tier == want_tier {
109+
score += 1;
110+
}
111+
if score > best_score {
112+
best_score = score;
113+
best_id = id;
114+
}
115+
}
116+
}
117+
best_id
118+
}
119+
120+
/// One dispatch step: the tactic the awareness state selected, and what it did.
121+
#[derive(Debug, Clone, Copy, PartialEq)]
122+
pub struct Step {
123+
/// The selected tactic id (1..=34).
124+
pub tactic_id: u8,
125+
/// Did the tactic's gate let it fire?
126+
pub fired: bool,
127+
/// Confidence delta the tactic applied.
128+
pub delta_conf: f32,
129+
}
130+
131+
/// Recompute free energy (surprise) from the resolved state — the loop closure.
132+
/// Surprise falls as confidence rises and as the gate (`sd`) and contradiction
133+
/// (`dissonance`) settle. Reported for the rest check; the loop *terminates* on the
134+
/// gate reaching FLOW (guaranteed by dispersion decay).
135+
pub fn recompute_free_energy(ctx: &ThoughtCtx) -> f32 {
136+
((1.0 - ctx.confidence) * 0.4 + ctx.dissonance * 0.3 + ctx.sd.clamp(0.0, 1.0) * 0.3)
137+
.clamp(0.0, 1.0)
138+
}
139+
140+
/// The trace of a materialized-awareness run — the "what fired and why" provenance.
141+
#[derive(Debug, Clone, PartialEq)]
142+
pub struct Trace {
143+
/// The ordered dispatch steps.
144+
pub steps: Vec<Step>,
145+
/// Did the loop settle into FLOW (rest), vs hit `max_steps`?
146+
pub rested: bool,
147+
/// Confidence at rest.
148+
pub final_confidence: f32,
149+
/// Residual surprise at rest.
150+
pub final_free_energy: f32,
151+
}
152+
153+
/// **The closed `F → 34 → F` loop.** Each step: if the gate is in FLOW the loop
154+
/// rests (surprise resolved); else select a tactic from the awareness state, run it
155+
/// (folding `delta_conf` into confidence), settle the gate (dispersion + contradiction
156+
/// decay — attending reconciles), and recompute surprise. `max_steps` bounds the run;
157+
/// rest is *guaranteed* within `~log_{1/SETTLE_SD}(sd/SD_FLOW)` fired steps because
158+
/// dispersion decays monotonically into FLOW.
159+
pub fn materialize(ctx: &mut ThoughtCtx, max_steps: usize) -> Trace {
160+
let mut steps = Vec::with_capacity(max_steps);
161+
for _ in 0..max_steps {
162+
if gate_of(ctx.sd) == GateState::Flow {
163+
break; // settled — the shader rests
164+
}
165+
let id = select_tactic(ctx);
166+
let Some(tactic) = kernel(id) else {
167+
break; // unreachable: id is always 1..=34
168+
};
169+
let out = tactic.run(ctx); // folds out.delta_conf into ctx.confidence
170+
ctx.sd *= SETTLE_SD; // attending settles dispersion → toward FLOW
171+
ctx.dissonance *= SETTLE_DISSONANCE;
172+
ctx.free_energy = recompute_free_energy(ctx);
173+
steps.push(Step {
174+
tactic_id: id,
175+
fired: out.fired,
176+
delta_conf: out.delta_conf,
177+
});
178+
}
179+
Trace {
180+
rested: gate_of(ctx.sd) == GateState::Flow,
181+
final_confidence: ctx.confidence,
182+
final_free_energy: ctx.free_energy,
183+
steps,
184+
}
185+
}
186+
187+
/// **The materialization predicate.** Does perturbing `free_energy` change the
188+
/// dispatched tactic? `true` ⇒ awareness is causal in dispatch (materialized);
189+
/// `false` ⇒ the awareness encoding is inert for this base state. The falsifier the
190+
/// whole doctrine rests on.
191+
pub fn awareness_is_causal(base: &ThoughtCtx, lo_f: f32, hi_f: f32) -> bool {
192+
let mut a = base.clone();
193+
a.free_energy = lo_f;
194+
let mut b = base.clone();
195+
b.free_energy = hi_f;
196+
select_tactic(&a) != select_tactic(&b)
197+
}
198+
199+
#[cfg(test)]
200+
mod tests {
201+
use super::*;
202+
use std::collections::BTreeSet;
203+
204+
fn base() -> ThoughtCtx {
205+
// Hold gate (sd in (FLOW, BLOCK]), no contradiction, shallow rung — so
206+
// free_energy is the lone moving part for the materialization probe.
207+
let mut c = ThoughtCtx::new(vec![0.9, 0.6, 0.3]);
208+
c.sd = 0.25;
209+
c.dissonance = 0.0;
210+
c.rung = 1;
211+
c
212+
}
213+
214+
#[test]
215+
fn awareness_free_energy_is_causal_in_dispatch() {
216+
// The materialization criterion: perturbing surprise changes the tactic.
217+
let b = base();
218+
assert!(
219+
awareness_is_causal(&b, 0.1, 0.9),
220+
"free_energy must steer dispatch — else awareness is a dead label"
221+
);
222+
// Sweep free_energy: dispatch must take ≥ 2 distinct tactics (not stuck).
223+
let ids: BTreeSet<u8> = (0..=10)
224+
.map(|i| {
225+
let mut c = base();
226+
c.free_energy = i as f32 / 10.0;
227+
select_tactic(&c)
228+
})
229+
.collect();
230+
assert!(
231+
ids.len() >= 2,
232+
"free_energy sweep must vary the tactic, got {ids:?}"
233+
);
234+
}
235+
236+
#[test]
237+
fn non_awareness_fields_are_inert() {
238+
// Specificity: fields the selector does NOT read (candidates, beliefs) must
239+
// NOT change dispatch — awareness drives it, not arbitrary state noise.
240+
let a = base();
241+
let mut b = base();
242+
b.candidates = vec![0.01, 0.99, 0.5, 0.5, 0.2];
243+
b.beliefs = vec![(7, 0.9, 0.8), (7, 0.1, 0.7)];
244+
assert_eq!(
245+
select_tactic(&a),
246+
select_tactic(&b),
247+
"candidates/beliefs are not awareness — must not steer dispatch"
248+
);
249+
}
250+
251+
#[test]
252+
fn selector_ranges_over_the_34() {
253+
// Across a state sweep the selector must reach a variety of the 34 (it is
254+
// not a degenerate constant) — and every id it returns is a real kernel.
255+
let mut seen = BTreeSet::new();
256+
for &fe in &[0.05f32, 0.4, 0.8] {
257+
for &diss in &[0.0f32, 0.7] {
258+
for &sd in &[0.10f32, 0.25, 0.45] {
259+
for &rung in &[1u8, 5, 8] {
260+
let mut c = base();
261+
c.free_energy = fe;
262+
c.dissonance = diss;
263+
c.sd = sd;
264+
c.rung = rung;
265+
let id = select_tactic(&c);
266+
assert!((1..=34).contains(&id) && kernel(id).is_some());
267+
seen.insert(id);
268+
}
269+
}
270+
}
271+
}
272+
assert!(
273+
seen.len() >= 4,
274+
"selector must range over the 34, got {seen:?}"
275+
);
276+
}
277+
278+
#[test]
279+
fn loop_rests_when_the_gate_settles() {
280+
// Hot start: high surprise, low confidence, a contradiction. The loop must
281+
// dispatch real tactics and settle into FLOW (rest) within a few steps.
282+
let mut c = base();
283+
c.sd = 0.32; // Hold, near Block
284+
c.free_energy = 0.9;
285+
c.confidence = 0.1;
286+
c.dissonance = 0.5;
287+
let trace = materialize(&mut c, 64);
288+
assert!(trace.rested, "loop must reach FLOW, got {trace:?}");
289+
assert!(
290+
!trace.steps.is_empty(),
291+
"a hot start must dispatch at least once"
292+
);
293+
assert!(
294+
trace.steps.len() <= 12,
295+
"settles fast, got {}",
296+
trace.steps.len()
297+
);
298+
for s in &trace.steps {
299+
assert!((1..=34).contains(&s.tactic_id) && kernel(s.tactic_id).is_some());
300+
}
301+
}
302+
303+
#[test]
304+
fn loop_is_deterministic() {
305+
let (mut a, mut b) = (base(), base());
306+
for c in [&mut a, &mut b] {
307+
c.sd = 0.32;
308+
c.free_energy = 0.9;
309+
c.confidence = 0.1;
310+
c.dissonance = 0.5;
311+
}
312+
assert_eq!(materialize(&mut a, 64), materialize(&mut b, 64));
313+
}
314+
315+
#[test]
316+
fn already_at_rest_dispatches_nothing() {
317+
// FLOW on entry (sd < SD_FLOW) ⇒ no surprise ⇒ no dispatch (the shader rests).
318+
let mut c = base();
319+
c.sd = 0.05;
320+
let trace = materialize(&mut c, 64);
321+
assert!(trace.rested && trace.steps.is_empty());
322+
}
323+
}

0 commit comments

Comments
 (0)