Reason — chain of thought, deterministic, MoT split

A chain of thought, deterministic, at picojoule grain. A small cascade walks each query through five tiers (arithmetic → named-fact lookup → HDC-style shape match → cited composition → model-class). Each tier is split into a reasoning expert (classify the form) and a generation expert (produce the answer); generation only runs when reasoning matches. Indented rows on the trace are generation hops; left-aligned rows are reasoning hops. Every sub-hop carries its own joule receipt. The exhibit rotates through eight canonical queries that exercise every resolution outcome including the model-class refusal.

view the kernel that wrote this receipt crates/mathground-view/src/cascade.rs
//! A tiny cognitive cascade for the math domain, split mixture-of-
//! transformers-style into per-tier reasoning + generation experts.
//!
//! Demonstrates the same SHAPE that a reasoning VLA emits — a chain of
//! decisions, each tagged with what was tried, what matched, and what it
//! cost — except every hop is deterministic. Each tier is now split into
//! two sub-experts:
//!
//!   • Reasoning expert  — does this query fit this tier's grammar?
//!                         (Decides the *form*; cheap; classifies.)
//!   • Generation expert — given a recognised form, produce the answer.
//!                         (Only runs when the reasoning expert matched.)
//!
//! The visible trace expands to one hop per sub-expert that fired. A
//! resolved tier contributes TWO hops (reasoning ✓ → generation ✓); an
//! unmatched tier contributes ONE (reasoning ✗ — no generation needed).
//! Same architectural shape as Cosmos 3's mixture-of-transformers — one
//! reasoning transformer + one expert generation transformer — at zero
//! GPU cost.
//!
//! Tiers (ladder, checked in order; first matching tier resolves):
//!
//!   L0-closed       — arithmetic / symbolic evaluation.
//!   L0-retrieved    — exact citation lookup in a fixed table.
//!   L0-identifiable — HDC-style shape match against a small atom pool.
//!   L1              — citation-backed composition.
//!   L2              — model-generated. Reasoning matches (recognises the
//!                     query as model-class); generation refuses and reports
//!                     the would-cost — substrate honesty.

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CascadeTier {
    L0Closed,
    L0Retrieved,
    L0Identifiable,
    L1,
    L2,
}

impl CascadeTier {
    pub fn label(self) -> &'static str {
        match self {
            CascadeTier::L0Closed => "L0-closed",
            CascadeTier::L0Retrieved => "L0-retrieved",
            CascadeTier::L0Identifiable => "L0-identifiable",
            CascadeTier::L1 => "L1",
            CascadeTier::L2 => "L2",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SubExpert {
    Reasoning,
    Generation,
}

impl SubExpert {
    pub fn label(self) -> &'static str {
        match self {
            SubExpert::Reasoning => "reasoning",
            SubExpert::Generation => "generation",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CascadeHop {
    pub tier: CascadeTier,
    pub expert: SubExpert,
    /// Short, human-readable description of what this expert did.
    pub action: String,
    pub matched: bool,
    pub bit_ops: u64,
    pub wall_ns_per_op: f64,
    /// Set only on the generation hop when generation succeeded.
    pub answer: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CascadeTrace {
    pub query: String,
    pub hops: Vec<CascadeHop>,
    pub resolved_at: Option<CascadeTier>,
    pub final_answer: Option<String>,
    pub total_bit_ops: u64,
}

/// Probe of a single tier — what its reasoning expert decided, what (if
/// anything) its generation expert produced, and the cost of each.
struct TierProbe {
    reasoning_matched: bool,
    reasoning_action: &'static str,
    reasoning_cost: u64,
    generation_action: Option<&'static str>,
    generation_answer: Option<String>,
    generation_cost: u64,
}

// ── L0-closed: arithmetic and basic algebra ───────────────────────────

enum ClosedForm {
    BinaryArithmetic,
    Factorial,
}

fn probe_l0_closed(query: &str) -> TierProbe {
    let q = query.trim();
    let parts: Vec<&str> = q.split_whitespace().collect();
    let reason_cost = 64u64;
    let mut form: Option<ClosedForm> = None;

    if parts.len() == 3
        && matches!(parts[1], "+" | "-" | "*" | "/")
        && parts[0].parse::<f64>().is_ok()
        && parts[2].parse::<f64>().is_ok()
    {
        form = Some(ClosedForm::BinaryArithmetic);
    } else if let Some(stripped) = q.strip_suffix('!') {
        if let Ok(n) = stripped.trim().parse::<u32>() {
            if n <= 12 {
                form = Some(ClosedForm::Factorial);
            }
        }
    }

    if form.is_none() {
        return TierProbe {
            reasoning_matched: false,
            reasoning_action: "classify as arithmetic / factorial",
            reasoning_cost: reason_cost,
            generation_action: None,
            generation_answer: None,
            generation_cost: 0,
        };
    }

    let (answer, action, cost) = match form.unwrap() {
        ClosedForm::BinaryArithmetic => {
            let a = parts[0].parse::<f64>().unwrap();
            let b = parts[2].parse::<f64>().unwrap();
            let answer = match parts[1] {
                "+" => Some(format!("{}", a + b)),
                "-" => Some(format!("{}", a - b)),
                "*" => Some(format!("{}", a * b)),
                "/" if b != 0.0 => Some(format!("{}", a / b)),
                _ => None,
            };
            (answer, "evaluate arithmetic expression", 32)
        }
        ClosedForm::Factorial => {
            let n: u32 = q.strip_suffix('!').unwrap().trim().parse().unwrap();
            let f: u64 = (1..=n as u64).product();
            (Some(format!("{f}")), "evaluate factorial", (n as u64) * 32)
        }
    };

    TierProbe {
        reasoning_matched: true,
        reasoning_action: "classify as arithmetic / factorial",
        reasoning_cost: reason_cost,
        generation_action: Some(action),
        generation_answer: answer,
        generation_cost: cost,
    }
}

// ── L0-retrieved: named constants and theorems ────────────────────────

const RETRIEVED: &[(&str, &str)] = &[
    ("pi", "π ≈ 3.141592653589793"),
    ("e", "e ≈ 2.718281828459045"),
    ("phi", "φ ≈ 1.618033988749895   (golden ratio)"),
    ("c", "c = 299 792 458 m/s   (speed of light, exact)"),
    ("h", "h ≈ 6.62607015×10⁻³⁴ J·s   (Planck, exact since 2019)"),
    ("kt ln 2 at 300 k", "kT·ln 2 ≈ 2.870×10⁻²¹ J   (Landauer floor)"),
    ("pythagorean theorem", "a² + b² = c²   (for a right triangle, c = hypotenuse)"),
    ("eulers identity", "e^(iπ) + 1 = 0"),
    ("eulers formula", "e^(iθ) = cos θ + i sin θ"),
    ("schwarz inequality", "|⟨u, v⟩| ≤ ‖u‖·‖v‖"),
    ("law of cosines", "c² = a² + b² − 2 a b cos C"),
    ("planck-einstein", "E = h ν"),
    ("entropy of binary distribution", "H(p) = −p log₂ p − (1−p) log₂(1−p)"),
];

fn probe_l0_retrieved(query: &str) -> TierProbe {
    let q = query.trim().to_lowercase();
    let reason_cost = (RETRIEVED.len() as u64) * 50;

    // Reasoning expert: is the query EXACTLY a known key?
    let hit = RETRIEVED.iter().find(|(k, _)| q == *k);
    if let Some((_, value)) = hit {
        TierProbe {
            reasoning_matched: true,
            reasoning_action: "match against named-fact table",
            reasoning_cost: reason_cost,
            generation_action: Some("emit cited value"),
            generation_answer: Some((*value).to_string()),
            generation_cost: 32,
        }
    } else {
        TierProbe {
            reasoning_matched: false,
            reasoning_action: "match against named-fact table",
            reasoning_cost: reason_cost,
            generation_action: None,
            generation_answer: None,
            generation_cost: 0,
        }
    }
}

// ── L0-identifiable: HDC-style margin-gated shape match ───────────────

const IDENTIFIABLE: &[(&str, &str)] = &[
    ("area of a unit circle", "π r²  with r = 1  →  π"),
    ("circumference of a unit circle", "2π r  with r = 1  →  2π"),
    ("area of an equilateral triangle, side 1", "(√3 / 4) · s²  with s = 1  →  ≈ 0.433"),
    ("volume of a unit sphere", "(4/3) π r³  with r = 1  →  ≈ 4.189"),
    ("rms of a sine wave amplitude a", "a / √2"),
];

fn probe_l0_identifiable(query: &str) -> TierProbe {
    let q = query.trim().to_lowercase();
    let q_tokens: std::collections::HashSet<&str> =
        q.split_whitespace().filter(|t| t.len() > 1).collect();

    let mut scores: Vec<(f64, &str, &str)> = Vec::with_capacity(IDENTIFIABLE.len());
    for (key, value) in IDENTIFIABLE {
        let k_tokens: std::collections::HashSet<&str> =
            key.split_whitespace().filter(|t| t.len() > 1).collect();
        let inter: usize = q_tokens.intersection(&k_tokens).count();
        let union: usize = q_tokens.union(&k_tokens).count();
        let sim = if union == 0 { 0.0 } else { inter as f64 / union as f64 };
        scores.push((sim, key, value));
    }
    scores.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
    let reason_cost = (IDENTIFIABLE.len() as u64) * 200;

    let top = scores.first().copied();
    let second_sim = scores.get(1).map(|t| t.0).unwrap_or(0.0);
    if let Some((sim, _key, value)) = top {
        if sim >= 0.5 && (sim - second_sim) >= 0.15 {
            return TierProbe {
                reasoning_matched: true,
                reasoning_action: "shape-match against atom pool (margin-gated)",
                reasoning_cost: reason_cost,
                generation_action: Some("emit matched-atom value"),
                generation_answer: Some(value.to_string()),
                generation_cost: 32,
            };
        }
    }
    TierProbe {
        reasoning_matched: false,
        reasoning_action: "shape-match against atom pool (margin-gated)",
        reasoning_cost: reason_cost,
        generation_action: None,
        generation_answer: None,
        generation_cost: 0,
    }
}

// ── L1: cited composition ─────────────────────────────────────────────

fn probe_l1(query: &str) -> TierProbe {
    let q = query.trim().to_lowercase();
    let reason_cost = 200u64;

    if let Some(rest) = q.strip_prefix("compose ") {
        let parts: Vec<&str> = rest.split(" times ").collect();
        if parts.len() == 2 {
            let l_probe = probe_l0_retrieved(parts[0]);
            let r_probe = probe_l0_retrieved(parts[1]);
            if let (Some(lv), Some(rv)) = (l_probe.generation_answer, r_probe.generation_answer) {
                let answer = format!("compose({}, {}) — both cited from L0-retrieved", lv, rv);
                return TierProbe {
                    reasoning_matched: true,
                    reasoning_action: "recognise `compose X times Y` form",
                    reasoning_cost: reason_cost,
                    generation_action: Some("retrieve both cites and compose"),
                    generation_answer: Some(answer),
                    generation_cost: 400,
                };
            }
        }
    }
    TierProbe {
        reasoning_matched: false,
        reasoning_action: "recognise `compose X times Y` form",
        reasoning_cost: reason_cost,
        generation_action: None,
        generation_answer: None,
        generation_cost: 0,
    }
}

// ── L2: model-generated (we don't ship one) ───────────────────────────

fn probe_l2(_query: &str) -> TierProbe {
    // Reasoning expert: anything that reached this tier is by definition a
    // would-need-a-model query — reasoning always matches. The generation
    // expert refuses and reports the would-cost (~10⁹ bit-ops for a small
    // local model's single short generation).
    TierProbe {
        reasoning_matched: true,
        reasoning_action: "classify as model-class (escapes lower tiers)",
        reasoning_cost: 64,
        generation_action: Some("refuse: substrate does not ship a model"),
        generation_answer: None,
        generation_cost: 1_000_000_000,
    }
}

// ── Walker ────────────────────────────────────────────────────────────

pub struct TimeFn {
    pub now_ns: fn() -> f64,
}

fn record_probe(
    tier: CascadeTier,
    probe: TierProbe,
    hops: &mut Vec<CascadeHop>,
    total_bit_ops: &mut u64,
    time: &TimeFn,
) -> (bool, Option<String>) {
    // Reasoning hop — always recorded.
    let t_r = (time.now_ns)();
    let dt_r = ((time.now_ns)() - t_r).max(0.0);
    let wall_ns_per_op = if probe.reasoning_cost > 0 {
        dt_r / probe.reasoning_cost as f64
    } else {
        dt_r
    };
    *total_bit_ops = total_bit_ops.saturating_add(probe.reasoning_cost);
    hops.push(CascadeHop {
        tier,
        expert: SubExpert::Reasoning,
        action: probe.reasoning_action.into(),
        matched: probe.reasoning_matched,
        bit_ops: probe.reasoning_cost,
        wall_ns_per_op,
        answer: None,
    });

    if !probe.reasoning_matched {
        return (false, None);
    }

    // Generation hop — only when reasoning matched.
    let t_g = (time.now_ns)();
    let dt_g = ((time.now_ns)() - t_g).max(0.0);
    let gen_wall = if probe.generation_cost > 0 {
        dt_g / probe.generation_cost as f64
    } else {
        dt_g
    };
    *total_bit_ops = total_bit_ops.saturating_add(probe.generation_cost);
    let answer = probe.generation_answer.clone();
    hops.push(CascadeHop {
        tier,
        expert: SubExpert::Generation,
        action: probe
            .generation_action
            .map(|s| s.to_string())
            .unwrap_or_default(),
        matched: answer.is_some(),
        bit_ops: probe.generation_cost,
        wall_ns_per_op: gen_wall,
        answer: answer.clone(),
    });
    (answer.is_some(), answer)
}

/// Walk the cascade for `query`. Each tier first runs its reasoning expert,
/// then (if reasoning matched) its generation expert. A tier "resolves" the
/// query only when generation also produces an answer; otherwise the cascade
/// continues with the next tier. L2's generation always refuses → no
/// resolution.
pub fn walk(query: &str, time: &TimeFn) -> CascadeTrace {
    let mut hops = Vec::new();
    let mut total_bit_ops: u64 = 0;
    let mut resolved_at: Option<CascadeTier> = None;
    let mut final_answer: Option<String> = None;

    let probes: [(CascadeTier, fn(&str) -> TierProbe); 5] = [
        (CascadeTier::L0Closed, probe_l0_closed),
        (CascadeTier::L0Retrieved, probe_l0_retrieved),
        (CascadeTier::L0Identifiable, probe_l0_identifiable),
        (CascadeTier::L1, probe_l1),
        (CascadeTier::L2, probe_l2),
    ];

    for (tier, probe_fn) in probes {
        let probe = probe_fn(query);
        let (gen_ok, ans) = record_probe(tier, probe, &mut hops, &mut total_bit_ops, time);
        if gen_ok {
            resolved_at = Some(tier);
            final_answer = ans;
            break;
        }
    }

    CascadeTrace {
        query: query.into(),
        hops,
        resolved_at,
        final_answer,
        total_bit_ops,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn now() -> f64 {
        use std::sync::atomic::{AtomicU64, Ordering};
        static CLOCK: AtomicU64 = AtomicU64::new(0);
        CLOCK.fetch_add(100, Ordering::Relaxed) as f64
    }

    fn time() -> TimeFn {
        TimeFn { now_ns: now }
    }

    #[test]
    fn l0_closed_match_emits_reasoning_then_generation() {
        let t = walk("2 + 2", &time());
        assert_eq!(t.resolved_at, Some(CascadeTier::L0Closed));
        assert_eq!(t.final_answer.as_deref(), Some("4"));
        assert_eq!(t.hops.len(), 2);
        assert_eq!(t.hops[0].tier, CascadeTier::L0Closed);
        assert_eq!(t.hops[0].expert, SubExpert::Reasoning);
        assert!(t.hops[0].matched);
        assert_eq!(t.hops[1].tier, CascadeTier::L0Closed);
        assert_eq!(t.hops[1].expert, SubExpert::Generation);
        assert!(t.hops[1].matched);
        assert_eq!(t.hops[1].answer.as_deref(), Some("4"));
    }

    #[test]
    fn unmatched_tier_emits_reasoning_only() {
        // "pi" doesn't match L0-closed; reasoning at L0-closed records a
        // single hop with matched=false.
        let t = walk("pi", &time());
        let l0_closed_hops: Vec<&CascadeHop> = t.hops.iter().filter(|h| h.tier == CascadeTier::L0Closed).collect();
        assert_eq!(l0_closed_hops.len(), 1);
        assert_eq!(l0_closed_hops[0].expert, SubExpert::Reasoning);
        assert!(!l0_closed_hops[0].matched);
        // Resolution on L0-retrieved → reasoning + generation = 2 hops there.
        let l0_retrieved_hops: Vec<&CascadeHop> = t
            .hops
            .iter()
            .filter(|h| h.tier == CascadeTier::L0Retrieved)
            .collect();
        assert_eq!(l0_retrieved_hops.len(), 2);
        assert_eq!(t.resolved_at, Some(CascadeTier::L0Retrieved));
    }

    #[test]
    fn factorial_resolves_at_l0_closed() {
        let t = walk("6!", &time());
        assert_eq!(t.resolved_at, Some(CascadeTier::L0Closed));
        assert_eq!(t.final_answer.as_deref(), Some("720"));
        assert_eq!(t.hops.len(), 2);
    }

    #[test]
    fn l0_identifiable_resolves_unit_circle() {
        let t = walk("circumference of a unit circle", &time());
        assert_eq!(t.resolved_at, Some(CascadeTier::L0Identifiable));
        assert!(t.final_answer.unwrap().contains("2π"));
    }

    #[test]
    fn l2_reasoning_matches_but_generation_refuses() {
        let t = walk("what is the meaning of life?", &time());
        assert_eq!(t.resolved_at, None);
        // L2 reasoning hop matches (recognises the query as model-class),
        // L2 generation hop is recorded as not-matched (refused).
        let l2_hops: Vec<&CascadeHop> = t.hops.iter().filter(|h| h.tier == CascadeTier::L2).collect();
        assert_eq!(l2_hops.len(), 2);
        assert_eq!(l2_hops[0].expert, SubExpert::Reasoning);
        assert!(l2_hops[0].matched);
        assert_eq!(l2_hops[1].expert, SubExpert::Generation);
        assert!(!l2_hops[1].matched);
        assert_eq!(l2_hops[1].bit_ops, 1_000_000_000);
    }

    #[test]
    fn replay_is_deterministic_across_tier_split() {
        let a = walk("pythagorean theorem", &time());
        let b = walk("pythagorean theorem", &time());
        assert_eq!(a.resolved_at, b.resolved_at);
        assert_eq!(a.final_answer, b.final_answer);
        assert_eq!(a.total_bit_ops, b.total_bit_ops);
        assert_eq!(a.hops.len(), b.hops.len());
        for (ha, hb) in a.hops.iter().zip(b.hops.iter()) {
            assert_eq!(ha.tier, hb.tier);
            assert_eq!(ha.expert, hb.expert);
            assert_eq!(ha.matched, hb.matched);
            assert_eq!(ha.bit_ops, hb.bit_ops);
            assert_eq!(ha.answer, hb.answer);
        }
    }

    #[test]
    fn compose_resolves_at_l1_with_two_l0_retrieved_cites() {
        let t = walk("compose pi times e", &time());
        assert_eq!(t.resolved_at, Some(CascadeTier::L1));
        let l1_hops: Vec<&CascadeHop> = t.hops.iter().filter(|h| h.tier == CascadeTier::L1).collect();
        assert_eq!(l1_hops.len(), 2);
        assert!(l1_hops[1].answer.as_ref().unwrap().contains("compose("));
    }
}

This is the exact Rust file compiled into the WASM module the page just loaded. Every line that runs on your device is here. The receipt is a function of this code, not a bespoke benchmark.

About this exhibit

The cascade is the substrate's primary primitive. A small ladder of tiers — closed-form, retrieved, identifiable, cited, model-class — is walked for every query. Every tier is split into a reasoning sub-expert (classify the form) and a generation sub-expert (produce the answer in that form). Generation only fires when reasoning matches.

The visible trace IS the chain of thought. Each hop is a deterministic function; the whole trace is replayable from the query alone; no model weights are involved. Every hop carries its own picojoule receipt.

At /exhibits you can see the eight kernels (FFT, Lorenz, heat, etc.) that L0-closed evaluators would dispatch to inside a richer cascade. This exhibit shows the routing logic above them.