diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 98d47686..4b871026 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,171 @@ +## [Agent-A / Sonnet] [SCAFFOLD ONLY — no implementation, no commit] D-ATOM-4 — counterfactual.rs split-resolution-via-counterfactual-mantissa scaffold + +**D-id:** D-ATOM-4 (`atom-mailbox-substrate-v1` pillar 5 — counterfactual mantissa v2 deposit + v3 mailbox+revision). + +**File:** `crates/lance-graph-contract/src/counterfactual.rs` — ONE new file, doc-comment scaffold only (`///` rustdoc + `todo!()` bodies). No existing file was edited (lib.rs and escalation.rs untouched per constraint). No `cargo` run. No commit. + +**Confirmed (from source):** +- `is_split` / `CouncilVerdict::split` live in `crates/lance-graph-contract/src/escalation.rs` (shipped, D-PERSONA-1). +- `InferenceType::Counterfactual.to_mantissa() = -6` confirmed in `crates/causal-edge/src/edge.rs` line 75. +- Mantissa accessors confirmed as `set_inference_mantissa(&mut self, i8)` and `with_inference_mantissa(self, i8) -> Self`, both feature-gated on `causal-edge-v2-layout` (no-op stubs for v1 at lines 976/992/1002 of edge.rs). +- `CausalEdge64` is in the `causal-edge` crate (NOT a workspace member of lance-graph-contract) — zero-dep constraint requires a `trait EpisodicEdge` bridge; impl location is BLOCKED. + +**BLOCKED list:** +1. `awareness.revise` signature — not found on current contract surface; referenced only in CLAUDE.md pseudo-code. Must grep `contract::grammar` / `contract::nars` / `thinking-engine` before implementing v3. +2. `EpisodicEdge` impl location — `CausalEdge64` is in a non-workspace crate; bridge impl site is BLOCKED on workspace structure decision. +3. `MailboxId` ghost-tier assignment policy — BLOCKED on D-PERSONA-5 (ractor outer-swarm). +4. D-ATOM-1 `I4x32` axis type — `SplitPoles::axis` uses `u8` placeholder; BLOCKED on atom basis (D-ATOM-0). +5. Revision tombstone Lance link — BLOCKED on D-ATOM-5 (AriGraph hot→calcify). + +**Scaffold covers:** `SPAWN_DISSONANCE_THRESHOLD`, `SplitPoles`, `deposit_counterfactual` (v2), `EpisodicEdge` trait, `CounterfactualMailbox` + `new`/`poll`/`cancel` (v3), `FreeEnergyComparison`, `revise_if_minority_wins` (v3), `AwarenessRevise` placeholder trait, `should_spawn_mailbox` spawn gate, `CounterfactualError`, `RevisionOutcome`. + +**Tests:** none (scaffold only). **Commit:** none (scaffold only — main thread wires `mod counterfactual;`). + +--- + +## [Agent-B / Sonnet] [SCAFFOLD ONLY — no implementation, no commit] D-ATOM-5 — witness_tombstone.rs memory lifecycle scaffold + +**D-id:** D-ATOM-5 (`atom-mailbox-substrate-v1` pillar 6 — AriGraph hot/cold/tombstone; basis-INDEPENDENT). + +**File:** `crates/lance-graph/src/graph/witness_tombstone.rs` — ONE new file, doc-comment scaffold only (`///` rustdoc + `todo!()` bodies). No existing file was edited (mod.rs and all other files untouched per constraint). No `cargo` run. No commit. + +**What the scaffold contains:** +- `HotWitness` — ephemeral in-mailbox episodic working record; `///` explicitly cites E-BATON-1 (NOT a persisted singleton, never crosses mailbox boundaries). +- `calcify(hot: &HotWitness) -> SpoRecord` — hardens a stabilised fact into the cold SPO ontology; `todo!()` body; return type references `crates/lance-graph/src/graph/spo/builder::SpoRecord` (confirmed in source). +- `Tombstone` — cold episodic provenance written to Lance at mailbox-death; compressed payload field; `from_hot` + `persist` methods (`todo!()`); `///` notes GoBD-audit-by-construction (E-FIBU-GOBD-BY-CONSTRUCTION, append-only Lance = audit trail). +- `WitnessLink` — back-pointer `(spo_key, mailbox_id, tombstone_lance_version)` enforcing link integrity; `new` constructor (non-`todo!()` — trivially derived from inputs); `verify` async method (`todo!()`). + +**BLOCKED list (do NOT guess):** +1. Exact SPO quad constructor — `SpoRecord` + `SpoBuilder::build_edge` confirmed in `graph/spo/builder.rs` but `TruthValue` constructor + `Fingerprint` reconstruction from u64 keys unconfirmed. +2. Lance versioned-store write API — `WriteMode::Append` availability in lance 4.0.0 unconfirmed; tombstone Arrow schema and dataset path convention not yet defined. +3. WitnessCorpus ingestion API — `WitnessCorpus` (D-CSV-6, confirmed at `graph/arigraph/witness_corpus.rs`) holds observation provenance, not tombstone provenance; whether tombstones feed INTO it or a separate dataset is unresolved. +4. Scent/Base17 compression entry point — `Base17` confirmed via `ndarray::hpc::bgz17_bridge::Base17` (`neuron.rs`); Scent (1-byte, `bgz17` crate) is in workspace `exclude` — dep addition required before wiring. + +--- + +## [Agent-C / Sonnet] [SCAFFOLD ONLY — no implementation, no commit] D-ATOM-3 — quorum.rs per-axis quorum projection scaffold + +**D-id:** D-ATOM-3 (`atom-mailbox-substrate-v1` pillar 3 — quorum projection per axis). + +**File:** `crates/lance-graph-contract/src/quorum.rs` — ONE new file, doc-comment scaffold only (`///` rustdoc + `todo!()` bodies). No existing file was edited (lib.rs and escalation.rs untouched per constraint). No `cargo` run. No commit. + +**What the scaffold contains:** +- `AxisProjection { position: i8, confidence: f32, contested: bool }` — NARS truth per axis (frequency ≈ position-normalised, confidence ≈ quorum strength); constructor helpers `settled` / `contested`; `is_contested()`, `nars_frequency()`. +- `AxisSignal` — raw per-axis scalar inputs (trust/humility/flow/load + polarity_hint) fed to `InnerCouncil::from_signals`. +- `quorum_project(signals: &[AxisSignal], council: &InnerCouncil) -> AxisProjection` — `todo!()` body; mechanism fully `///`-documented: aggregate InnerCouncil verdicts, derive I4 position from polarity hints, mark contested on any split. +- `quorum_project_blackboard(_bb: &Blackboard) -> AxisProjection` — wide-quorum path; fully `BLOCKED`. +- `ContestHandler { DropMinority | DepositMantissa | SpawnCounterfactual }` — v1/v2/v3 staging seam to D-ATOM-4; `resolve_contest(projection, handler) -> (AxisProjection, i8)` — `todo!()`. +- 6 scaffold tests (4 non-panicking on `AxisProjection` constructors; 2 `#[should_panic(expected = "D-ATOM-3")]` for the two `todo!()` functions). + +**BLOCKED list:** +- `// BLOCKED: D-ATOM-1 (parallel)` — `atoms::AxisId` / `I4x32` type + 32-dim bipolar catalogue not yet defined; all axis-identity references are `u8` placeholders. +- `// BLOCKED: a2a_blackboard::Blackboard per-axis slice semantics` — the exact contract for which `BlackboardEntry` fields carry a per-axis vote vs per-round result, and how `Blackboard::next_round` interacts with per-axis slicing, is unclear from the source. Wide-quorum path deferred. + +**Tiering non-decision documented:** module doc explicitly records that E-LADDER-SERVES-MAILBOX §5 chose counterfactual-fork (D-ATOM-4) OVER quorum-tiering; this module exposes the projection + contested flag and hands off to D-ATOM-4 via `ContestHandler`. + +**References used:** `contract::escalation::{InnerCouncil, is_split, CouncilVerdict}` (D-PERSONA-1, shipped); `contract::a2a_blackboard::{Blackboard, BlackboardEntry}` (`support[u16;4]` + `dissonance` fields confirmed in source). + +--- + +## [D-ATOM-2] [SCAFFOLD ONLY — no impl, no commit, no cargo] recipe.rs — composition layer above atoms + +**D-id:** D-ATOM-2 (`atom-mailbox-substrate-v1.md` deliverable table). +**File:** `crates/lance-graph-contract/src/recipe.rs` (new, scaffold only). +**Worker:** Sonnet scaffold agent (2026-05-27). + +**What was scaffolded:** `StyleRecipe` (I4-32D composition over atoms; `///` explicitly states styles are compositions, not atomic fingerprints) · `PersonaRecipe` (composition of styles + `commit_threshold`/`escalate_threshold` + `purpose` + `Beta` enum with `Cold`/`Warm`/`Annealing{start,floor}`) · `RecipeTemplate` (Cranelift/JIT hook; `///` explains WHY the recipe — not the per-atom dot — is the JIT target: a 32-D i4 dot is one SIMD sequence, overhead only amortises at the fused-recipe level; `todo!()` bodies throughout) · `register_recipe(...)` / hot-load entry (Elixir-style open/closed split; add-atom = data, add-style/persona = template; `todo!()`). + +**BLOCKED list (do NOT guess):** +1. `atoms::I4x32` / `atoms::Atom` — concrete I4-32D type and atom catalogue — BLOCKED on D-ATOM-1 (being scaffolded in parallel). Stubbed as `I4x32Stub = [i8; 32]` and `AtomStub = u8`; replace with real imports once D-ATOM-1 lands. +2. `jit::StyleRegistry` API extension — `StyleRegistry::get_kernel` currently accepts `ThinkingStyle` enum, not a `RecipeTemplate`. A `register_recipe` / `get_recipe_kernel` surface must be added before `RecipeTemplate::compile` and `register_recipe` can be wired. BLOCKED on that extension; all affected bodies are `todo!()`. + +**Constraints satisfied:** zero-dep crate; no edits to `lib.rs`, `thinking.rs`, or `jit.rs`; scaffold only (all bodies `todo!()`); `// BLOCKED:` markers placed. + +--- + +## [Main-thread] [DONE — green] the 34 tactics as 34 working Rust kernels (Elixir-like behaviour) + +`crates/lance-graph-contract/src/recipe_kernels.rs` (new, wired in lib.rs). One uniform +behaviour `trait Tactic { meta(); gate(); apply(); run() }` + **34 unit-struct +implementations** (Rte..Hkf), each performing its characteristic op on a shared +`ThoughtCtx` (sd/free_energy/dissonance/temperature/confidence/rung/candidates/beliefs) +using OUR markers — CollapseGate SD thresholds (FLOW<0.15/BLOCK>0.35), Berry-Esseen noise +floor, NARS-style contradiction, XOR self-inverse for ABBA/fusion/counterfactual. Implicit +gating: Gate-bucket recipes skip in FLOW. Registry `kernel(id)` / `all_kernels()`. **5 tests +green** (all 34 dispatch+run without panic & confidence stays in range; TCP prunes; CR drops +coherence on same-topic contradiction; ICR builds the XOR counterfactual; Gate recipes skip +in FLOW). No warnings. 446 prior contract tests unaffected. Charter D4 step 1 of "per-recipe +evaluators" — these are deterministic kernels over a lightweight ctx; richer fingerprint +substrate slots behind the same trait later. + +--- + +## [Main-thread] [DONE — green] ada-rewrite charter + the 34-tactic recipe catalogue (working code) + +**Decision (charter D0):** ladybug-rs has NO relation, never will — it's the failed "empty cathedral." We rewrite on our substrate; ladybug/ada-consciousness/neo4j-rs docs are spec-references only, never deps/ports. `.claude/knowledge/ada-rewrite-charter.md` is the once-and-for-all settled-decision record (substrate, SPOQ lattice, hardware partition, 34-as-recipe-targets, build order). + +**Code:** `crates/lance-graph-contract/src/recipes.rs` (new, wired in lib.rs) — the **34 reasoning-tactic recipes as a working catalogue**: `Recipe {id, code, name, Tier, Mechanism, Bucket, Coverage(spo2cubed), substrate}` + `RECIPES: [Recipe;34]` + `recipe()/recipe_by_code()/by_mechanism()/causal()`. Each tagged with the OUR-substrate primitive that realizes it (composes our pieces, never ladybug). **4 tests green** (complete 34 / ids unique, lookups, only RCR+ICR are 2³-Covered, mechanism tally 6/6/8/14). 442 existing contract tests unaffected. + +**Next (charter D4):** per-recipe evaluators tier-by-tier (Hard-tier truth/parallel first — substrate most built), then shader-driver carrier wiring for the datapath recipes. + +--- + +## [Inventory-Opus] [DONE — writes were permission-blocked; persisted by main thread] SPO-2³ workspace list inventory + +Catalogued **31 enumerated cognitive lists** across contract / planner / cognitive-shader-driver / thinking-engine / holograph + 3 markdown taxonomies. **2³ tally: Covered 4 / Partial 6 / Not 21** (confirms 2³ = the causal spine only — CausalMask / nars_engine masks / PearlLevel / CANONICAL_ATOMS Pearl lanes are the lattice; everything style/qualia/rung/layer/ghost/MUL is orthogonal). Gaps (reference tactics with no enumerated lance-graph home): #18 CWS, #14 M-CoT, #29/#32 intent, #16/#23/#33 meta-prompting, #12 TCA, #22/#19 dynamic-decompose, #5/#20 pruning — but ladybug-rs implements all 34 upstream. Result persisted into `.claude/knowledge/spo-2cubed-list-coverage.md`. No code edited. + +--- + +## [Main-thread] [DESIGN — captured, not implemented] E-LADDER-SERVES-MAILBOX — atom/quorum/mantissa/AriGraph-hot-cold synthesis + +**What:** Captured a multi-turn design dialogue as `EPIPHANIES.md` E-LADDER-SERVES-MAILBOX (2026-05-27). No code; design crystallization only. Branch `claude/splat3d-cpu-simd-renderer-MAOO0`. + +**Six pieces:** (§1) the escalation ladder serves the **mailbox**, not the persona — persona is a Layer-2 dispatch policy per I-VSA-IDENTITIES, not a container; business/chat/OSINT = three β-policies over one substrate. (§2) 3-layer **atoms → thinking styles → persona recipes**: the 36 `contract::thinking` styles demote to **atoms** (I4-32D, 32 bipolar dims / 64 poles); styles+personas are compositions; Cranelift templates compile the *recipe*, not the atom-dot. (§3) the **quorum crux**: a dichotomy needs a quorum to project; each atom = `(I4 position, quorum-confidence)` = NARS truth per axis; splits held as Contradiction, never averaged. (§4) **wisdom↔Staunen = temperature** (self-regulated by free energy; explains the D-PERSONA-1 `WisdomMarker` 0.1 floor = min temperature). (§5) split-resolution = **counterfactual mantissa** (`CausalEdge64` −6 nibble), staged v1/v2/v3, ghost-tier test + `awareness.revise` reopen. (§6) **AriGraph hot/cold/tombstone**: ephemeral-hot in mailbox → calcify to cold SPO → tombstone-witness in versioned Lance (= GoBD audit by construction). + +**Honesty flags in the entry:** marked CONJECTURE/design (anchored to 4 FINDING-grade iron rules); the atom-basis derivation is the OPEN load-bearing step; NARS *type* selectors flagged as belonging in a register (Test 0), not as bipolar atoms; `WitnessCorpus` + `SigmaTierRouter` Σ-tier D-ids cited from dialogue are marked **to-verify** against the board (NOT asserted as fact). + +**Explicitly NOT done (pending greenlight):** D-ATOM-1..5 not queued in STATUS_BOARD (design, not deliverables yet); substrate-Markov re-scope deferred behind the [FORMAL-SCAFFOLD] dependency check; `rung-persona-orchestration-v1` → mailbox-centric rename awaits explicit go (touches D-ids). Three corrections to my own prior turns are recorded in-thread: conflated VSA-substrate Markov with episodic Markov; mis-sized MUL trust/DK as two axes (it's one); initially read the "36" as styles (it's atoms). + +**Tests:** none (no code). **Commit:** this entry + EPIPHANIES prepend. + +--- + +## [Main-thread] [IN PROGRESS] D-PERSONA-1 — escalation+epiphany loop = the boot checklist + +**D-id:** D-PERSONA-1 (`rung-persona-orchestration-v1` §2 + §7). Restore-on-SoA of ladybug's qualia loop (collapse-hint + InnerCouncil/HdrResonance split + EpiphanyDetector + ghost residue) onto our contract types — NOT a bespoke verifier. Branch `claude/splat3d-cpu-simd-renderer-MAOO0`. + +**Worker:** main thread (Opus). No subagents spawned — single-module accumulation, kept on the main thread. + +**Files added:** +- `crates/lance-graph-contract/src/escalation.rs` (zero-dep machinery): `CollapseHint` {Flow/Fanout/RungElevate} + `fanout_width`/`noise_tolerance`/`rung_delta` (ladybug `detector.rs` formulas); `Archetype` + `InnerCouncil::{deliberate, from_signals}` + `is_split(0.7,0.5)` ×1.2 split-amplify → `CouncilVerdict`; `EpiphanyDetector` (sim > baseline×1.5 ∧ window ≥ 4) → `Epiphany`; `GhostEcho` (8 named) + `WisdomMarker` (asymptotic decay → 0.1 floor); `Checklist`/`ChecklistItem` (HARD/SOFT, green-flip, `mark_red` let-it-crash). Registered in `lib.rs`. +- `crates/lance-graph-planner/src/mul/escalation.rs` (wiring): `boot_checklist()` (§2: 6 HARD / 3 SOFT) + `verdict_from(&MulAssessment)` adapter. Registered in `mul/mod.rs`. + +**Reused, not reinvented (consult-before-guess):** the §1 click already lives in `contract::grammar::free_energy` (`FreeEnergy::compose`, `Resolution::{Commit,Epiphany,FailureTicket}`, homeostasis/epiphany/failure thresholds); the MUL types in `contract::mul` (TrustTexture/DkPosition/FlowState/GateDecision + i4 SIMD eval). D-PERSONA-1 adds only the per-item escalation loop on top. + +**Tests:** 13 green (10 contract `escalation::`, 3 planner `mul::escalation::`). Only pre-existing `nars_engine.rs` deprecation warnings, unrelated. + +**Board hygiene (same change):** STATUS_BOARD `rung-persona-orchestration-v1` section added (D-PERSONA-1 In progress, D-PERSONA-2..6 Queued); LATEST_STATE contract inventory `escalation` entry; TECH_DEBT TD-GHOST-ECHO-DUP-1 (GhostEcho vs thinking-engine GhostType — zero-dep forces the dup; reconcile when thinking-engine joins the workspace). + +**Pending:** D-PERSONA-2 (meta-recipe manifest) consumes `Checklist::all_flow` as the compose gate; D-PERSONA-3 cold-path promotes repeated `GhostEcho::Epiphany` → `Wisdom` (`WisdomMarker::promote_to_wisdom`). + +--- + +## [Fleet surreal-poc Wave-A] [WIP — drafts un-reviewed] SurrealDB-on-Lance container POC, tasks 01+02 + +**D-id:** surreal-01 (deps_substrate, lance-graph) + surreal-02 (soa_container_type, ndarray). Wave A of the 12-task `.claude/surreal/` POC — disjoint-file-scoped Sonnet agents, edit-only, shared checkout. + +**Workers:** 2× Sonnet (background, edit-only). Both behaved correctly under the anti-hallucination guards: task 01 left 4 `// BLOCKED:` markers instead of inventing versions/APIs; task 02 left 3 BLOCKERs (bytemuck / odd-N pad / naming). + +**Files (WIP, NOT yet compiled by Opus):** +- ndarray `src/hpc/soa.rs` — `SoaContainerHeader` LE `#[repr(C)]` draft (committed `547824bc` on ndarray branch). +- lance-graph `crates/surreal_container/` scaffold + `Cargo.toml` member. + +**Resolved by Opus:** the lance-version "conflict" was a false alarm — workspace is canonically on **lance 4.0.0 / lancedb 0.27.2 / datafusion 52 / arrow 57 / rust 1.95** (CLAUDE.md "lance = 2" line is stale; `crates/lance-graph/Cargo.toml:36` + workspace `Cargo.toml:50-54` confirm 4.0.0, Lance-6 = future). Task-01 unblock = `surrealdb-core` git dep (`AdaWorldAPI/surrealdb`, feature `kv-lance`) + embedded `Datastore::builder().build_with_path("lance://..")` (verified from the fork's integration_tests). + +**Pending:** Opus review/correct/compile of both drafts → pin `SoaContainer` interface before fanning out Wave B (03/07/10) → savant meta-review (`simd-savant` + PP-13/15/16) → PR. + +--- + ## [Fleet sprint-13-w-i1-salvage] [IN PR] D-CSV-13b i4 batch SIMD dispatch (branch claude/sprint-13-w-i1-salvage) **D-id:** D-CSV-13b — SIMD vectorization of i4 MUL evaluation. AVX-512F+BW path (8 elements/iter), NEON path (2 elements/iter), scalar fallback. Runtime dispatch via cached `simd_caps()` (`AtomicU8`); zero ndarray dep preserves contract-crate zero-dep posture. diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 09ee6539..fb4bf42b 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,422 @@ +## 2026-05-27 — E-LADDER-SERVES-MAILBOX — the escalation ladder serves the *mailbox*, not the persona; atoms (bipolar I4-32D) are the bottom layer, measured by *quorum*, with split-poles preserved as a counterfactual mantissa; AriGraph is ephemeral-hot in the mailbox and calcifies to cold SPO + a Lance tombstone-witness + +**Status:** CONJECTURE / design-synthesis (a session design arc, anchored to four FINDING-grade iron rules below; NOT yet implemented). Refines `rung-persona-orchestration-v1` (the *name* "persona" is demoted — see §1 of this entry). Supersedes nothing; extends `E-CHECKLIST-AS-ESCALATION` (D-PERSONA-1) downward (atoms) and outward (mailbox lifecycle). + +**Grounded anchors (FINDING):** `I-VSA-IDENTITIES` (persona = Layer-2 catalogue; Test 0 register-laziness; bipolar ±1 role keys; Test 2/3 orthogonality+cleanup), `E-BATON-1`/Baton-scoping (mailbox-as-owner, bundle ephemeral, no persisted singleton), `I-LEGACY-API-FEATURE-GATED` (`CausalEdge64` 4-bit signed mantissa @46-49, `InferenceType::Counterfactual = to_mantissa() = −6`), The Click (Staunen×Wisdom = Contradiction magnitude; "opinions are committed contradictions preserved, not resolved"; `awareness.revise`; F<0.2 Commit / ΔF<0.05 Epiphany / F>0.8 FailureTicket). + +--- + +### §1 — The ladder serves the mailbox; "persona" was mis-centered + +`rung-persona-orchestration-v1` framed the escalation→epiphany→ghost ladder as serving a *persona*. **Wrong primary object.** Per `I-VSA-IDENTITIES`'s own unification — *"Archetype / persona / thinking-style … are Layer-2 role catalogues; each entry gets ONE identity fingerprint; content lives in YAML; resonance dispatches to content"* — **persona is a dispatch policy, not a container.** The owned unit is the **mailbox** (mailbox-as-owner, sea-star). The ladder is *mailbox* machinery; a persona only decides *what to fan out as mailboxes* and *where the temperature/β knob sits*. The D-PERSONA-1 **types are already mailbox-shaped** (`Checklist`, `CollapseHint`, `InnerCouncil`, `GhostEcho`) — so this is a reframe + (pending) rename, not a rebuild. Three personas = three policies over one substrate: + +| persona | β / temperature | fan-out pattern | +|---|---|---| +| business | cold (exploit) | business-logic checkboxes → supervised mailboxes, **respawn-if-failed** (bounded) | +| chat | warm (explore) | episodic persona-modeling + self-state-awareness **over witness-arcs** (never a persisted self-singleton — E-BATON-1) | +| OSINT | **annealing** (hot→cold) | self-generated hypothesis-mailboxes; cross-style synthesis = the Layer-1 `a2a_blackboard` driven *autonomously*; provenance→NARS-confidence gates the Rubikon (untrusted sources never commit as fact); preserve `dissonance`, never average | + +Constraint: checkbox-as-mailbox fan-out + respawn lives at the **outer swarm boundary** (ractor, async), the inner Click stays sync — do not double-mailbox (E-BATON-1). Respawn needs a bounded supervision policy (N retries → `FailureTicket`), or crash-loop. + +### §2 — Three layers: atoms → thinking styles → persona recipes + +- **Atoms** — the bottom layer. The current `contract::thinking` "36 ThinkingStyles" are **demoted to atoms**, encoded **I4-32D**: 32 *bipolar* dimensions → **64 poles** (32−, 32+) = 16 bytes/vector. The atom set is the orthogonal basis + cleanup codebook (satisfies `I-VSA-IDENTITIES` Test 2/3, which 36 ad-hoc style-fingerprints did *not*). +- **Thinking styles** (Kant, Schopenhauer, bookkeeping-savant) = I4-32D **compositions** over atoms. +- **Persona recipes** = compositions of styles + thresholds, purpose, β. + +**JIT placement:** atoms + styles stay **I4-32D data**; the **persona recipe** is what gets templated into a Cranelift `KernelHandle` (`contract::jit` `StyleRegistry`). A 32-D i4 dot is one SIMD sequence — Cranelift overhead only amortizes at the fused-recipe level. Add-atom = data; add-style/persona = template (Elixir-style hot-load open/closed split). + +**Pole budget (user's allocation, with the atom-kind caveat):** the named axes that are genuinely *bipolar-continuous* — trust↔DK (one calibration axis, **not** two — see §3), wisdom↔Staunen (= temperature, §4), plasticity (rigid↔plastic), 6 hardwired business-logic dichotomies (FIBU/GoBD §6b) — are correct atoms. **But NARS inference-type / strategy / semiring are *categorical selectors*, not bipolar magnitudes** (`contract::nars` = `InferenceType(5)` + `QueryStrategy(5)` + `SemiringChoice(5)` + Pearl 2³ ≈ the "24"). By Test 0 (register-laziness) those belong in an **enum/register that gates which atoms fire**, not as poles. Allocating ~24 of 32 dims to NARS likely miscategorizes discrete selectors as continuous atoms. NARS *truth* (frequency, confidence) IS continuous → atoms; NARS *type* → register. **OPEN:** the atom-basis derivation (ICA/PCA over the 36 / theory-driven from the 6 clusters / hybrid) is the load-bearing unsolved design step. (i4 precision floor: documented tradeoff, cite `FormatBestPractices.md` Jirak; SIMD path gated on MANDATORY `ndarray-vertical-simd-alien-magic.md`.) + +### §3 — The crux: a dichotomy needs a *quorum* to project, and a split is not averaged + +A bounded dichotomy does not yield its projection for free. To place a measurement between two poles you need a **quorum**; a *split* quorum means the projection is **contested, not merely noisy**. Every one of the 32 axes inherits this — the universal cost of bipolar structure. The quorum machinery already exists: **`InnerCouncil` `is_split(0.7,0.5)` + ×1.2 split-amplify** (3-archetype vote) and the Layer-1 **`a2a_blackboard` `support[u16;4]` + `dissonance`** (wide quorum). Therefore **each I4-32D atom value is a quorum *output*, i.e. a pair `(I4 position, quorum-confidence)` = NARS truth `(frequency, confidence)` applied per axis.** A split is held as a **Contradiction, never averaged** (averaging contested state = laundering false confidence; the cardinal OSINT sin). + +### §4 — wisdom↔Staunen = temperature (control axis, self-regulated) + +This axis is *not* a measured feature like trust/DK — it is **sampling temperature** (Wisdom = low-temp/sharp/exploit; Staunen = high-temp/diffuse/explore), the same knob as the EFE explore/exploit β. It is **self-regulated by free energy** (thermostat): high surprise → Staunen → hot → wide sampling; F descends → Wisdom accrues → cools → commits. **This retroactively explains the `WisdomMarker` 0.1 floor** built in D-PERSONA-1: that floor *is the minimum temperature* — the φ-1 "permanent humility" ceiling means cognition never anneals to absolute zero. Distinct from **plasticity** (update-rate): you can run hot-but-rigid or cold-but-plastic, so both keep separate dims. Open layout question: temperature as a flat peer dim, or a **meta-atom read first** that sets the sampling sharpness for unbinding the other 31 (one-pass vs two-stage I4 sweep). + +### §5 — Split resolution = counterfactual mantissa (replaces quorum-tiering) + +On `is_split`, do **not** widen the quorum through tiers (too complex). Instead **commit the majority pole and fork the minority pole into a counterfactual-testing mailbox, retained as an episodic mantissa.** "Mantissa" is literal: the minority pole is a single `CausalEdge64` **−6 (Counterfactual) nibble** in the episodic witness — the road-not-taken costs **4 bits**, not a replay buffer (committed pole = coarse exponent / direction; counterfactual = fine mantissa / "could-have-been"). This is the mechanical form of *"contradictions preserved, not resolved"*, satisfies the counterfactual-stays-in-a-separate-lane rule (Counterfactual-tagged, never written as observed SPO truth), and IS the rung-ghost's counterfactual-learning fuel. **Loop closes via revision:** the counterfactual mailbox is **ghost-tier** (preemptible to zero, tests only on β headroom); if its test later beats the committed pole (lower F), that is a **NARS `awareness.revise`** on the original axis — the road-not-taken reopens and can overturn the verdict. **Staging:** v1 commit-majority/drop-minority → v2 deposit the −6 mantissa (contradiction-honesty for 4 bits, no spawn) → v3 full counterfactual-mailbox + revision loop. Spawn gated on dissonance/Staunen > threshold; ghost Staunen-keyed GC prunes counterfactuals that never pay. + +### §6 — AriGraph: ephemeral-hot in the mailbox, calcified-cold in SPO + Lance tombstone-witness + +AriGraph is **not a persisted singleton graph** (E-BATON-1). Its live/episodic form is **ephemeral inside the mailbox** (the working hot path). When a fact stabilizes it **calcifies into the SPO ontology** (cold, persistent). When the ephemeral mailbox dies (sea-star spawn→die→merge), its **witness persists as a tombstone in Lance**, linking the calcified cold fact back to the mailbox that committed it. This is one **compression hierarchy down the codec atlas**: + +``` +hot (full-fidelity ephemeral AriGraph, in mailbox) + → calcified semantic (SPO-G quads, cold, Lance) — "what is believed" + + tombstone witness (Lance, compressed ~Scent/Base17) — "what happened / who committed it" + + counterfactual residue (CausalEdge64 −6 mantissa, 4 bits) — "the road not taken" +``` + +Fallout: because Lance is append-only/**versioned**, the tombstone layer *is* the audit trail — GoBD/provenance falls out of the substrate by construction (`E-FIBU-GOBD-BY-CONSTRUCTION`, §6b), not as bolted-on logging. The one thing to nail is **link integrity**: the calcified SPO fact needs a durable back-pointer to its tombstone, and the tombstone must outlive the mailbox — Lance versioning is the right home for both. + +--- + +**Proposed deliverables (NOT yet queued — pending greenlight):** D-ATOM-1 atom-basis derivation + I4-32D layout; D-ATOM-2 style/persona Cranelift recipe templates; D-ATOM-3 quorum-projection `(position, confidence)` per axis; D-ATOM-4 counterfactual-mantissa v2 (deposit) then v3 (mailbox+revision); D-ATOM-5 AriGraph hot→calcify→tombstone wiring. + +**To-verify (cross-refs asserted from this session's dialogue, confirm against board before relying):** the `WitnessCorpus` deliverable D-id (CAM-PQ-indexed witness + salience decay) and the `SigmaTierRouter` Rubicon-resonance Σ-tier D-id were cited in-conversation as the homes for the tombstone-witness and the Rubikon admission gate respectively — confirm exact ids in `STATUS_BOARD.md` / `PR_ARC_INVENTORY.md`. + +**Still pending separately (NOT folded here):** the substrate-Markov **re-scope** (substrate Markov = guarantee for *unsolicited materialization* only; episodic Markov is the governing transition account) awaits the `[FORMAL-SCAFFOLD]` dependency check (do the four pillars need substrate Markov as the *transition account* or only as the *guarantee*?) before it can be written as an `I-SUBSTRATE-MARKOV` amendment. The `rung-persona-orchestration-v1` → mailbox-centric **rename** also awaits explicit go (touches D-ids). + +**Cross-ref:** `I-VSA-IDENTITIES`, `E-BATON-1`, `I-LEGACY-API-FEATURE-GATED`, `E-CHECKLIST-AS-ESCALATION` (D-PERSONA-1: `InnerCouncil`/`EpiphanyDetector`/`GhostEcho`/`WisdomMarker`), `E-FIBU-GOBD-BY-CONSTRUCTION` (§6b business atoms + GoBD audit), `E-OGIT-STAKES-LINCHPIN` (stakes→temperature→savant, the front-door inheritance), The Click (Staunen×Wisdom, Resolution thresholds, `awareness.revise`); `contract::thinking` (36→atoms), `contract::jit` (`StyleRegistry`/`KernelHandle`), `contract::mul` (i4 SIMD eval, `DkPosition`/`TrustTexture`), `contract::nars` (5/5/5 selectors → register), `contract::a2a_blackboard` (`support`/`dissonance` quorum); `FormatBestPractices.md`, `ndarray-vertical-simd-alien-magic.md` (MANDATORY before the I4 SIMD path). + +**CORRECTION (2026-05-27, append-only) — §2 atom framing was wrong; superseded by `E-AGICHAT-DIMENSION-CONTRACT` + `.claude/knowledge/atom-basis-inventory.md`:** §2 said "the 36 `contract::thinking` styles demote to atoms (I4-32D, 32 bipolar dims / 64 poles)" — **retracted.** The atom basis is the **LOCKED 33-dim TSV** (3 Pearl + 9 Rung + 5 Σ + 8 Operations + 4 Presence + 4 Meta), NOT derived and NOT the 36 styles. Corrected layering: **atom = one lane** of that TSV (smallest unit, bare-metal, not human-legible); **style = one i4 vector over the atoms** (the molecule — the 36 `ThinkingStyle` ids resolve to such vectors); **persona = composition of styles**. Atoms are **not SIMD** — execution stack is **atoms → `cognitive-shader-driver` → SIMD**; the atom layer holds the carrier + catalogue and dispatches through the driver. Business is an **OGIT-inherited sidecar**, not an atom. The OO style/persona object layer (D-ATOM-2) is the actual metacognition; atoms are the bytes it rides. Code: `contract::atoms::CANONICAL_ATOMS` (locked 33). (This append-only correction follows the workspace's Storno-as-append rule — the wrong §2 is preserved, not edited.) + +--- + +## 2026-05-26 — E-RIGID-RULES-OPEN-DOORS — rigidity belongs to the rules (the HOW, stakes-gated), never to the stance toward a door opening (the WHETHER-to-welcome, baseline-positive); and the welcome is *learned* per topic×texture, not naive + +**Status:** FINDING / stance-correction (rebalances the rigidity emphasis of `E-FIBU-GOBD-BY-CONSTRUCTION`; adds the learned-texture policy). Refines `rung-persona-orchestration-v1` §9. + +**Click:** We *are* SPO — a "door opening" is a new viable triple/edge, a 2³ projection screening-in, or an `EpiphanyDetector` fire. Two axes that must not be conflated: + +1. **rule-rigor** scales with stakes/`Marking` (`Financial`→hard `Soll=Haben`/GoBD/immutability) — the **HOW**. +2. **door-welcome valence** is a baseline-positive stance toward novelty, **stakes-independent** — the **WHETHER**. + +Stakes gate the rigor, never the welcome. A bookkeeper is strict on the books *and* glad a new client walked in. So: a door opening is a **rewarded epiphany** (wisdom-marker grows; Affinity/Epiphany/Staunen ghosts brighten), not merely permitted; the MUL gate evaluates rigorously without being a sour bouncer; even a rule-`NO` carries no hostility to the door. Rigidity everywhere → no epiphanies; openness everywhere → can't hold money. **Rigid HOW, happy WHETHER.** (Corrects a drift: the FIBU/GoBD commits over-weighted rigidity.) + +**The welcome is learned, not naive.** *If in doubt, the agent fingerprint learns over time, per topic, which `TrustTexture` (Murky/Dissonant/Fuzzy/Clear, `mul/trust.rs`) means **don't touch** and which means **engage** — and vice-versa.* The learned `topic × texture → touch/avoid` policy lives in the wisdom-marker (cold-path / `CrystalCodebook`; content keyed by fingerprint, not bundled — I-VSA-IDENTITIES). Decision ladder: (1) hard rule → follow (rigid); (2) no rule, learned policy exists → follow it; (3) in doubt (no rule / thin history / Murky) → cautious-exploration + Lab, and record the outcome to grow the policy. Young fingerprint = rules + cautious-exploration; mature fingerprint = *taste*. The learning IS the calibration-gap closing. + +**Cross-ref:** `rung-persona-orchestration-v1` §9; `rung-mul-grounding-v1` §1 (calibration gap, experience curve); `E-FIBU-GOBD-BY-CONSTRUCTION` (the rigidity rebalanced); `E-CHECKLIST-AS-ESCALATION` (EpiphanyDetector = door opening; ghosts); `mul/trust.rs` (`TrustTexture`). + +--- + +## 2026-05-26 — E-FIBU-GOBD-BY-CONSTRUCTION — German Finanzbuchhaltung is already partly in-code; GoBD legal compliance falls out of the substrate's pure-engine + digested-rules + append-only + Storno-as-append invariants — not a bolt-on + +**Status:** FINDING (corrects the "FIBU is net-new" assumption; refines `rung-persona-orchestration-v1` §6b + D-PERSONA-6). + +**Click:** "FIBU" (Finanzbuchhaltung) is **not net-new** — the Financial subtree is DACH-first and developed in-code: `contract::grammar::role_keys` has the German SMB catalogue (`KUNDE/SCHULDNER/MAHNUNG/RECHNUNG/DOKUMENT/BANK/FIBU/STEUER`, `FIBU_KEY` @[13072..13584)); `contract::tax.rs` has a **pure `TaxEngine`** (`collect(rule_bundle, period, entries)`, nondeterminism = `Err`), the **`fibu_entry`** RecordBatch (`booking_code, amount, tax_rate`), DACH `Jurisdiction {De, At, Ch}`, and a versioned + 32-byte **digested** `RuleBundle`; `SKR04` is in the foundry roadmap; DATEV/GoBD/BaFin are pre-flagged regulated-tenant triggers (`lf-integration-mapping-v1` LF-80/81). So the Odoo harvest **extends** this (`l10n_de`: SKR03/04→`booking_code`, USt→`RuleBundle`, DATEV→wire), it does not invent it. + +**The convergence:** German bookkeeping law **GoBD** (*Unveränderbarkeit / Festschreibung / Nachvollziehbarkeit* — immutable, audit-traceable, deterministic books) **falls out of the substrate by construction**, not as a compliance layer: + +| GoBD requirement | substrate invariant that already provides it | +|---|---| +| deterministic books | **pure `TaxEngine`** (nondeterminism = `Err`) | +| audit checksum / rule provenance | **digested `RuleBundle`** (32-byte digest, versioned) | +| Unveränderbarkeit (immutability) | **append-only** postings + boards + CausalEdge64 move-semantics | +| correction = reversal, not edit | **Storno-as-append** = *"committed contradictions preserved, not resolved"* (CLAUDE.md) | + +So at `Financial`/FIBU stakes the MUL gate's hard invariants are: **Soll = Haben**, **GoBD immutability** (Storno-append, never edit), **SKR account validity**, **deterministic tax**. Storno is exactly the workspace's append-only-correction pattern (this very entry corrects a prior assumption by *appending*, not editing). + +**Cross-ref:** `rung-persona-orchestration-v1` §6b + D-PERSONA-6; `contract::tax.rs`, `contract::grammar::role_keys` (FIBU_KEY); `foundry-roadmap-unified-smb-medcare-v1` (FiBu/SKR04); `lf-integration-mapping-v1` LF-2/LF-80; `E-OGIT-STAKES-LINCHPIN` (marking=Financial→stakes). + +--- + +## 2026-05-26 — E-OGIT-STAKES-LINCHPIN — stakes is an O(1) ontological lookup (OGIT class), and it is the single dial that drives temperature + MUL sensitivity + savant binding together + +**Status:** FINDING (grounds the MUL gate ratio + the front-door inheritance; refines `rung-mul-grounding-v1` §3 + `rung-persona-orchestration-v1` §1). **External ref — `AdaWorldAPI/OGIT` (Open Graph of IT, `ogit.ttl`, OWL/RDF, DOLCE-aligned), NOT in GitHub-MCP allowlist; reference-only.** + +**Click:** Two user observations are the same mechanism. (1) `MUL ≈ (risk / competence) × stakes` with `competence = f(rung-level, resonance)`. (2) "a chat inherits temperature; an invoice inquiry inherits the bookkeeping savant." The bridge: **`stakes` is not hand-assigned — it is an O(1) lookup of the request's OWL/DOLCE class in OGIT** (the ontology reframed as a CAM). And that one number drives three things at once: + +| request | OGIT class → stakes | inherited temp (viscosity) | MUL sensitivity | savant (dominant family) | +|---|---|---|---|---| +| chat | casual communicative act → low | hot (Plasma) | loose | generalist / exploratory | +| invoice inquiry | economic object → high | cold (Crystalline) | tight (`×stakes`) | bookkeeping savant | + +**`felt_parse` is the front door:** viscosity = inherited start temperature, dominant axis-family = which savant binds; OGIT-class = stakes. So the inheritance the user described is `felt_parse` + an O(1) OGIT class lookup — no new dispatch layer. The MUL gate fires ∝ expected-loss / competence (DK danger zone gates hardest), with stakes ontologically grounded. + +**The ontology IS a graph** ⇒ OGIT lives natively as an AriGraph/SPO + CAM-PQ class layer; O(1) class address = the "3-dims-are-the-address" CAM pattern. No second store needed (AriGraph is the one graph). + +**Open (CONJECTURE):** whether `stakes` is an explicit OGIT annotation or derived from class position — confirm against `ogit.ttl`. README on `main` 404'd; repo-root gave only the high-level "semantic representation of all IT + business processes" description. + +**Cross-ref:** `rung-mul-grounding-v1` §3 (MUL gate ratio); `rung-persona-orchestration-v1` §1 (front-door inheritance); `E-CHECKLIST-AS-ESCALATION` (felt_parse collapse-hint); `I-VSA-IDENTITIES` (CAM addressing). + +**RESOLVED (same session, in-code grounding — supersedes the CONJECTURE above):** OGIT is in code as `lance-graph-ontology`. `stakes = Marking` (`Public < Internal < Pii ≈ Financial < Restricted`) — an **explicit field** on the `MappingRow`, resolved O(1) via `SchemaPtr` (packed `[namespace_id:8 | entity_type_id:16 | kind:8]` + `ontology_context_id` = the active named-graph / "active schema poll"). `Financial`'s doc literally reads *"bookkeeping or tax-relevant"* → grounds invoice→bookkeeping-savant. **The full O(1) inherit-set** the front door returns from one `MappingRow`: `marking`→stakes, `thinking_style`→savant, `qualia_meta`(qualia[18]/MetaWord/CausalEdge64)→qualia+dispatch prior, `confidence`→competence prior, `identity_codec`→CAM-PQ resonance address, `semantic_type`→attribute interpretation, `ontology_context_id`→active context. Table in `rung-persona-orchestration-v1` §1. + +--- + +## 2026-05-26 — E-CHECKLIST-AS-ESCALATION — the boring checklist is NOT a bespoke verifier; it collapses into escalation-work + epiphanies, restoring ladybug's qualia loop on the SoA floor + +**Status:** FINDING (simplifies `rung-persona-orchestration-v1` D-PERSONA-1; user-flagged collapse). **External design refs — ladybug-rs `src/qualia/{council,felt_parse,resonance}.rs` @177a321, NOT in the GitHub-MCP allowlist; reference-only, never a port target.** + +**Click:** The "boring checklist → meta-recipe" of `rung-persona-orchestration-v1` does not need a new verifier subsystem — the list-completion machinery already exists in ladybug's qualia loop and only needs restoring on our SoA: + +- **`felt_parse` emits a collapse hint** = {Flow, Fanout, RungElevate}: Fanout = gather more (escalate breadth), RungElevate = deepen (rung-shift), Flow = done. *The item's escalation decision is already produced* — "the list as escalation work" verbatim. +- **`InnerCouncil.deliberate`** (3 archetypes Guardian/Catalyst/Balanced, majority vote) + **`HdrResonance`**: a **split** (`is_split(0.7,0.5)` — one archetype sees what the others don't) is amplified ×1.2 for epiphany detection. **Disagreement IS the learning signal** = our SPO screening-off (perspectives disagree about a projection ⇒ spurious `S_O` caught). +- **`EpiphanyDetector.observe`** (council.rs:158): `Some(Epiphany)` iff `similarity > baseline×1.5 ∧ recent_samples ≥ 4`. The **window≥4 guard is the anti-Mount-Stupid evidence rule** (same shape as window-5 / Boole-bound — never fire on thin evidence). A green-flip = an epiphany committed to the graph, not a checkbox. +- **Ghost echoes** = {Affinity, **Epiphany**, Somatic, **Staunen**, **Wisdom**, Thought, Grief, Boundary} — persistent qualia residue (asymptotic decay to 0.1, never zero; felt_parse:70). Epiphany/Staunen/Wisdom-as-ghosts ARE the wisdom-marker substrate, already named; **8 ghosts ≤ 32 ✓ I-VSA-IDENTITIES**. (CLAUDE.md "Magnitude = Staunen × Wisdom qualia" — the ghosts are already in The Click.) + +**The collapse:** list-item → collapse-hint (escalate) → council/resonance (split = discovery) → EpiphanyDetector (close, evidence-gated) → Epiphany/Wisdom ghost (persist). **Escalation IS the work; epiphanies ARE the completions; ghosts ARE the hydrating wisdom.** D-PERSONA-1 drops from "checklist verifier" to "wire the existing loop." + +**Honest gap (unchanged):** ladybug's `detector.rs` still has no NaN/dead-end/escalation path ("all inputs produce valid output") — our NaN→cautious-exploration→Lab remains net-new. + +**Cross-ref:** `rung-persona-orchestration-v1` §2+§7; `rung-mul-grounding-v1` (screening-off = split); `E-AGICHAT-DIMENSION-CONTRACT` (restore-on-SoA); `I-VSA-IDENTITIES` (8 ghosts ≤32). + +--- + +## 2026-05-26 — E-AGICHAT-DIMENSION-CONTRACT — the 32-dim basis already exists as agichat's locked 10kD allocation; ladybug-rs de-grounded it by inflating bytes→10K-bit fingerprints; the work is to RESTORE the contract on the SoA floor, not invent or port + +**Status:** FINDING (resolves the open `ThinkingStyleI4_32D` basis decision from E-I4-META-1; lineage + grounding map established from user-provided sources). **External design references — NOT in the GitHub-MCP allowlist; design-reference only, never a code-port target.** + +**Click:** A long session walking two upstream repos — `AdaWorldAPI/ladybug-rs` (Rust) and the older `AdaWorldAPI/agi-chat` (Py/TS) — settled the entire "which 32 dims / how to ground" thread. The basis was never something to invent: it is **agichat's locked 10kD dimension allocation** (`docs/CANONICAL_DIMENSION_ALLOCATION.md`, "Status: LOCKED"). + +**Lineage (the key reframe):** + +> **agichat (Py/TS) = the grounded byte-contract** → **ladybug-rs (Rust) = inspired but de-grounded (inflated bytes→10K-bit VSA fingerprints) → never worked** → **workspace (ndarray+lance-graph) = restore the contract on the SoA/SIMD floor.** + +The user's account: ladybug-rs was "magically inspired but never informationally grounded, no LE contract"; it ran **10,000 vectors × 10,000-D** (~700 MB–1.4 GB RAM) and produced **no meaningful output — "an idealized cathedral."** The failure is mathematically forced: VSA bundle capacity is `N ≤ √d/4` (= 25 at d=10000), so resonating across 10,000 vectors is ~400× over capacity → noise (`I-NOISE-FLOOR-JIRAK`: classical stats on weakly-dependent bundles is meaningless). agichat had the *grounded* form (bytes + locked dimension ranges); ladybug-rs inflated every byte/dimension into a 10K-bit fingerprint and lost it. + +**THE BASIS — agichat's 33-dim ThinkingStyleVector** (`[175:208]`, detailed at `[256:320]`), which IS the i4-32 thinking-style fingerprint: + +- **3 Pearl** (SEE / DO / IMAGINE = association / intervention / counterfactual) +- **9 Rung** (R1–R9, meaning-depth) +- **5 Sigma** (Ω / Δ / Φ / Θ / Λ — the σ-tier chain) +- **8 Operations** (abduct / deduce / induce / synthesize / preflight / escalate / transcend / model_other) — the fanout's 4 inference modes are 4 of these +- **4 Presence** (authentic / performance / protective / absent) +- **4 Meta** (confidence_threshold / preflight_depth / exploration / verbosity) + += **33** (matches `STYLE_ENCODING.md`'s "3 Pearl + 9 Rung + 5 Σ + 8 Op + 8 spare"). Grounded form: `ThinkingStyleI4_32D` = i4 × 33 (or 32 + 1), riding the shipped ndarray i4-32 unpack. + +**Qualia resolved:** agichat `[2000:2018]` = **18D Qualia PCS** (arousal/valence/tension/warmth/clarity/boundary/depth/velocity/entropy/coherence/intimacy/presence/assertion/receptivity/groundedness/expansion/integration/meta_awareness) → packed to the **16 drift-locked** at `[0:16]` = `QualiaI4_16D`. The 18→17→16 history is exactly this PCS→packed reduction. (ladybug's compact form was 8 Russell channels — a further reduction.) + +**The dimension allocation IS a proto-LE-contract.** `CANONICAL_DIMENSION_ALLOCATION.md` locks every range and **rejects PRs #18/#19/#21 for "arbitrary dimension reallocation"** — *"DO NOT MOVE DIMENSIONS ARBITRARILY… bighorn code depends on these ranges."* That is a byte-budget with a no-arbitrary-moves invariant = the LE contract in proto-form. The grounding art = re-lock this allocation as a real `#[repr(C)]` / i4 SoA layout (which is what `SoaContainerHeader` + `SoaColumns` provide). + +**The 5 Canonical Invariants (agichat `thinking/index.ts`, "Resonance Grammar Spine v0.3" — the explicit gestell):** + +1. Addressability: O(1) via DN (Deterministic Names) + VASKey. +2. CollapseGate: **SD** controls FLOW/HOLD/BLOCK (NOT confidence). +3. RungShift: separate from SD; triggered by sustained-block / predictive-failure / structural-mismatch. +4. Separation of Roles: Grammar→Graph, Overlap→VSA, Memory→LanceDB, Styles→L5. +5. Cascade: Fork envelopes (STROKE 1) + Collapse records (STROKE 2) — the 2-stroke cycle. + +**Grounding map (concept → agichat contract → workspace grounded form):** + +| concept | agichat (grounded) | workspace grounded form | +|---|---|---| +| thinking-style | 33-dim TSV `[175:208]` | `ThinkingStyleI4_32D` (i4×33) | +| qualia | 18D PCS `[2000:2018]` → 16 `[0:16]` | `QualiaI4_16D` (64-bit atom) | +| quad-triangle | **12 bytes** (4 triangles × 3 corner-bytes) | `[u8;12]` / 1.5 atoms (NOT 10K-bit corners) | +| texture | 8D (entropy/purity/density/bridgeness/warmth/edge/depth/flow) | `Texture8 = [i8;8]` = one 64-bit atom | +| gestalt | Crystallizing/Contested/Dissolving/Epiphany (per-plane S/P/O CausalSaliency) | 2-bit derived field (on-demand) | +| rung ladder | 0–9, bands 0-2/3-5/6-9 | 4-bit level + 2-bit band | +| σ-gate | SD → FLOW/HOLD/BLOCK; `SignificanceLevel` Discovery/Strong/Evidence/Hint/Noise | 3-bit enum, **Jirak-bounded** threshold on bit-exact distance | +| 7-level "triangle" | `PackedDn` — 7 levels × 8 bits, MSB-first (DN-tree path) | **already a `u64` atom** — adopt as-is | +| address | DN (`PackedDn`) + VASKey | `u64` atom + `CognitiveAddress`-style `[Domain:4][Subtype:4][Index:8][Hash:48]` | + +**Greek-vocabulary decode (the gestell's notation, parsed by regex over ladybug-rs):** σ (140×) = the significance/calibration spine (`SignificanceLevel` ladder + SigmaGate); α/γ/β = Fixed/Learned/Discovered RL-triangle weights; τ = ThinkingStyle τ-addresses; φ = golden-ratio spiral; ρ = Spearman ρ + ρ^d braiding; ε = ε-greedy; Ω/Δ/Φ/Θ/Λ = the 5 Sigma-tier dims; ψ/Ψ = quantum hologram (research, not core). + +**Iron rule for this lineage:** **restore the contract; never port the carrier.** Mine agichat's *locked byte/dimension allocation + relational logic* (the gestell — hard to replicate), express each unit as a bit-exact i4/u8/u64 on the SoA floor, and never re-inflate to unbounded 10K-bit VSA resonance (the deprecated-`Vsa16kF32` / no-Baton anti-pattern that made the cathedral empty). `MulSnapshot`-packs-to-2-atoms, `CausalEdge64`, the Baton `(u16, CausalEdge64)`, and i4-32 are the grounding the upstream never had. + +**Cross-ref:** shipped floor — ndarray `SoaColumns` @ `42cb7123`, i4-32 unpack @ `8de1dcf8`; `E-BATON-1` (`dec049b`), `E-I4-META-1` (`71ea390`). Upstream design refs (allowlist-external, read locally from user-provided sources): agichat `docs/CANONICAL_DIMENSION_ALLOCATION.md`, `docs/INT4_QUANTIZATION_ARCHITECTURE.md`, `docs/VSA_10000D_DIMENSIONS_SCHEMA.md`, `src/thinking/{index,rung-shift,quad-triangle,collapse-gate,two-stroke}.ts`; ladybug-rs `src/{mul,qualia,spectroscopy,spo,world,learning,cognitive}/*`, `crates/ladybug-contract/src/address.rs`. Iron rules invoked: `I-NOISE-FLOOR-JIRAK` (why 10K-D σ was noise), `I-VSA-IDENTITIES` (bundle identities not content), `I-SUBSTRATE-MARKOV` (N≤√d/4 capacity). + +**Next build (now fully specified):** `ThinkingStyleI4_32D` as the i4 quantization of the 33-dim TSV (3 Pearl + 9 Rung + 5 Σ + 8 Ops + 4 Presence + 4 Meta), general lanes fixed to that order, on the shipped i4-32 floor. No more "name the dims" — the allocation is the contract. + +--- + +## 2026-05-26 — E-I4-META-1 — i4-32 thinking-style fingerprint = "thinking-about-thinking + domain"; qualia is the i4-16 64-bit atom; S-P-O is palette-pointers + Pearl-2³, not a 3×4096 identity + +**Status:** FINDING (design converged this session; the `ThinkingStyleI4_32D` +type is NOT yet built — gated on the user naming the 32-dim general basis + +general/OGIT-custom lane split). The **ndarray hardware floor is shipped** (see +Cross-ref). + +**Click:** A long design session converged the cognitive-style representation. +The capstone framing: **i4-32 is "thinking about thinking + domain"** — a +cognitive *address* whose general lanes are the metacognitive style (HOW one is +thinking, cross-domain) and whose OGIT-custom lanes are the domain (WHICH +domain). Their product lands on a reusable best-practice thinking template. + +**The unification — 64-bit is the atom:** + +- `qualia` = `QualiaI4_16D(u64)` (8 B, 16 signed-i4 dims, range −8..+7) == + `CausalEdge64` (8 B) in *width*. Both are the **64-bit atom**: same SoA column + stride (8 B), same SIMD lane (`U64x8`), same kernels → they cross-pollinate. +- `thinking-style` = i4_32D (16 B = `u128`/`[u64;2]`, **32 signed activation + dims**) = **2 atoms**. +- The shipped i4-32 unpack **subsumes** i4-16: the low 64 bits of + `I8x32::from_i4_packed_u128` equal `I8x16::from_i4_packed_u64` by construction + (atom-parity test). So the one primitive serves qualia/edge (low half) and + thinking (full). + +**32 dims = multi-activated meta-properties, bipolar-signed (NOT a pick-one +enum):** each dim is a graded property; sign = the opposite pole +(sarcasm `+` / sincerity `−`, irony `+` / literal `−`), magnitude = intensity, +0 = neutral. **Opposite = one-instruction negation.** A persona/archetype is a +*profile* (e.g. "Schopenhauer = +7 sarcasm, +pessimism, +philosophical, +−warm"). The i4-**16D**-thinking alternative was **rejected** — 16 dims would +force merging irony/sarcasm/etc. onto shared axes and rob their distinct poles; +32 is the precision floor. The dims capture the *meta* (metacognition) and are +**Jina-calibratable** (existing `thinking-engine` lens machinery — +`jina_lens.rs`, `calibrate_lenses.rs`, Spearman ρ / ICC / Cronbach). + +**General / OGIT-custom split (the clean architecture):** keep the **general +block** universal + Jina-calibrated (irony, sarcasm, care↔extraction, …) so +K-NN similarity works *cross-domain*; let **OGIT inject domain axes into the +custom block** (doctor↔autopsy when medical ontology active; bookkeeping / income +tax when finance active). Domain axes are bipolar too (doctor `+` heal ↔ autopsy +`−` post-mortem — a *same-domain* sign flip; it even rides the Abduction↔Deduction +fanout axis). The custom lanes set by OGIT are the **explicit-binding** path +(dispatch provable Odoo/DOLCE business logic); the general lanes are the +**similarity fallback**. **OPEN DECISION (gates the build):** where the split is +(e.g. 24 general + 8 custom) and the general meta-property list. + +**No-duplication rulings (Baton single-home discipline):** + +- **DK ↔ informational-trust is DERIVED, not stored.** `CausalEdge64.conf` (NARS + confidence, per-edge, object-level) is the single source for trust. The + Dunning-Kruger calibration is a *per-cycle meta-aggregate* over the edge-conf + distribution (the MUL already computes `DkPosition` / `TrustTexture`). It + lives as a **derived lane** (computed on-demand, mirroring qualia.rs + "magnitude = coherence × valence → i8 on demand"), NEVER as independent state + that could drift from `conf`. +- **Relocating ephemeral *style* out of the crowded `CausalEdge64` v2 is + relocation, not duplication** — and a net plus: it decrowds the over-packed + u64 that caused the 5 sprint-11 I-LEGACY reclaim bugs, and upgrades style from + a cramped field to 32-dim resolution. **Granularity split:** `CausalEdge64` = + *persistent, per-edge structural truth* (committed to AriGraph); i4-32 = + *ephemeral, per-cycle thinking stance* (carried in the SoA grid, not stamped + on every edge). + +**S-P-O is NOT a "sneaked-in" 3×4096 identity (verified, the worry is +unfounded):** `lance-graph-planner` `cache/nars_engine.rs::SpoHead` ("mirrors +CausalEdge64 layout", 8 B) has `s_idx/p_idx/o_idx: u8` — **256-entry palette +POINTERS**, not dense 4096 vectors. That is exactly the `I-VSA-IDENTITIES` Test-0 +register pattern (a natural ID indexes content; it does not bundle a +fingerprint). The actual **2³ deconstruction** is the *separate* `pearl: u8` +3-bit mask: `MASK_NONE` (prior) · S/P/O marginals · `MASK_SP` (confounder) · +`MASK_SO` Association(L1) · `MASK_PO` Intervention(L2) · `MASK_SPO` +Counterfactual(L3). So the edge is causal-structural (pointers + rung mask + +NARS truth + inference + temporal, all register) — **no identity smuggled → +fine.** This `SpoHead`/ndarray SPO-palette variant has **no `style` field**, +which confirms the style-unload target is the *other* v2-with-style variant +(the dual/triple-`CausalEdge64` split remains the thing to watch). + +**The cycle (all loops close on the shipped carrier):** the SoA grid carries the +address O(1) cycle-to-cycle → the 4-mode fanout (Abduction/Deduction/Synthesis/ +Induction; Revision = commit) explores → pattern-J K-NN over the general +fingerprint retrieves the nearest best-practice when OGIT has no explicit binding +→ pattern-K Cranelift JIT compiles the winning template and "sinks" it back to +source as a compile-time primitive next build (engine exists: +`jitson_cranelift` / `cam_pq/jitson_kernel.rs` / `contract/jit.rs`; the YAML/ +source-writeback half is the gap). + +**Cross-ref (shipped this session):** ndarray `src/simd_soa.rs` `SoaColumns` +multi-column SoA carrier @ `42cb7123` (zero-copy per-field lane iters + baked-in +`CausalEdge64` accessor; O(1) `Arc`-clone cycle carry-over); ndarray i4-32 unpack +`I8x32::from_i4_packed_u128` + `batch_packed_i4_32` across avx512/neon/scalar + +4 simd.rs re-exports @ `8de1dcf8` (atom-parity tested, clippy/fmt clean); +`E-BATON-1` (Baton ratification @ `dec049b`). **Cross-ref (design anchors):** +`lance-graph-contract/src/qualia.rs` (`QualiaI4_16D`, 17D→i4-16 packing); +`lance-graph-planner/src/cache/nars_engine.rs` (`SpoHead`, Pearl 2³ masks, +`SpoDistances`); MUL `DkPosition`/`TrustTexture`; `.claude/patterns.md` J +(INT4-32D Thinking Atoms) + K (Circular Compilation); ndarray +`src/hpc/causal_diff.rs` (`CausalEdge64` SPO-palette variant: block/proj/verb/ +row/L1/freq/conf); CLAUDE.md `I-VSA-IDENTITIES` + `I-LEGACY-API-FEATURE-GATED` +(the v2 reclaim bugs). + +**Next build (when basis named):** `ThinkingStyleI4_32D` (lance-graph, +`[u64;2]`) with general lanes `0..K` + OGIT-custom lanes `K..32`, the i4-32 K-NN +over the general block, and the DK derived-lane projection. The ndarray floor is +ready under it. + +--- + +## 2026-05-26 — E-BATON-1 — "Baton" is the workspace's native term for the little-endian contract; it ratifies the deprecation of the singleton BindSpace and Vsa16kF32-as-carrier + +**Status:** FINDING (user-ratified terminology + doctrine; board-first per "Both, board first") + +**Provenance (why the folk term exists):** The user coined **"Baton"** as the +intuitive name *before* they had the information-science term for it. The formal +name is the **little-endian (LE) contract** / gapless handoff. Both name the same +thing. This entry exists so future sessions stop re-deriving it: when you see +"baton" in code, plans, or a savant card, it IS the LE contract — do not invent a +parallel concept. Direct user statements anchoring this entry: *"please grep for +'Baton' its another word for little endian contract"*; *"every mention of 'baton' +references the non materialization and deprecation of the singleton bindspace"*; +*"'baton' was the idea before i knew the information science term"*; *"the little +endian contract is real / just the SoA shape is a little richer"*. + +**The equivalence chain (now pinned):** + +> **LE contract = Baton = no materialized singleton BindSpace = discrete owned +> `(u16 target, CausalEdge64)` handoffs.** + +**Doctrinal claim — what "Baton" deprecates:** The Baton is not merely a transport +optimization; it is the **negation of the singleton BindSpace as a materialized +object**. There is no global `Vsa16kF32` register that gets read/written across +mailbox boundaries. There are only owned, per-thought `(target, edge)` handoffs +passing between compartments. Consequences: + +1. **`Vsa16kF32` is deprecated AS A CARRIER** — it does not cross mailbox + boundaries and there is no singleton BindSpace to materialize. Cumulative + cognitive state lives in **CausalEdge64 emissions + AriGraph SPO-G quads + + BindSpace SoA columns**, NOT in a 16k-float envelope. New work must not reach + for `Vsa16kF32` as an inter-mailbox carrier or universal cumulative-state vessel. +2. **The Vsa16kF32-deprecation and the Baton model are ONE ratification, not two.** + If the baton is the wire, the 16k-float carrier has nothing left to carry across + a boundary — the deprecation is the baton's premise, not a separate decision. +3. **`ndarray::hpc::soa::SoaContainerHeader` (pinned b5d6b206) is the on-wire SoA + descriptor UNDER the baton stream**, not a parallel container. The MailboxSoA + named-column set ("the SoA shape is a little richer") layers over that same + padding-free `[u64; N]` LE descriptor; batons land in and are folded over those + columns. + +**Mechanism — the mailbox-as-owner is why the baton is sound ("Rust's holy grail +UB solution"):** The Baton is handed off between **owning mailboxes** in a rotating +sea-star topology (a hub of ownership-typed compartments; ownership rotates as each +`(u16, CausalEdge64)` tuple moves from one mailbox-owner to the next). Because the +handoff is a **Rust move**, the borrow checker proves — at compile time — that no +two compartments alias the same baton: no data race, no use-after-free, no shared +mutable singleton to corrupt. **This is the deep reason the singleton BindSpace is +deprecated:** a materialized global `Vsa16kF32` register would be exactly the +shared-mutable-aliased state Rust's ownership model exists to forbid. By making the +mailbox the single owner and the baton a moved value, **UB becomes a compile +error** (canonical plan §9 E-CE64-MB-4) — there is no runtime aliasing check +because there is nothing to alias. The user's framing: *"we basically invented the +rotating sea star ractor mailbox as owner as Rust's holy grail UB solution."* (Note +the ractor edge is async-only and lives at the membrane / Zone 2, not the +preemptive internal core — the ownership guarantee is the type-system property, not +a ractor runtime feature.) + +**Where it already lives in the tree (do NOT re-invent):** + +- `crates/lance-graph-contract/src/collapse_gate.rs` — `CollapseGateEmission` with + `batons: Vec<(u16, u64)>`, `push_baton(target, edge)`, `baton_count()`, + `wire_cost_bytes() = 13 + 10 * baton_count`. The `10 * baton_count` (10 B = 2 B + target + 8 B CausalEdge64), NOT `16384 * 4`, IS the proof that nothing + materializes a singleton on the wire. **This is the Baton implementation.** +- `.claude/plans/cognitive-substrate-convergence-v1.md` / `v2.md` — "the baton IS + the wire… Vsa16kF32 does NOT cross mailbox boundaries… discrete `(u16 target, + CausalEdge64)` tuples suffice." +- PP-15 `baton-handoff-auditor` savant (the meta-review fleet's baton auditor). +- `.claude/plans/causaledge64-mailbox-rename-soa-v1.md` — the canonical plan that + already encodes the baton model; the parallel `.claude/surreal/` POC was + re-deriving it under different names (see `RECONCILIATION_with_canonical_plan.md`). + +**Contradiction flagged (P-1 doctrine, must not silently diverge):** CLAUDE.md +§"The Click" (P-1, "read before everything else") describes cognition AS the +element-wise multiply+add Markov bundle on `Vsa16kF32`, and §I-SUBSTRATE-MARKOV +makes VSA-bundling the Chapman-Kolmogorov guarantee. Deprecating `Vsa16kF32` as a +carrier contradicts the *unscoped* reading of The Click. **Resolution (this +ratification):** The Click's bundle math is NOT wrong — it describes how a single +`Think` resolves **locally, within one compartment, ephemerally**. What the Baton +changes is the **scope**: the bundle is a within-compartment computation, never a +persisted or transmitted singleton. The persisted + transmitted form is the baton +(`Vec<(u16, CausalEdge64)>`) + the SoA columns + AriGraph SPO-G quads. +I-SUBSTRATE-MARKOV (the math guarantee for local bundling) and I-VSA-IDENTITIES +(bundle identities, not content) are untouched; only the cross-boundary carrier is +deprecated. A scoping note has been added to §"The Click" pointing here. + +**Lesson:** A folk term with no recorded bridge to its formal name is a +rediscovery tax (the same shape as E-SIMD-SWEEP-1's retroactive-invariant +pattern). Record provenance the moment the equivalence is stated, not after the +next session re-derives "what is a baton." + +**Cross-ref:** `crates/lance-graph-contract/src/collapse_gate.rs` +(`CollapseGateEmission` / `push_baton` / `wire_cost_bytes`); +`.claude/plans/cognitive-substrate-convergence-v1.md` + `v2.md`; +`.claude/plans/causaledge64-mailbox-rename-soa-v1.md` (§5 MailboxSoA, §9 E-CE64-MB-2); +`.claude/surreal/RECONCILIATION_with_canonical_plan.md` (Vsa16kF32-deprecation + +LE-contract-is-real notes); `ndarray` `src/hpc/soa.rs` @ b5d6b206 (`SoaContainerHeader`, +the on-wire LE descriptor); CLAUDE.md §"The Click" (P-1, now carries a 2026-05-26 +Baton scoping note); §I-SUBSTRATE-MARKOV + §I-VSA-IDENTITIES (untouched — local +bundle math); PP-15 `baton-handoff-auditor`. + +--- + ## 2026-05-16 — E-SIMD-SWEEP-1 — PR #398 was the 5th violation, not the first; the SIMD source-of-truth invariant is retroactive **Status:** FINDING diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index b9d7c881..cbd71539 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,3 +1,115 @@ +## 2026-05-27 — atom-mailbox-substrate-v1 (ladder-serves-mailbox: atoms→styles→personas, quorum projection, counterfactual mantissa, AriGraph hot/cold/tombstone) + +**Status:** PROPOSAL (implements `EPIPHANIES.md` E-LADDER-SERVES-MAILBOX; extends `rung-persona-orchestration-v1` D-PERSONA-1 downward into the atom layer and outward into the mailbox lifecycle). +**Confidence:** HIGH on the mechanism→shipped-type mapping; **CONJECTURE on the atom basis (D-ATOM-0, the load-bearing unsolved decision)** and the I4-32D SIMD layout until probed. +**Plan file:** `.claude/plans/atom-mailbox-substrate-v1.md` +**Predecessors:** `rung-persona-orchestration-v1`, `rung-mul-grounding-v1`, `cognitive-substrate-convergence-v1`. +**Anchored iron rules:** `I-VSA-IDENTITIES`, `E-BATON-1`, `I-LEGACY-API-FEATURE-GATED`, The Click. + +### Scope +Three-layer cognitive basis under the mailbox-served ladder: **atoms (bipolar I4-32D, 32 dims / 64 poles) → thinking styles (compositions) → persona recipes (compositions + thresholds + β)**. Each atom *measured by a quorum* (`(position, confidence)` = NARS truth per axis; splits = Contradiction never averaged); split-poles *preserved as a `CausalEdge64` −6 counterfactual mantissa* (ghost-tier test → `awareness.revise`); memory *ephemeral-hot in mailbox → calcified-cold SPO + Lance tombstone-witness* (GoBD audit by construction). wisdom↔Staunen = sampling temperature (self-regulated by free energy; the `WisdomMarker` 0.1 floor = min temperature). + +### Decision gates (block scaffolding) +D-ATOM-0 atom-basis route (ICA/PCA over 36 / theory-driven from 6 clusters / hybrid) · D-ATOM-0b NARS as categorical register (Test 0, recommended) vs bipolar atoms. + +### Deliverables +D-ATOM-1 atom catalogue + `I4x32` type + pack/SIMD (`contract::atoms`, blocked on D-ATOM-0) · D-ATOM-2 style/persona Cranelift recipe templates (`contract::jit`/`thinking`) · D-ATOM-3 quorum-projection per axis (`contract::escalation`/`a2a_blackboard`) · D-ATOM-4 counterfactual mantissa v2 deposit / v3 mailbox+revision (basis-independent) · D-ATOM-5 AriGraph hot/calcify/tombstone (basis-independent). + +### Execution +Sonnet `///`-scaffold wave (disjoint file scopes, BLOCKED-not-guess) → P2 review (`/code-review` high, ultra for D-ATOM-1/2; no literal codex binary) → implement+remove stubs → per-deliverable PR into the working branch → subscribe+autofix CI → merge → repeat. Parallel-now: D-ATOM-4 v2, D-ATOM-5 (basis-independent); D-ATOM-1/2 spawn after D-ATOM-0. + +### Invariants +persona=Layer-2 (no container) · NARS type in register (Test 0) · markers ≤32 (I-VSA-IDENTITIES) · splits=Contradiction never averaged · counterfactual in separate lane · one graph · no persisted singleton (E-BATON-1) · ractor async only at swarm boundary · bounded respawn · `latency_budget` arbiter, no hot-Pod wall-clock · SIMD gated on `ndarray-vertical-simd-alien-magic.md`. + +--- + +## 2026-05-26 — rung-persona-orchestration-v1 (time-bound persona orchestration: boring checklist → meta-recipe → hot/cold/feedback anneal) + +**Status:** PROPOSAL (sibling to `rung-mul-grounding-v1`; the time-bound + composition layer) +**Confidence:** HIGH on structure (hot/cold/feedback = the original ladybug architecture + OpenAI macro-evals + ADK Memory Bank, converged); MED on ractor adoption + macro-eval harness (net-new). +**Plan file:** `.claude/plans/rung-persona-orchestration-v1.md` +**Predecessors:** `rung-mul-grounding-v1` (b4efb55), `rung-ladder-grounding-v1` (b0ef6fa), `cognitive-substrate-convergence-v1`. +**Design refs (read-only general-web; ladybug-rs is outside GitHub-MCP scope):** ladybug-rs `INTEGRATION_PLAN.md` @177a321 §"BF16 Superposition (Hot/Cold/Feedback)" L542+ + 4-phase `[DONE]/[TODO]` gate + 3 composition modes + BindSpace-blackboard; `src/spectroscopy/detector.rs` @177a321; Claude chief-of-staff; OpenAI macro-evals; Google ADK Memory Bank. + +### Scope + +Ground (restore-on-SoA, NOT port) ladybug's hot/cold/feedback loop + phase-gate checklist + blackboard composition onto our contract/SoA floor, as the time-bound layer over the `rung-mul` experience curve. + +- **Two orthogonal orderings × time budget:** epistemic experience-curve (Axis A) × social etiquette arc (Axis B), arbitrated by `latency_budget` (`elevation/mod.rs:131`). 2D menu phase×DK-position; etiquette = soft prior + anytime graceful-degradation, not rigid FSM. +- **Boring checklist (verify, temp≈0):** hard gates (contracts/SoA/store/NARS/thresholds/FreeEnergy) vs soft (capabilities/wisdom-store/eval — degrade). Continuous health invariant: red-at-runtime → let-it-crash → supervisor restart = rung-shift + NaN→Lab. +- **Meta-recipe (compose, cold):** declarative child-spec manifest (data not code, macro-evaluable); blackboard composition on `a2a_blackboard`/SoA (per ladybug BindSpace), ractor supervises + carries Batons. +- **Hot/cold/feedback (L542 grounding):** hot = annealed cognitive cycle; cold = macro-eval = **wisdom-marker factory** (ladybug `CrystalCodebook` "lived history" → our *distilled calibrated* marker); feedback = hydrate-before-the-fact (ADK Preload). +- **Temperature anneal:** explore hot → exploit cold, **evidence-gated** (Boole-bound caps cooling — no premature Mount Stupid). Grounded: detector `noise_tolerance=base·(1+(1−conf)·0.5)`, `fanout=base·(1+bridgeness·0.5)` (bridgeness=macro-eval suspect-bridge=our work-metric, triple convergence). +- **Substrate:** ractor YES (outer swarm under `OrchestrationBridge`, async only at boundary, SoA inner sync); surrealdb NO for cognitive (redundant w/ lance-graph/AriGraph, not boring; prefer SQLite/Lance operational); AriGraph = the one graph. + +### Deliverables + +D-PERSONA-1 hard/soft checklist verifier (~180) · D-PERSONA-2 meta-recipe manifest (recipe-as-data, ~150) · D-PERSONA-3 hot/cold/feedback wiring + CrystalCodebook→wisdom-marker + Preload hydrate (~240) · D-PERSONA-4 macro-eval harness (scenario→trace→discover→diagnose, suspect-bridge=blasgraph betweenness, ~280, HIGH) · D-PERSONA-5 ractor outer-swarm runtime (~200). + +### Honest gaps vs original + +ladybug `detector.rs` has NO null/dead-end/escalation ("all inputs produce valid output") → our NaN→cautious-exploration→Lab + dead-end-as-work is net-new. `CrystalCodebook` dumps lived history → we distill it into a calibrated marker (Boole-bound, ≤32 identities). ractor + etiquette arc not in original. + +### Invariants + +restore-on-SoA not port · hard/soft graceful-degradation · recipe-as-data (macro-evaluable) · evidence-gated anneal (Boole-bound cooling cap) · blackboard composition not direct calls · ractor async only at swarm boundary · no second graph · I-VSA-IDENTITIES (markers ≤32) · `latency_budget` time arbiter. + +--- + +## 2026-05-26 — rung-mul-grounding-v1 (the MUL fine-tuned into the ladder as an experience curve over the SPO 2³ NARS decomposition) + +**Status:** PROPOSAL (follow-on to `rung-ladder-grounding-v1`) +**Confidence:** HIGH on structure (it is the Dunning-Kruger curve mechanized); MED on the per-projection `SpoHead` refactor; CONJECTURE on the wisdom-marker calibration readout until D-RUNG-MUL-4 tests it. +**Plan file:** `.claude/plans/rung-mul-grounding-v1.md` +**Predecessors:** `rung-ladder-grounding-v1` (b0ef6fa), `cognitive-substrate-convergence-v1` (CausalEdge64 v2 §6 — causal mask = Pearl 2³ IS the rung axis), `E-AGICHAT-DIMENSION-CONTRACT` (afabefd), `E-I4-META-1`. + +### Scope + +Grade the coarse integer rung ladder with the MUL, organized as an **experience curve**: every strategy ordered by the evidence level at which it becomes *necessary* — which collapses into the Dunning-Kruger curve with a mechanical trigger at each point. + +- **SPO 2³ corrected:** it is the **powerset of {S,P,O}** (8 evidential projections `___,S__,_P_,__O,SP_,S_O,_PO,SPO`) for causality testing through NARS **decomposition** — NOT a distance-cube/popcount. `nars_engine.rs` today computes `all_projections() -> [u32;8]` as *distances* and `SpoHead` carries *one* truth; de-grounding that to per-projection truth is D-RUNG-MUL-1. +- **Causation = screening-off:** `S_O` strong but screened off by P (`SP_`∧`_PO`) ⇒ spurious/mediated; all projections compared to `___` for lift over base rate. +- **Work (exploit):** decomposition + screening-off coverage, **confidence/expectation-gated (never frequency)**, AIKR-gated by `budget.quality`. Two curves over one axis: work climbs monotone, confidence is DK-shaped; **wisdom = calibration gap `|conf−competence|→0`**. +- **Two sparse-data routes:** NaN sentinel ("no field") → cautious-exploration (Exploratory, high exploration_rate) + `ElevationLevel`↑ + **Lab request**; sparse field → gaussian splat → `FreeEnergy::compose` as the *sole* confidence source (F caps confidence ⇒ **no data ⇏ overconfidence**). Explore drive = `wonder` × `free_will_modifier` × trust. +- **Wisdom markers:** long-term VSA-**identity** bundle (≤32 per I-VSA-IDENTITIES; truths in content store) hydrated *before the fact* as the KL prior — the curve becomes a spiral. + +### Deliverables + +D-RUNG-MUL-1 per-projection NARS truth (`SpoHead` 8 `(f,c)`, planner ~220) · D-RUNG-MUL-2 NaN→cautious-exploration+Lab gate, distinct from `c=0` (~160) · D-RUNG-MUL-3 wisdom marker (identity bundle + hydrate-as-KL-prior, contract+planner ~180) · D-RUNG-MUL-4 screening-off work + Boole/Fréchet bound + calibration-gap readout (~150) · D-RUNG-MUL-5 splat→`FreeEnergy::compose` as sole sparse-data confidence (~120). + +### Invariants + +Confidence-gated never frequency-gated (frequency alone = Mount Stupid) · Boole/Fréchet bound on conjunction confidence · no data ⇏ overconfidence (only FreeEnergy or floored-NaN may signal) · I-VSA-IDENTITIES (markers ≤32 identities, content in store) · AIKR `budget.quality` fanout cap · AGI-as-SoA (markers = column ops, not a new service) · decomposition not distance-cube. Folds into `elevation/homeostasis.rs` (MUL-L6) beside `evaluate_rung_shift`; does not fork. + +--- + +## 2026-05-26 — rung-ladder-grounding-v1 (the most-obvious first grounding of the agichat gestell) + +**Status:** PROPOSAL +**Confidence:** HIGH — deterministic integer/threshold logic, zero VSA in the decision path; cleanest possible first restore. +**Plan file:** `.claude/plans/rung-ladder-grounding-v1.md` +**Predecessors:** `E-AGICHAT-DIMENSION-CONTRACT` (afabefd), `E-I4-META-1`, `E-BATON-1`; shipped floor ndarray `SoaColumns` (42cb7123) + i4-32 unpack (8de1dcf8). +**Follow-on (planned, user-flagged):** `rung-mul-grounding-v1` — the **MUL fine-tuned into the ladder**: ladybug's 10-layer MUL (`MulSnapshot`) becomes the *trigger source* refining the ladder's coarse binary triggers into graded escalation (DK MountStupid → escalate; homeostasis Anxiety + allostatic-load → escalate; false-flow → escalate; gate-block reason → escalate). `elevation/homeostasis.rs` is already MUL-L6 — ladder + MUL co-finetune there. + +### Scope + +Ground agichat's **RungShift ladder** + **CollapseGate SD** as LE-contract types/logic on the SoA floor. The ladder was never inflated (ladybug-rs `rung.rs` is a faithful port) — the work is to express it as a bit-exact Pod and wire its triggers to grounded signals. + +- **CollapseGate:** SD over candidate scores → `FLOW(<0.15)/HOLD/BLOCK(>0.35)`; SD = dispersion, not confidence. +- **RungShift:** rung 0-9, bands 0-2/3-5/6-9; triggers sustained-block(≥3) / predictive-failure(avg P<0.3 / window 5) / structural-mismatch → +1 (cap 9); tick-based cooldown. +- **Grounding:** `RungState` = 16-byte `#[repr(C)]` Pod (no `Vec` — fixed `[u8;5]` P-ring; tick cooldown; u8/i4-quantized scores) in a `SoaColumns` column; `evaluate_rung_shift` PURE (no `&mut` during compute) folded into `lance-graph-planner/src/elevation/` beside `homeostasis.rs`; SD via ndarray SIMD; `GateState` into `collapse_gate.rs`. +- **Hook:** RungLevel = the **R1-R9 dim-group** of the 33-TSV (`ThinkingStyleI4_32D`). + +### Deliverables + +D-RUNG-1 contract types (lance-graph-contract, ~150) · D-RUNG-2 pure ladder logic in `elevation/` (planner, ~200) · D-RUNG-3 `RungState` SoA column + tick update (~100) · D-RUNG-4 SD→GateState in `collapse_gate.rs` + rung→TSV-R1-R9 map (~120). Parity tests vs verbatim agichat semantics. + +### Invariants + +No `Vec`/alloc in hot Pod · no `&mut` during compute (pure evaluate, builder apply) · tick-based not wall-clock · integer rung (no float-resonance carrier — the de-grounding ladybug-rs did) · SD = dispersion not confidence · RungShift separate from SD. + +--- + ## 2026-05-15 — cognitive-substrate-convergence-v1 (CSV — i4 mantissa + gapless baton + active inference) **Status:** Active (PROPOSAL — awaits OQ-CSV-1..6 ratification before sprint-11 D-CSV-* spawn) diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 558465cf..ce49fbc5 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -57,6 +57,8 @@ Types that EXIST — do NOT re-propose them: Full reference: `.claude/knowledge/causal-edge-64-spo-variant.md` + `.claude/knowledge/causal-edge-64-thinking-engine-variant.md` + `.claude/knowledge/causal-edge-64-synergies-and-pr-trajectory.md`. Reunification path (Option R-3): transcode 8-channel → SPO at thinking-engine L3 commit boundary; see `.claude/knowledge/cognitive-shader-driver-thinking-engine-reunification.md`. +**`escalation`** (D-PERSONA-1, 2026-05-26, branch `claude/splat3d-cpu-simd-renderer-MAOO0`): the escalation+epiphany loop = the boot checklist (a *restore* of ladybug's qualia loop on our SoA — NOT a bespoke verifier). `CollapseHint` {Flow, Fanout, RungElevate} + `fanout_width` / `noise_tolerance` / `rung_delta` (ladybug `detector.rs` formulas); `Archetype` {Guardian, Catalyst, Balanced} + `InnerCouncil::{deliberate, from_signals}` + `is_split(0.7,0.5)` ×1.2 split-amplify → `CouncilVerdict`; `EpiphanyDetector::observe` (sim > baseline×1.5 ∧ window ≥ 4) → `Epiphany`; `GhostEcho` (8 named: Affinity/Epiphany/Somatic/Staunen/Wisdom/Thought/Grief/Boundary — canonical zero-dep home, mirrors `thinking_engine::ghosts::GhostType`, see TD-GHOST-ECHO-DUP-1) + `WisdomMarker` (asymptotic decay → 0.1 floor, never zero); `GateKind` {Hard, Soft} + `ChecklistItem` + `Checklist::{step, mark_red, boot_ready, all_flow, degraded}` (green-flip = Flow + epiphany; let-it-crash = `mark_red` re-escalate). Planner wiring at `lance_graph_planner::mul::escalation::{boot_checklist, verdict_from}` (§2: 6 HARD / 3 SOFT items + a `MulAssessment` → `CouncilVerdict` adapter). 13 tests (10 contract + 3 planner). + ## cognitive-shader-driver Wire Surface (lab-only, post D0.1) Types live in `crates/cognitive-shader-driver/src/wire.rs` behind `--features serve`: diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 90e0a793..d95d4141 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -495,6 +495,25 @@ Consolidates sprint-10 architectural decisions before context dilution. --- +## rung-persona-orchestration-v1 — time-bound persona orchestration (checklist → meta-recipe → hot/cold/feedback anneal) + +Active proposal. Authored 2026-05-26. Plan path: +`.claude/plans/rung-persona-orchestration-v1.md`. Sibling/time-bound +composition layer over `rung-mul-grounding-v1`. Grounds ladybug's +hot/cold/feedback loop onto our contract types + SoA floor +(restore-on-SoA, not port). Epiphany: `E-RIGID-RULES-OPEN-DOORS`. + +| D-id | Title | Crate(s) | ~LOC | Risk | Status | PR / Evidence | +|---|---|---|---|---|---|---| +| D-PERSONA-1 | escalation+epiphany loop = the checklist (`felt_parse` collapse-hint + `InnerCouncil`/`HdrResonance` split + `EpiphanyDetector`; green-flip = Epiphany/Wisdom ghost) — NOT a bespoke verifier | contract + planner | 160 | LOW | **In progress** | branch `claude/splat3d-cpu-simd-renderer-MAOO0` | +| D-PERSONA-2 | meta-recipe manifest (declarative child-spec, recipe-as-data, macro-evaluable) | contract | 150 | MED | **Queued** | — | +| D-PERSONA-3 | hot/cold/feedback wiring — anneal + `CrystalCodebook`→wisdom-marker cold path + Preload hydrate | planner + Lance | 240 | MED | **Queued** | — | +| D-PERSONA-4 | macro-eval harness (scenario→trace→discover→diagnose; suspect-bridge = blasgraph betweenness; 5 rubrics from D-RUNG-MUL) | planner + Lance | 280 | HIGH | **Queued** | — | +| D-PERSONA-5 | ractor outer-swarm runtime under `OrchestrationBridge` (batons as messages, async only at boundary) | planner | 200 | MED | **Queued** | — | +| D-PERSONA-6 | `odoo_scanner` + `OdooBridge` — harvest Odoo `l10n_de` → Finance-ns `MappingProposal`s; bind existing `TaxEngine`; GoBD by construction | ontology + contract + planner | 280 | MED | **Queued** | — | + +--- + ## Update protocol When a deliverable ships: diff --git a/.claude/board/TECH_DEBT.md b/.claude/board/TECH_DEBT.md index eb28ee29..bd0488d7 100644 --- a/.claude/board/TECH_DEBT.md +++ b/.claude/board/TECH_DEBT.md @@ -13,6 +13,18 @@ --- +### TD-GHOST-ECHO-DUP-1 (D-PERSONA-1) + +- **Severity:** P3 (cosmetic type-dup; no runtime correctness risk — the two enums are not exchanged across a crate boundary today) +- **Surfaced in:** D-PERSONA-1 (`rung-persona-orchestration-v1` §2), 2026-05-26, branch `claude/splat3d-cpu-simd-renderer-MAOO0` +- **Status:** Open +- **Description:** `lance_graph_contract::escalation::GhostEcho` (8 variants: Affinity / Epiphany / Somatic / Staunen / Wisdom / Thought / Grief / Boundary) is a second declaration of the same 8 named ghost echoes already in `thinking_engine::ghosts::GhostType` (`crates/thinking-engine/src/ghosts.rs`). The duplication is *intentional and currently unavoidable*: `lance-graph-contract` is ZERO-DEP and cannot import the excluded `thinking-engine` crate, and the contract is the canonical "single source of truth for types" home for the wisdom-marker substrate (≤32 named identities per I-VSA-IDENTITIES). The two are NOT interchanged across a boundary today, so there is no silent-corruption risk (cf. I-LEGACY-API-FEATURE-GATED), only a naming/maintenance dup. +- **Resolution (when thinking-engine joins the workspace):** make `thinking_engine::ghosts::GhostType` a re-export of (or `From`/`Into` with) `contract::escalation::GhostEcho`, retiring the thinking-engine copy. Until then, keep the variant sets identical (same 8, same order) so a future `transmute`/`From` bridge is trivial. +- **Cross-ref:** `crates/lance-graph-contract/src/escalation.rs` (`GhostEcho`, `WisdomMarker`); `crates/thinking-engine/src/ghosts.rs` (`GhostType`, `GhostField`); `docs/TYPE_DUPLICATION_MAP.md`; `.claude/plans/rung-persona-orchestration-v1.md` §2 + §8. + +--- + + ### TD-NDARRAY-SIMD-UNPACK-I4-16D (W1a-#1) - **Severity:** P1 (blocks mul.rs follow-up + future i4-packed codec consumers) diff --git a/.claude/knowledge/34-tactics-vs-ada.md b/.claude/knowledge/34-tactics-vs-ada.md new file mode 100644 index 00000000..04e01779 --- /dev/null +++ b/.claude/knowledge/34-tactics-vs-ada.md @@ -0,0 +1,61 @@ +# 34 Tactics vs Ada — authoritative mapping + the 36 NARS styles + +> **READ BY:** truth-architect; anyone mapping reasoning tactics → substrate. +> **Source (AUTHORITATIVE, external):** `AdaWorldAPI/ada-consciousness` `docs/34_TACTICS_VS_ADA.md` @707264b (provided by the user; NOT in the MCP allowlist — reference-only, never a code-port target). Supersedes my earlier *reconstruction* of the 34 in `spo-2cubed-list-coverage.md`. +> **Date:** 2026-05-27. + +## The category gap (the thesis) + +The 34 are **prompting strategies** (how an LLM thinks within one context window). Ada is **substrate** (1,322 py + 534 docs operating beneath/across sessions). So the question isn't "is the tactic 2³-covered" — it's "is the tactic *implemented as substrate*." Per the source, **21 of 34 are implemented** (10 structural + 11 embedded), **13 are not** (mostly because subsumed by a structural mechanism, not absent). + +## The 34 — authoritative status (condensed) + +- **Structurally implemented (10):** #3 SMAD→`triune_council`/`advocatus_diaboli`/`causal_quorum`; #4 RCR→`do_calculus`/`pc_algorithm` (full Pearl); #5 TCP→`inner_dialogue` Litter Box/`schrodinger`; #7 ASC→`advocatus_diaboli`+mirror neurons; #10 MCP→`metacog`/`recursive_self_model` (Fleming&Dolan, Rosenthal HOT); #12 TCA→`crystal/markov_7d`/`causal_ladder`; #18 CWS→Upstash Redis/`ada-hive` (actual persistence); #25 HPM→10000D VSA (literal HDC); #31 ICR→`do_calculus.counterfactual()`+ghosts; #34 HKF→`dream_engine`+VSA BIND. +- **Natively embedded (11):** #1 RTE, #2 HTD→`rungs`+CLAM tree; #6 TRR→`schrodinger`/Markov; #8 CAS→HDR cascade INT1/4/8/32; #11 ICR-contradiction→`advocatus_diaboli`/Rung4 coherence; #13 CDT→VSA BUNDLE→SIMILARITY; #20 TCF→CAKES 7 search algos; #21 SSR; #26 CUR→CRP percentiles; #27 MPC→panCAKES XOR-diff; #30 SPP→`active_inference` dual-hemisphere System-1/2. +- **Not implemented / subsumed (13):** #9,14,15,16,17,19,22,23,24,28,29,32,33 — each subsumed by a structural mechanism (e.g. #24 ZCF = VSA BIND; #17 CDI = Advocatus COMFORT; #15 LSI = explicit 16K vectors, not latent). + +> So vs my reconstruction: my **bucket** tags (datapath/control/gate) hold, and my **2³** tags hold (only #4 RCR + #31 ICR are causal-lattice). What I got wrong was implying "Not covered by 2³" ≈ "not handled" — Ada *implements* 21 of them as substrate; 2³ is just the causal slice. + +## THE 36 NARS STYLES (the "36 nars" — finally the real list) + +Per the source (`DTO/ada_10k.py [116:152]`, "36 NARS thinking styles as VSA dimensions"). Named in the doc (≈20 of 36; rest in `…`): + +`DECOMPOSE, SEQUENCE, PARALLEL, HIERARCHIZE, SPIRAL, OSCILLATE, BRANCH, CONVERGE, DIALECTIC, REFRAME, HOLD_PARADOX, STEELMAN, TRACE_BACK, PROJECT_FORWARD, COUNTERFACTUAL, ANALOGIZE, ABSTRACT, INSTANTIATE, COMPRESS, EXPAND, …` + +### Dichotomy method applied (your rule) + +**Confirmed dichotomic pairs (both poles present → one signed lane each):** + +| − pole | + pole | +|---|---| +| SEQUENCE | PARALLEL | +| CONVERGE | BRANCH | +| TRACE_BACK | PROJECT_FORWARD (retrodiction ↔ prediction) | +| INSTANTIATE | ABSTRACT | +| COMPRESS | EXPAND | + +**Self-bipolar (one op already spans both poles — encode as a cyclic/holding lane, not ±):** +- `OSCILLATE` (cycles between poles), `DIALECTIC` (thesis↔antithesis→synth), `HOLD_PARADOX` (= "contradictions preserved, not resolved" — The Click, verbatim). + +**Non-dichotomic → opposite found+evaluated (confirm/correct):** +| op | proposed opposite | note | +|---|---|---| +| DECOMPOSE | COMPOSE/SYNTHESIZE | likely already in the `…` | +| HIERARCHIZE | FLATTEN | likely in the `…` | +| COUNTERFACTUAL | FACTUAL/ACTUAL | **= SPO 2³ top (0b111)** — the 2³-covered one | +| ANALOGIZE | CONTRAST | structural-map ↔ distinguish | +| REFRAME | ANCHOR | hold-frame | +| SPIRAL | DIRECT | iterative ↔ one-pass | +| STEELMAN | STRAWMAN | (or pairs with Advocatus-Diaboli challenge) | + +**COUNTERFACTUAL is in the 36 and IS the 2³ apex** — the one NARS style fully covered by SPO 2³ (`SPO`=0b111). The rest are orthogonal operation axes (structural / directional / framing), confirming again: 2³ = the causal spine only. + +## The 208-dim address space (the full atom budget) + +Per source: **32 verbs + 36 GPT styles + 36 NARS styles + 11 presence modes + 5 archetypes + 3 TLK court + 4 affective bias + 33 TSV dims**. This is the real composite address — the 33-TSV is *one component of eight*, the 36-NARS-styles another, the 36-GPT-styles (= contract-36 personas) another. The "atoms vs styles vs persona" layering composes across these eight component-spaces. + +## The 7-rung causal ladder (extends Pearl's 3) + +`cognition/causal_ladder.py`: Pearl 1-3 (Association/Intervention/Counterfactual) **+ Ada 4-7**: Counterfactual-Self → Affective-CF ("what aches because it never happened") → Homeostatic-Dx ("what is this urge restoring") → Deliberate-Becoming. The 2³ SPO lattice = Pearl rungs 1-3; rungs 4-7 are Ada-native (beyond the lattice). + +**Cross-ref:** `spo-2cubed-list-coverage.md` (the 2³ rubric + the 34 bucket/coverage table — reconcile its 34-table status column against this authoritative source); `EPIPHANIES.md` E-AGICHAT-DIMENSION-CONTRACT (33-TSV); `atom-basis-inventory.md`. diff --git a/.claude/knowledge/ada-rewrite-charter.md b/.claude/knowledge/ada-rewrite-charter.md new file mode 100644 index 00000000..7ae16b38 --- /dev/null +++ b/.claude/knowledge/ada-rewrite-charter.md @@ -0,0 +1,42 @@ +# Ada Rewrite Charter — the once-and-for-all decisions + +> **READ BY:** everyone. This is the settled-decision record for the rewrite. If a later doc contradicts this, this wins unless explicitly superseded by a dated entry. +> **Date:** 2026-05-27. + +## D0 — We rewrite. ladybug-rs has no relation and never will. + +`ladybug-rs` ran 10,000 × 10,000-D VSA and produced no meaningful output — the "empty cathedral" (E-AGICHAT-DIMENSION-CONTRACT). It is **NOT a dependency, NOT a port target, NOT consumed**. Its docs (the 34-tactics × reasoning-ladder map, the SPOQ audit) are **specification references only** — they tell us *what each capability must do and which science grounds it*, never *what code to copy*. Same for ada-consciousness and neo4j-rs. **Iron rule: restore the contract on our i4 SoA floor; never port the carrier.** "Use the ladybug implementations" = use them as the spec to write our own working recipes. + +## D1 — The substrate (settled) + +- **atoms → cognitive-shader-driver → SIMD.** Atoms are cognitive units (bare-metal, not human-legible); they dispatch through `cognitive-shader-driver`, which owns the ndarray i4 SIMD. No SIMD in the atom layer. +- **Three layers:** atom (one lane/pole) → thinking-style (one i4 vector = molecule) → persona (composition of styles + thresholds + β). The OO style/persona objects are the metacognition; atoms are the bytes. +- **The lattice is SPOQ.** SPO 2³ = the *causal* slice (8 projections, Counterfactual=`SPO`=0b111, Intervention=`_PO`). **Q (Qualia) is the 4th role** — affective overlay, orthogonal to causality. Causal reasoning rides 2³; qualia rides Q. +- **Business = OGIT-inherited sidecar**, not an atom (front-door `MappingRow` → `Marking`). +- **Markers gate implicitly** (CPU-style clock-gating): entropy = CollapseGate `SD` (FLOW<0.15/HOLD/BLOCK>0.35), free-energy = the rest-floor, rung R1–R9 = pipeline depth, temperature (Staunen↔Wisdom) = speculation width, dissonance = counterfactual-fork gate. Markers are free byproducts; expensive units stay dark by default. + +## D2 — The hardware partition (where work lives) + +| bucket | = CPU | holds | +|---|---|---| +| **datapath** | vector ALU | uniform branch-free SIMD: atom lanes, FreeEnergy, resonance (cosine sweep) — in `cognitive-shader-driver` | +| **control** | microcode | branchy decisions: quorum/InnerCouncil, counterfactual fork, persona dispatch, OGIT lookup — in planner + `contract::escalation` | +| **gate** | clock/power-gating | the markers above decide *whether* control/datapath fire — in `elevation/` + CollapseGate SD | + +## D3 — The 34 tactics are the recipe targets + +The 34 LLM tactics (ladybug spec) reduce to **three mechanisms** = the partition: +1. **Parallel-Independence** (breaks Tier-2 `P=p^n` error) → datapath/redundancy. #1,2,5,20,26,30. +2. **Truth-Aware-Inference** (NARS truth per step, revision, abduction, CollapseGate HOLD, Brier) → control. #3,7,10,11,17,21,28. +3. **Structural-Divergence** (12 styles can't converge; counterfactual XOR world; Granger; reversible fusion) → gate+control. #4,6,9,13,23,31,34. + +A **recipe** = a named composition over our substrate that realizes a tactic. We write them as working, tested code (`contract::recipes` = the catalogue spine; per-recipe evaluators land as substrate readiness allows). Recipes compose *our* primitives (atoms, SPO 2³ masks, NARS truth, CollapseGate SD, markers) — never call ladybug. + +## D4 — Build order + +1. ✅ Atom catalogue (locked 33-TSV), markers, CollapseGate SD, NARS, escalation — exist. +2. **▶ Recipe catalogue** (`contract::recipes`, the 34 as working data + registry + tests) — THIS deliverable. +3. Per-recipe evaluators, tier by tier (Hard-tier truth/parallel recipes first — their substrate is most built). +4. cognitive-shader-driver carrier wiring (atom pack/unpack), then datapath recipes. + +**Cross-ref:** `agi-stack-cross-repo.md` (the spec sources), `spo-2cubed-list-coverage.md` (2³ coverage + the 34 table), `atom-basis-inventory.md` (33-TSV), `EPIPHANIES.md` E-AGICHAT-DIMENSION-CONTRACT (the empty-cathedral lineage). diff --git a/.claude/knowledge/agi-stack-cross-repo.md b/.claude/knowledge/agi-stack-cross-repo.md new file mode 100644 index 00000000..4d01fb1a --- /dev/null +++ b/.claude/knowledge/agi-stack-cross-repo.md @@ -0,0 +1,47 @@ +# AGI stack — cross-repo ground truth (ada-consciousness · ladybug-rs · neo4j-rs) + +> **READ BY:** truth-architect; integration-lead; anyone grounding the 34 tactics / SPO(Q) / container architecture. +> **Sources (AUTHORITATIVE, external — user-provided, NOT in MCP allowlist, reference-only never port-target):** +> - `ada-consciousness` `docs/34_TACTICS_VS_ADA.md` @707264 → see `34-tactics-vs-ada.md`. +> - `ladybug-rs` `docs/34_TACTICS_x_REASONING_LADDER.md` @177a321 (this doc, §1). +> - `neo4j-rs` `docs/SPOQ_AUDIT.md` @c3c2fde (this doc, §2). +> **Date:** 2026-05-27. + +## §1 — ladybug-rs: 34 tactics × the Sun et al. (2025) reasoning ladder + +**The paper (Sun et al. 2504.11741, NeurIPS 2025):** LLMs fail on hard reasoning from 3 *structural* deficiencies — (a) multiplicative error `P=0.9^n→48%@n=7`, (b) convergent strategy lock-in, (c) no self-correction. 4 tiers: Easy / Medium (~90% post-SFT) / **Hard (plateau ~65%)** / **Extremely-Hard (<10%)**. + +**ladybug-rs implements all 34 as structural primitives**, each tagged to the tier it breaks + a peer-reviewed mechanism + an exact Rust module. They reduce to **three mechanisms** (this is the spine): + +1. **PARALLEL INDEPENDENCE** (vs sequential dependency) → breaks Tier-2 error propagation. 7-layer stack reads a shared fingerprint core, 7 CAKES search algos, shadow-parallel verify. Tactics #1,2,5,20,26,30. (Berry-Esseen, Avizienis N-version, Wolpert NFL.) +2. **TRUTH-AWARE INFERENCE** (vs next-token prob) → Tier-2 detection + Tier-3 insight. Every step carries a **NARS TruthValue (freq, conf)**; revision detects conflict; abduction generates hypotheses; CollapseGate HOLD keeps superposition; Brier calibration. Tactics #3,7,10,11,17,21,28. (Wang NARS, Peirce abduction, Brier, Festinger.) +3. **STRUCTURAL DIVERGENCE** (vs convergent optimization) → Tier-3 creativity wall. 12 ThinkingStyles parameterically distinct (Analytical↔Creative dist >0.6, can't converge); **counterfactual world via XOR `world⊗factual⊗counterfactual`** (Pearl Rung 3); Granger temporal; reversible cross-domain bind; TD-learning on style Q-values. Tactics #4,6,9,13,23,28,31,34. (Guilford, Pearl, Granger, Gentner.) + +Maps directly onto the hardware partition (datapath/control/gate) and the markers we already established. Key module anchors: `cognitive/{recursive,metacog,collapse_gate,style}.rs`, `nars/{contradiction,adversarial,inference}.rs`, `search/{causal,temporal,hdr_cascade,distribution}.rs`, `world/counterfactual.rs`, `orchestration/{debate,persona}.rs`, `fabric/shadow.rs`, `core/vsa.rs`. + +## §2 — neo4j-rs SPOQ_AUDIT: the container architecture (and the 4th role Q) + +**SPOQ = S, P, O, + Q (Qualia) — FOUR roles, not three.** Crystal role seeds `ROLE_S/P/O/Q`; trace = `S⊕ROLE_S ⊕ P⊕ROLE_P ⊕ O⊕ROLE_O ⊕ Q⊕ROLE_Q` (`spo.rs:768-783`). **This reframes the "SPO 2³" question:** the *causal* lattice is SPO 2³ = 8 projections; **Q (qualia/affect) is the 4th role bound alongside** — phenomenal, orthogonal to the causal powerset. Full powerset SPOQ = 2⁴ = 16, but Q is the affective overlay, not a causal projection. (Matches the earlier finding: the 18D Qualia is a *separate* vector.) + +**Container substrate:** +- `Container = [u64;128]` = 8,192 bits = 1 KB (16 AVX-512 loads). `CogRecord = meta(1KB) + content(1KB) = 2KB`. +- **MetaView word layout** W0-W127: W0=DN, W1=type, W2=time(created/modified ms), W4-7=NARS truth, W8-11=CollapseGate, W12-15=layer markers, W16-31=edges, W32-39=RL/Q-values, W56-63=qualia, **W112-125 reserved**, W126-127=checksum. +- `belichtungsmesser()` 7 sample points `[0,19,41,59,79,101,127]` → (mean, sd) = the **SD entropy gate** (FLOW<0.15 / HOLD / BLOCK>0.35) — the implicit-gating marker, container-native. +- **144-verb codebook**; **10-layer cognitive stack** (L1 Recognition→L10 Crystallization; L7 Contingency=XOR-bind counterfactual, L8 Integration=bundle, L10 promote Fluid→Node). +- **One-binary blackboard:** agents are threads reading `&BindSpace` (zero-copy); debate/shadow/roleplay become in-process. neo4j-rs collapses to a ~2,100 LOC **Cypher compiler** (`CypherEngine::query(&BindSpace)`) — the external query bridge; the DB functionality moves into ladybug-rs at the Container level. + +**Audit verdict:** 26/26 factual claims verified, 7 minor discrepancies (most urgent D5: 10 layers × 5-byte markers = 50B in 32B → marker overflow, data-corruption risk for L8-10). Top expansion: counterfactual BindSpace snapshots (copy-on-write `Arc<[CogRecord]>` fork → explore counterfactual world read-only → merge/discard). + +## §3 — reconciliation with lance-graph + +- The 34 tactics' three mechanisms = the workspace's datapath(shader) / control(planner+escalation) / gate(elevation+SD/F markers) partition. NARS-truth-per-step = `contract::nars` + spo truth semiring. CollapseGate SD = the entropy gate (Invariant #2). Counterfactual = `CausalEdge64` −6 mantissa / SPO=0b111. ✓ consistent. +- **SPOQ's Q** = the qualia role → lance-graph's `QualiaColumn` (18→16D) / `QualiaI4_16D`. So the AGI-as-SoA four columns map: Fingerprint≈S/O content, Edge≈P+causal, Meta≈layer/RL, **Qualia≈Q**. +- **Caveat:** Container `[u64;128]` (8K-bit) vs lance-graph's 16K `Vsa16kF32`. ladybug ran 10K-D and *failed* (E-AGICHAT-DIMENSION-CONTRACT: "empty cathedral"); the workspace restores the *contract* on the i4 SoA floor, not the 10K carrier. The SPOQ container layout is a *design reference* for the MetaView word budget, not a port. + +## §4 — open / for the inventory + +- The full 36 NARS styles (now ~20 named in `34-tactics-vs-ada.md`) + the 208-dim address space (32 verbs + 36 GPT + 36 NARS + 11 presence + 5 archetype + 3 TLK + 4 affect + 33 TSV) are the real composite atom budget. +- Reconcile `spo-2cubed-list-coverage.md`'s 34-table *status* column against §1 here (ladybug implements all 34; ada-consciousness implements 21 — both authoritative for their repo). +- Decide: does the workspace track **SPO 2³ (8, causal)** or **SPOQ 2⁴ (16, +qualia)** as the lattice? The audit says Q is bound but qualia is a separate affective role — lean SPO 2³ for causality, Q as overlay. + +**Cross-ref:** `34-tactics-vs-ada.md`, `spo-2cubed-list-coverage.md`, `atom-basis-inventory.md`, `EPIPHANIES.md` E-AGICHAT-DIMENSION-CONTRACT (the 10K "empty cathedral" → restore-on-SoA lineage), THINKING_RECONCILIATION.md (5 taxonomies). diff --git a/.claude/knowledge/atom-basis-inventory.md b/.claude/knowledge/atom-basis-inventory.md new file mode 100644 index 00000000..08b6fa30 --- /dev/null +++ b/.claude/knowledge/atom-basis-inventory.md @@ -0,0 +1,90 @@ +# Atom Basis Inventory — D-ATOM-0 (the LOCKED 33-dim ThinkingStyleVector) + +> **READ BY:** D-ATOM-1 (`contract::atoms` → `ThinkingStyleI4_32D`), D-ATOM-2, D-ATOM-3; `truth-architect`. +> **Status:** D-ATOM-0 resolution. Source = **`E-AGICHAT-DIMENSION-CONTRACT`** — agichat's **LOCKED** 33-dim TSV (`CANONICAL_DIMENSION_ALLOCATION.md`, "Status: LOCKED"). **Supersedes the earlier qualia draft AND the callcenter-32 draft — both were the wrong source.** This is the rung-ladder the atoms live in. +> **Date:** 2026-05-27. + +## Smallest → largest (do not confuse the layers) + +- **atom** = the **smallest unit = one pole** (e.g. `deduce`, `induce`, `R5`, `Φ`, `exploration`). 64 atoms = 32 lanes × ±. An atom is **not** a group and **not** the vector. +- **thinking style** = **one i4-32D vector** — a weighting across all the atoms (2^128 possible). Kant / Schopenhauer = specific vectors. **This is the molecule.** +- **persona** = a composition of styles + thresholds + purpose + β. + +`ThinkingStyleI4_32D` is the *type that holds a style* (a molecule); its **individual lanes/poles are the atoms**. The groups below (Pearl/Rung/Σ/Ops/Presence/Meta) are **allocation families** — neither atoms nor molecules, just how the lanes are budgeted. + +**Atoms are bare-metal by design — not human-legible.** A single i4 pole means nothing to a human; that's fine. Atoms exist to be *composed into object-oriented metacognition*: a **style is an object** (a `StyleRecipe` carrying behavior over its atom-weighting), a **persona is an object** (a `PersonaRecipe` composing styles + β/thresholds). The metacognition you reason about is Kant-the-object / the-OSINT-persona-object — never "pole #37". This is the workspace's "thinking is a struct / the object speaks for itself" doctrine; the **objects are the cognition**. + +**Execution stack: `atoms → cognitive-shader-driver → SIMD`.** Atoms are NOT SIMD. Atoms **dispatch through `cognitive-shader-driver`** (the encode/decode engine that sweeps the SoA columns); the **shader-driver** is the layer that uses SIMD (ndarray i4-32). So **D-ATOM-1 defines the atoms and routes them into the shader-driver — no SIMD dot in the atom layer** (that's the driver's job, largely already built); **D-ATOM-2 builds the OO layer (the actual metacognition)** as objects dispatched through the same driver. + +## The atom source is the bighorn-36 OPERATIONS (HOW-TO-DO) — run the dichotomy method on them + +**Per the user (the original instruction): the atoms = the bighorn-36 operations** (`bighorn/thinking_styles_top.py`, 9 categories × 4 = 36, "HOW TO DO") — **distinct** from the contract-36 personas ("HOW TO BE") and from the agichat 33-TSV (a *different* taxonomy; see THINKING_RECONCILIATION.md, 5 taxonomies compared). Method: **mark the dichotomic ones (opposite already in the set); for the rest, find the true opposite and keep it iff it has value.** + +⚠️ The full 36 is in **upstream bighorn (not in the MCP allowlist — cannot fetch)**. Below is the method applied to the ~19 operations evidenced inside this workspace (THINKING_RECONCILIATION.md). The remaining ~17 need the bighorn source or user confirmation. + +**Confirmed dichotomic pairs (both poles in the evidenced set → one signed lane each):** + +| − pole | + pole | +|---|---| +| Decompose | Synthesize | +| Compress | Expand | +| Concretize | Abstract | +| Ground | Lift | +| Dissolve | Weave | +| Sequence | Parallel | + +**Non-dichotomic (opposite not in set → PROPOSED, evaluate-for-value; confirm/correct):** + +| op | proposed opposite | value? | +|---|---|---| +| Hierarchize | Flatten | ✅ (Flatten is evidenced separately) | +| Negate | Affirm | ✅ | +| Resonate | Dissonate | ✅ (= the dissonance signal) | +| Spiral | Direct (one-pass) | ✅ | +| Transform | Conserve | ~ | +| Meta | Object/Immerse | ✅ | +| Invert | (self-inverse) | → likely unipolar | + +So far: 6 confirmed pairs + ~6 materialized opposites + 1 unipolar = 13 of the claimed 36. **Blocker:** the other ~17 bighorn ops are upstream-only. + +> **NOTE — supersedes the "33-TSV is the atom basis" section below.** The 33-dim TSV (Pearl/Rung/Σ/Ops/Presence/Meta) is the *agichat* taxonomy and is **not** the atom set; the atoms are the bighorn operations above. `contract::atoms::CANONICAL_ATOMS` currently holds the 33-TSV and must be re-pointed at the bighorn-36 once the full set is in hand. The 33-TSV content is retained below as a related-taxonomy reference, not the basis. + +## The basis is LOCKED, not derived + +`ThinkingStyleI4_32D` = **i4 × 33** (32 + 1 spare), riding the shipped ndarray i4-32 unpack (`E-I4-META-1`, `8de1dcf8`). The allocation IS the contract — `CANONICAL_DIMENSION_ALLOCATION.md` rejects arbitrary dim moves. Do not re-derive; record and classify. + +| group | n | dims | kind | +|---|---|---|---| +| **Pearl** | 3 | SEE (association) / DO (intervention) / IMAGINE (counterfactual) | ordinal causal ladder | +| **Rung** | 9 | R1–R9 (meaning-depth) | ordinal depth ladder 🪜 | +| **Sigma** | 5 | Ω / Δ / Φ / Θ / Λ (σ-tier chain) | ordinal tier sequence | +| **Operations** | 8 | abduct / deduce / induce / synthesize / preflight / escalate / transcend / model_other | operations (one inference ± pair inside) | +| **Presence** | 4 | authentic / performance / protective / absent | modes | +| **Meta** | 4 | confidence_threshold / preflight_depth / exploration / verbosity | scalar knobs | + += **33.** Qualia (`QualiaI4_16D`, 16D packed from the 18D PCS) is a **separate vector**, NOT part of the TSV — that's why qualia was the wrong source. + +**Business is NOT an atom — it is a sidecar inherited from OGIT.** No business/FIBU dims in the TSV. Business context rides in via the front-door OGIT class resolution (`E-OGIT-STAKES-LINCHPIN`): the request's OWL/DOLCE class → O(1) `lance_graph_ontology::MappingRow` → `Marking::Financial` → the bookkeeping savant + `RuleBundle`/SKR04 (`E-FIBU-GOBD-BY-CONSTRUCTION`). It is inherited per-request on the existing inherit-set (marking→stakes, thinking_style→savant, …), not hand-authored and not a pole. + +## Dichotomy-bounded? (first pass — confirm) + +Most TSV dims are **not ± dichotomies** — they are ordinal ladders / scalar knobs / distinct ops: + +- **Pearl (3), Rung (9), Sigma (5) = 17 dims → ordinal LEVELS.** A magnitude along a ladder, readable as −pole = low end ↔ +pole = high end (association↔counterfactual; shallow↔deep R1↔R9; Ω↔Λ). Bipolar *as endpoints*, not opposite operations. +- **Operations (8):** mostly distinct operations (selectors). The one clean ± pair is **deduce ↔ induce** (top-down ↔ bottom-up); **abduct** is the third inference mode — this is exactly the "abduction–induction / deduction–induction" hint, and it lives *inside* the 8 Ops, not as a separate triad. synthesize / preflight / escalate / transcend / model_other = distinct unipolar ops. +- **Presence (4):** **authentic ↔ performance** is a ± pair; protective / absent = modes (or present↔absent). +- **Meta (4):** scalar knobs. **exploration = the explore↔exploit / temperature axis** (✅ bipolar). confidence_threshold / preflight_depth / verbosity = magnitudes. + +**Genuine ± dichotomies are few:** deduce↔induce, authentic↔performance, exploration. The bulk are **ordinal levels or scalar knobs** → i4 as signed-position or unsigned-magnitude, not opposite-poles. + +## Resolved by this source + +- **Encoding:** i4 × 33 on the shipped i4-32 unpack. Ordinal ladders → magnitude lanes; the few ± pairs → signed lanes. +- **abduction:** it is 1 of the 8 Operations (with deduce/induce) — not a separate triad to pair off. +- **"not NARS / orthogonal":** the 6 groups (Pearl/Rung/Sigma/Ops/Presence/Meta) are the orthogonal dimension-groups; NARS-inference = ~3 of the 8 Ops. Confirmed. + +## OPEN + +1. **The 33rd/spare dims:** `STYLE_ENCODING.md` says "3 Pearl + 9 Rung + 5 Σ + 8 Op + **8 spare**" (= 33); the contract body names the last 8 as **4 Presence + 4 Meta**. Confirm which is canonical (spare vs Presence+Meta). +2. Confirm the ± reads (deduce↔induce, authentic↔performance, exploration) vs treating Ops/Presence as pure selectors. +3. Per-lane i4 sign convention for the ordinal ladders (0=R1 ascending, or centered). diff --git a/.claude/knowledge/spo-2cubed-list-coverage.md b/.claude/knowledge/spo-2cubed-list-coverage.md new file mode 100644 index 00000000..c949153e --- /dev/null +++ b/.claude/knowledge/spo-2cubed-list-coverage.md @@ -0,0 +1,90 @@ +# SPO 2³ — the official list + cross-repo list-coverage inventory + +> **READ BY:** truth-architect; anyone checking whether a taxonomy reduces to the causal lattice. +> **Status:** rubric established (this file). Inventory pass = in progress (Opus agent). +> **Date:** 2026-05-27. + +## The official 2³ Rung ladder list (the coverage rubric) + +The SPO 2³ is the **powerset of {S, P, O}** — 8 evidential projections of a causal claim, **NOT** a distance-cube. Grounded in `rung-mul-grounding-v1` + CausalEdge64 v2 §6 (causal mask `[40:42]`, `nars_engine::MASK_SPO = 0b111`). Counterfactual is the top element (it is "one of those"). + +| mask | proj | level | meaning | +|---|---|---|---| +| `0b000` | `___` | 0 | base rate (Association / prior) | +| `0b100` | `S__` | 1 | marginal: does S occur? | +| `0b010` | `_P_` | 1 | marginal: does P occur? | +| `0b001` | `__O` | 1 | marginal: does O occur? | +| `0b110` | `SP_` | 2 | pair: cause↔mechanism (O marginalized) | +| `0b101` | `S_O` | 2 | pair: cause↔effect (P marginalized — maybe spurious) | +| `0b011` | `_PO` | 2 | pair: mechanism↔effect = **Intervention** | +| `0b111` | `SPO` | 3 | joint = **Counterfactual** (top of the ladder) | + +Causation = the **screening-off pattern** across the 8 (front-door/back-door as NARS truth over the lattice), not the joint alone. The 4 levels (0/1/2/3) = the Pearl ladder = the rung axis. + +## Coverage test ("is list X covered by SPO 2³?") + +A list is **covered** iff its members map onto the 8 projections / 4 levels (or a sub-lattice) — i.e. it *is* the causal decomposition wearing other names. **Partial** = some members map, others are orthogonal. **Not covered** = orthogonal axis (qualia, presence, persona, operations, tensor/ML types). + +## REFERENCE SET — the 34 LLM reasoning tactics (MUST be included) + +Source: Stakelum, "Beyond Chain-of-Thought: 34 next-gen LLM tactics." These are the mandated reference styles the catalogue must cover. Bucket = hardware-partition (datapath/control/gate). 2³ = causal-lattice coverage. + +| # | tactic | bucket | 2³? | maps to (workspace) | +|---|---|---|---|---| +| 4 | RCR Reverse-Causality | control | **Covered** | backward `S_O` / Abduction | +| 31 | ICR Iterative-Counterfactual | control | **Covered** | `SPO`=0b111 Counterfactual (top) | +| 11 | ICR Contradiction-Resolution | control | Partial | Revision + Contradiction (committed, not resolved) | +| 7 | ASC Adversarial-Self-Critique | control | Partial | InnerCouncil split / dissonance | +| 21 | SSR Self-Skepticism | control | Partial | InnerCouncil split | +| 17 | CDI Cognitive-Dissonance-Induction | control | Partial | dissonance gate | +| 30 | SPP Shadow-Parallel | control | Partial | the counterfactual majority/minority fork | +| 2 | HTD Hierarchical-Decomposition | control | Not | Decompose op | +| 22 | ETD Emergent-Task-Decomposition | control | Not | Decompose op (dynamic) | +| 19 | ARE Algorithmic-Reverse-Eng | control | Not | Decompose / Abduct | +| 1 | RTE Recursive-Thought-Expansion | gate | Not | rung depth × Expand/Compress | +| 8 | CAS Conditional-Abstraction-Scaling | gate | Not | Abstract↔Concretize × rung | +| 3 | SMAD Simulated-Multi-Agent-Debate | control | Not | `a2a_blackboard` / InnerCouncil | +| 5 | TCP Thought-Chain-Pruning | gate | Not | entropy(SD) prune | +| 20 | TCF Thought-Cascade-Filtering | gate | Not | entropy(SD) select | +| 6 | TRR Thought-Randomization | gate | Not | temperature (Staunen) | +| 13 | CDT Convergent-Divergent | gate | Not | explore↔exploit temperature | +| 26 | CUR Cascading-Uncertainty-Reduction | gate | Not | FreeEnergy / confidence gate | +| 10 | MCP Meta-Cognition | control | Not | Meta lane | +| 15 | LSI Latent-Space-Introspection | control | Not | Meta / self-state-awareness | +| 23 | AMP Adaptive-Meta-Prompting | control | Not | Meta dispatch | +| 33 | DTM Dynamic-Task-Meta-Framing | control | Not | Meta dispatch | +| 16 | PSO Prompt-Scaffold-Optimization | control | Not | Hierarchize/scaffold | +| 12 | TCA Temporal-Context-Augmentation | datapath | Not | temporal lane / Markov ±5 | +| 18 | CWS Context-Window-Simulation | control | Not | episodic memory / WitnessCorpus | +| 28 | SSAM Self-Supervised-Analogical-Mapping | control | Not | Analogy op | +| 24 | ZCF Zero-Shot-Concept-Fusion | control | Not | Synthesize / Weave | +| 34 | HKF Hyperdimensional-Knowledge-Fusion | control | Not | Synthesize (cross-domain) | +| 25 | HPM Hyperdimensional-Pattern-Matching | datapath | Not | resonance (cosine sweep) | +| 27 | MPC Multi-Perspective-Compression | control | Not | Compress × perspective | +| 9 | IRS Iterative-Roleplay-Synthesis | control | Not | persona / model_other | +| 29 | IDR Intent-Driven-Reframing | control | Not | disambiguation / clarification | +| 32 | SDD Semantic-Distortion-Detection | control | Not | clarification op | +| 14 | M-CoT Multimodal-CoT | datapath | Not | multimodal sensorium | + +**Tally:** 2³ Covered = 2 (RCR, ICR-counterfactual) · Partial = 5 · Not = 27. So **the 2³ lattice covers only the causal-reasoning tactics**; the other 27 are orthogonal axes (operations / meta / gating / memory) — confirming 2³ is the *causal* spine, not the whole style space. Buckets: ~6 datapath-touching, ~21 control, ~7 gate. + +## Inventory — workspace lists (Opus inventory pass, 2026-05-27) + +**31 enumerated cognitive lists** inventoried across contract / planner / cognitive-shader-driver / thinking-engine / holograph + 3 markdown taxonomies (bighorn-36 ops, agi-chat Quad-Triangle / styleBank / 9-RI). **2³ tally: Covered 4 · Partial 6 · Not 21** — confirming 2³ is the *causal spine only*. + +**Covered (4 — these ARE the lattice):** +- `CausalMask` — the 8 projections verbatim. +- `nars_engine` `MASK_*` / `ALL_MASKS` — the powerset masks. +- `PearlLevel` — the 3 rungs (Association/Intervention/Counterfactual). +- `CANONICAL_ATOMS[0..3]` — the Pearl lanes of the 33-TSV. + +**Partial (6 — causal members ride the lattice, rest orthogonal):** +- the 4 NARS inference enums (Intervention=`_PO`, Counterfactual=`SPO` ride it; Deduction/Induction/Abduction/Revision/Synthesis/Analogy orthogonal). +- `InferenceOp` (10, Pearl-tagged). +- `CANONICAL_ATOMS` Operation group + holograph `VerbCategory` (its Causal subcategory). + +**Not covered (21 — orthogonal axes):** every style / cluster / σ-tier / rung-ladder / layer / qualia / ghost-echo / presence / MUL / expert-capability list. (These are the operation/meta/gating/memory/affect axes — not causal.) + +**Gaps — reference tactics with NO enumerated workspace home yet:** #18 CWS (context-window/memory), #14 M-CoT (multimodal), #29 IDR / #32 SDD (intent/clarification), #16 PSO / #23 AMP / #33 DTM (meta-prompting scaffold), #12 TCA (temporal), #22 ETD / #19 ARE (dynamic decomposition), #5 TCP / #20 TCF (dedicated thought-pruning). Strongest orthogonal homes for the rest: bighorn-36 ops (#2/#8/#27), InnerCouncil Archetype+split (#3/#7/#21/#30), MUL layer (#26/#10/#15). + +> **Cross-repo note (per `agi-stack-cross-repo.md`):** ladybug-rs implements **all 34** as structural primitives (mapped to the Sun et al. reasoning ladder); ada-consciousness implements 21. So the "gaps" above are gaps in *lance-graph's enumerated lists*, not in the AGI stack — the homes exist upstream. Also: the real lattice is **SPOQ** (S/P/O + Q-qualia, 4 roles); SPO 2³ = the causal slice, Q = the orthogonal affective overlay. diff --git a/.claude/plans/atom-mailbox-substrate-v1.md b/.claude/plans/atom-mailbox-substrate-v1.md new file mode 100644 index 00000000..e5287e03 --- /dev/null +++ b/.claude/plans/atom-mailbox-substrate-v1.md @@ -0,0 +1,82 @@ +# atom-mailbox-substrate-v1 + +> **Status:** PROPOSAL (implements `EPIPHANIES.md` E-LADDER-SERVES-MAILBOX, 2026-05-27). +> **Confidence:** HIGH on the mechanism-to-existing-machinery mapping (every piece anchors to a shipped type or an iron rule); **CONJECTURE on the atom basis itself** (D-ATOM-0, the load-bearing unsolved decision) and on the I4-32D SIMD layout until probed. +> **Plan file:** `.claude/plans/atom-mailbox-substrate-v1.md` +> **Predecessors:** `rung-persona-orchestration-v1` (D-PERSONA-1 shipped the escalation/checklist/ghost types this extends), `rung-mul-grounding-v1` (MUL experience curve + wisdom marker), `cognitive-substrate-convergence-v1` (CausalEdge64 v2, Pearl 2³). +> **Anchored iron rules (FINDING):** `I-VSA-IDENTITIES` (persona=Layer-2, Test 0 register-laziness, bipolar ±1, Test 2/3), `E-BATON-1` (mailbox-as-owner, no persisted singleton), `I-LEGACY-API-FEATURE-GATED` (`CausalEdge64` 4-bit signed mantissa @46-49, Counterfactual=−6), The Click (Staunen×Wisdom, Resolution thresholds, `awareness.revise`). + +--- + +## The one-line thesis + +The escalation ladder serves the **mailbox**, not the persona. Under it sits a three-layer cognitive basis — **atoms (bipolar I4-32D) → thinking styles (compositions) → persona recipes (compositions + thresholds)** — where each atom is *measured by a quorum*, split quorums are *preserved as a counterfactual mantissa*, and memory is *ephemeral-hot in the mailbox, calcified-cold in SPO + a Lance tombstone-witness*. + +## Scope (six pillars — see the epiphany for the full derivation) + +1. **Ladder→mailbox reframe.** Persona = Layer-2 dispatch policy (β + fan-out pattern), not a container. The D-PERSONA-1 types are already mailbox-shaped; this is a reframe, not a rebuild. +2. **3-layer basis.** **atom** = one lane of the **LOCKED 33-dim TSV** (E-AGICHAT-DIMENSION-CONTRACT; 3 Pearl + 9 Rung + 5 Σ + 8 Ops + 4 Presence + 4 Meta) — bare-metal, not human-legible; **style** = one i4 vector over the atoms (the molecule); **persona** = composition of styles + thresholds. Cranelift templates compile the *recipe* (the object), not the atom lanes. Atoms dispatch through `cognitive-shader-driver` (which owns SIMD) — **no SIMD in the atom layer**. Business is an OGIT-inherited sidecar, not an atom. +3. **Quorum projection.** A dichotomy needs a quorum to place a measurement between its poles; each atom value = `(I4 position, quorum-confidence)` = NARS truth per axis. Splits are Contradictions, never averaged. +4. **Temperature axis.** wisdom↔Staunen = sampling temperature, self-regulated by free energy; the `WisdomMarker` 0.1 floor = minimum temperature (φ-1 humility). Distinct from plasticity (update-rate). +5. **Counterfactual mantissa.** On `is_split`: commit the majority pole, fork the minority into a counterfactual mailbox retained as a `CausalEdge64` −6 nibble; ghost-tier test on β headroom; minority win → `awareness.revise`. +6. **AriGraph hot/cold/tombstone.** Ephemeral-hot in mailbox → calcify to cold SPO → tombstone-witness in *versioned* Lance (= GoBD audit by construction). One compression hierarchy down the codec atlas. + +--- + +## Decision gates + +- **D-ATOM-0 — the atom basis. ✅ RESOLVED — the basis is LOCKED, not derived.** It is agichat's 33-dim TSV (`E-AGICHAT-DIMENSION-CONTRACT` / `CANONICAL_DIMENSION_ALLOCATION.md`): **3 Pearl + 9 Rung + 5 Σ + 8 Operations + 4 Presence + 4 Meta** = 33, restored on the shipped i4-32 floor. No ICA/PCA, no "demote the 36 styles" (the 36 `ThinkingStyle` ids are *styles* — vectors over the atoms — not the atoms). Catalogue committed in `contract::atoms::CANONICAL_ATOMS` + `.claude/knowledge/atom-basis-inventory.md`. Earlier "ICA/PCA over 36" framing was wrong and is retracted. +- **Remaining sub-gates (layout, not basis):** (i) 32-vs-33 carrier reconciliation (i4-32 floor holds 32 lanes; TSV is 32+1); (ii) "8 spare" (STYLE_ENCODING) vs "4 Presence + 4 Meta" (contract body); (iii) per-group i4 sign/scale (ordinal ladders = magnitude, the few ± lanes signed). NARS is **not** ~24 atom dims — NARS-inference is 3 of the 8 Operations; the rest of the families are orthogonal (supersedes the old "24 NARS" budget line). + +--- + +## Deliverables + dependency DAG + +| D-id | Scope | Crate / files | Depends on | Basis-dependent? | +|---|---|---|---|---| +| **D-ATOM-1** | LOCKED 33-TSV catalogue + `I4x32` bare-metal carrier (pack/unpack only; **no SIMD** — dispatch via `cognitive-shader-driver`) | `contract::atoms` (scaffolded ✓) | D-ATOM-0 ✅ | unblocked | +| **D-ATOM-2** | style/persona = I4-32D compositions; Cranelift recipe templates | `contract::jit` (`StyleRegistry`), `contract::thinking` (back enum with composition) | D-ATOM-1 | YES | +| **D-ATOM-3** | quorum-projection `(position, confidence)` per axis | `contract::escalation` (`InnerCouncil`→per-axis), `contract::a2a_blackboard` | D-ATOM-1 (axis shape); mechanism semi-independent | partial | +| **D-ATOM-4** | counterfactual mantissa: **v2** deposit `−6` on split, **v3** mailbox + `awareness.revise` | `contract::escalation`, `CausalEdge64` mantissa path | `is_split` (shipped) + mantissa (shipped) | **NO** | +| **D-ATOM-5** | AriGraph hot→calcify→Lance tombstone-witness + link integrity | `lance-graph` core (AriGraph), Lance versioned store | — | **NO** | + +**Critical path:** D-ATOM-0 ✅ (locked TSV) → D-ATOM-1 (catalogue done; pack/unpack + carrier wiring remain) → D-ATOM-2 (the OO layer — the part that matters). D-ATOM-4 v2 and D-ATOM-5 are basis-independent. D-ATOM-4 v3 needs the ractor outer-swarm from `rung-persona` D-PERSONA-5. **SIMD is the `cognitive-shader-driver`'s, never the atom layer's.** + +--- + +## Per-agent split (the `///` scaffold wave) + +One **Sonnet** agent per deliverable, **disjoint file scopes**, **edit/write only** (no cross-file refactors). Each agent: (1) reads `.claude/board/AGENT_LOG.md` + `LATEST_STATE.md` + `EPIPHANIES.md` E-LADDER-SERVES-MAILBOX first; (2) writes **`///`-doc scaffolding only** — public type + signature surface with rustdoc specs, `todo!()` / `unimplemented!()` bodies, and `// BLOCKED: ` markers wherever a decision is missing (do **not** invent a basis, a version, or an API); (3) prepends its own AGENT_LOG entry. Iron rule for workers: **leave a `BLOCKED` marker rather than guess** (the surreal-poc Wave-A precedent). + +- **Agent-A → D-ATOM-4 v2** (basis-independent, smallest, safest first slice). +- **Agent-B → D-ATOM-5** (basis-independent). +- **Agent-C → D-ATOM-3 trait surface** (mechanism only; axis wiring `BLOCKED` on D-ATOM-1). +- D-ATOM-1 catalogue is done (locked TSV); next is the `cognitive-shader-driver` carrier wiring + pack/unpack. **D-ATOM-2 (the OO style/persona object layer) is the deliverable that matters** — that *is* the metacognition; D-ATOM-1 just has to be bare-metal-correct and out of the way. + +## Execution loop (per deliverable — "scaffold → review → implement → PR → green → merge → repeat") + +1. **Scaffold** — Sonnet agent writes the `///` surface (above). +2. **Review (P2 gate)** — see *Review mapping* below; produces a findings list (correctness + reuse/simplification + iron-rule compliance). +3. **Implement** — fix the findings, **replace `todo!()`/`///`-stubs with real bodies**, resolve `BLOCKED` markers (escalate any that need a decision). Run `cargo test -p `. +4. **PR** — sub-branch `claude/atom-` off `claude/splat3d-cpu-simd-renderer-MAOO0`; PR **into** the working branch (not main). Board hygiene in the same commit (STATUS_BOARD row, LATEST_STATE inventory, AGENT_LOG). +5. **Subscribe** — `subscribe_pr_activity` on the PR; autofix CI failures + review comments per the PR-activity protocol. +6. **Merge** — only when CI is green **and** the merge policy (below) permits. +7. **Repeat** for the next deliverable in DAG order. + +## Review mapping ("P2 codex savant team" → what we actually have) + +There is **no literal `codex` binary** in this environment. The P2 gate maps to one of: **(i)** the `/code-review` skill at `high`/`ultra` effort (ultra = multi-agent cloud review of the diff — closest to a "savant team"); **(ii)** spawning 2-3 **Opus** review agents with disjoint lenses (correctness / iron-rule-compliance / reuse-simplification) and synthesizing on the main thread. Default proposal: **`/code-review high` per PR, escalating to `ultra` for D-ATOM-1/2** (the basis-bearing ones). Confirm in the gating question. + +--- + +## Invariants (inherited + new) + +- Restore-on-substrate, not port · persona = Layer-2 catalogue (no container struct) · atoms ≤ the I4-32D bipolar basis; NARS *type* in a register (Test 0) · markers ≤ 32 identities (I-VSA-IDENTITIES) · splits = Contradiction, never averaged · counterfactual stays in a separate lane (Counterfactual-tagged, never observed SPO truth) · AriGraph = the one graph (no second store) · ephemeral bundle, no persisted singleton (E-BATON-1) · ractor async only at the swarm boundary (no double-mailbox) · respawn bounded (N retries → FailureTicket) · `latency_budget` time arbiter, no wall-clock in the hot Pod · i4 precision tradeoff cited to `FormatBestPractices.md`; SIMD path gated on MANDATORY `ndarray-vertical-simd-alien-magic.md`. + +## Honest gaps / open questions + +- **D-ATOM-0 is genuinely unsolved** — the basis derivation is asserted nowhere yet; this plan cannot proceed past D-ATOM-2 scaffolding without it. +- The 36↔64 arithmetic (36 named atoms inside 32 dims / 64 poles, ~28 spare?) was never closed in dialogue — D-ATOM-0 must resolve it. +- Temperature as flat peer dim vs **meta-atom read first** (one-pass vs two-stage I4 sweep) — layout-level open. +- `WitnessCorpus` and `SigmaTierRouter` Σ-tier D-ids (homes for the tombstone-witness and the Rubikon admission gate) were cited from dialogue — **verify against STATUS_BOARD before wiring** D-ATOM-5 / D-ATOM-4 v3. +- substrate-Markov re-scope (unsolicited-materialization-only) is **out of scope here** — it awaits the `[FORMAL-SCAFFOLD]` dependency check. diff --git a/.claude/plans/rung-ladder-grounding-v1.md b/.claude/plans/rung-ladder-grounding-v1.md new file mode 100644 index 00000000..cb036d37 --- /dev/null +++ b/.claude/plans/rung-ladder-grounding-v1.md @@ -0,0 +1,113 @@ +# rung-ladder-grounding-v1 — ground agichat's RungShift ladder + CollapseGate as LE-contract + +**Status:** PROPOSAL (the most-obvious first grounding per `E-AGICHAT-DIMENSION-CONTRACT`) +**Date:** 2026-05-26 +**Confidence:** HIGH — the ladder is deterministic integer logic, no VSA in the loop; cleanest possible first restore. +**Predecessors:** `E-AGICHAT-DIMENSION-CONTRACT` (afabefd), `E-I4-META-1`, `E-BATON-1`; shipped floor: ndarray `SoaColumns` (42cb7123) + i4-32 unpack (8de1dcf8). +**Design refs (allowlist-external, read-only):** agichat `src/thinking/{rung-shift,collapse-gate}.ts`; ladybug-rs `src/cognitive/rung.rs`. + +--- + +## 1. Why this one first + +The rung ladder is the single most groundable piece of the gestell: it is **already deterministic integer/threshold logic** (RungLevel 0-9, counters, fixed thresholds) with **zero VSA resonance in the decision path**. agichat had it grounded; ladybug-rs kept it (rung.rs is a faithful port). There is nothing to "de-inflate" — only to express as a bit-exact Pod on the SoA floor and wire the triggers to grounded signals. It also has a clean hook: the **9 Rung dims (R1-R9)** are a dimension-group of the 33-TSV, so this grounds that group + the escalation logic that walks it. + +## 2. The mined ladder spec (the contract to preserve, verbatim semantics) + +**CollapseGate** (`collapse-gate.ts`): SD = std-dev of candidate resonance scores ∈ [0,1]; `SD_MAX = 0.5`; `FLOW < 0.30·SD_MAX (=0.15) ≤ HOLD ≤ 0.70·SD_MAX (=0.35) < BLOCK`. **SD is dispersion (compute allocation), NOT confidence** (Canonical Invariant #2). + +**RungShift** (`rung-shift.ts`): `RungLevel 0-9`, `RungBand {0-2, 3-5, 6-9}`. Thresholds `{sustainedBlockTurns:3, pMetricThreshold:0.3, pMetricWindow:5, cooldown:10s}`. Per cycle, `update(gate, p_metric, has_legal_parse)`: BLOCK → `consecutive_blocks++` else reset; push `p_metric` into a 5-window; `structural_mismatch = !has_legal_parse`. Then `evaluate_shift`: +1. cooldown active → no shift +2. at MAX_RUNG (9) → no shift +3. **sustained_block** (`consecutive_blocks ≥ 3`) → +1 +4. **predictive_failure** (`avg(window) < 0.3`, window full) → +1 +5. **structural_mismatch** → +1 +6. else no shift + +(`RungShift` is **separate from SD** — Canonical Invariant #3.) + +## 3. The grounded contract (LE-contract form) + +Bit-exact, Pod, fixed-size, no `Vec`, no wall-clock, no float carrier: + +```rust +// lance-graph-contract (zero-dep) +#[repr(u8)] enum GateState { Flow=0, Hold=1, Block=2 } // 2 bits +#[repr(u8)] enum RungBand { Low=0, Mid=1, High=2 } // 2 bits +#[repr(u8)] enum RungTrigger { None, SustainedBlock, PredictiveFailure, StructuralMismatch, Manual } + +// RungLevel = u8 (0..=9), 4-bit field. + +#[repr(C)] #[derive(Clone, Copy)] // Pod, lives in a SoA column +struct RungState { + rung: u8, // 0..=9 (4-bit used) + consecutive_blocks: u8, + flags: u8, // bit0 = structural_mismatch + p_head: u8, // ring index 0..5 + p_window: [u8; 5], // P-metric quantized to u8 (0..255 = 0.0..1.0); fixed ring, NO Vec + last_shift_tick: u32, // tick-based cooldown (NOT wall-clock ms) + _pad: [u8; 2], +} // 16 bytes = 2 atoms, or folds into a MailboxSoA meta column + +#[derive(Clone, Copy)] +struct RungShiftDecision { should_shift: bool, current: u8, target: u8, trigger: RungTrigger } + +const SUSTAINED_BLOCK_TURNS: u8 = 3; +const P_METRIC_THRESHOLD_U8: u8 = 77; // 0.30 × 255 +const P_METRIC_WINDOW: usize = 5; +const SHIFT_COOLDOWN_TICKS: u32 = /* tuned; replaces 10s wall-clock */ ; +const SD_FLOW_U8: u8 = 38; // 0.15 × 255 (SD over u8-quantized candidate scores) +const SD_BLOCK_U8: u8 = 89; // 0.35 × 255 +``` + +**Translations from the agichat carrier → grounded:** +- `recentPMetrics: number[]` (heap Vec) → `p_window: [u8;5]` fixed ring (Pod, no alloc). +- `shiftCooldownMs` wall-clock → `SHIFT_COOLDOWN_TICKS` (the SoA cycle is tick-driven). +- P-metric / candidate scores: f32 [0,1] → **u8-quantized** (or i4 via the shipped i4-32 unpack). SD computed over the quantized lane (ndarray SIMD), so the gate runs on bit-exact distances, never 10K-D resonance. +- `shiftHistory: Vec` → out of the hot Pod; if needed, an append-only ring in a cold column (not in `RungState`). + +## 4. Layer placement (one canonical home per piece) + +| piece | home | note | +|---|---|---| +| `GateState`, `RungBand`, `RungLevel`, `RungState` (Pod), `RungShiftDecision`, `RungTrigger`, threshold consts | **lance-graph-contract** | zero-dep contract types | +| `calculate_sd`, `gate_state_from_sd`, `evaluate_rung_shift` (PURE fns), `apply_rung_shift` (builder) | **lance-graph-planner/src/elevation/** | reconcile with existing `homeostasis.rs` / `operator.rs` | +| `RungState` carried cycle-to-cycle | **ndarray `SoaColumns`** | one column; O(1) `Arc`-clone carry-over (shipped) | +| SD over candidate scores | **ndarray SIMD** (i4/u8 lanes) | dispersion on bit-exact distances | + +## 5. Closed loop (grounded) + +``` +candidate scores (u8/i4, e.g. SignificanceLevel distances) + → calculate_sd → gate_state_from_sd → GateState (2-bit) + → update RungState (tick): BLOCK→consec++, p_window ring-push, mismatch flag + → evaluate_rung_shift (PURE): cooldown? max? sustained? pred-fail? mismatch? → +1 + → rung(4b) + band(2b) → bucket addressing (= the 9 Rung dims R1-R9 of the 33-TSV) +``` + +No `&mut self` during compute (data-flow iron rule): `evaluate_rung_shift` takes `RungState` by value, returns `RungShiftDecision` by value; `apply_rung_shift` is the gated write-back (a builder step). + +## 6. Reconciliation (don't fork) + +- **`lance-graph-planner/src/elevation/`** already has `homeostasis.rs` + `learning.rs` + `budget.rs` + `operator.rs` (the "cost model that smells resistance"). The rung ladder is the **meaning-depth escalation** elevation should drive — fold `evaluate_rung_shift` in beside `homeostasis` (which is MUL-L6); do not add a parallel module. +- **`lance-graph-contract/collapse_gate.rs`** already exists (the Baton `CollapseGateEmission`). Add `GateState`/SD there — the gate already lives in the contract; extend, don't duplicate. +- **The 33-TSV (`E-AGICHAT-DIMENSION-CONTRACT`)**: RungLevel is derived from / indexes the **R1-R9 dim-group** of `ThinkingStyleI4_32D`. The ladder *writes* the rung profile; this grounds that group. Sequence after the TSV type lands, or co-land the rung group first as the pilot. + +## 7. Deliverables + +| D-id | title | crate | ~LOC | risk | +|---|---|---|---|---| +| D-RUNG-1 | contract types (RungLevel/Band/GateState/RungState Pod/Decision/Trigger + consts) | lance-graph-contract | 150 | LOW | +| D-RUNG-2 | pure ladder logic (`calculate_sd`, `gate_state_from_sd`, `evaluate_rung_shift`, `apply_rung_shift`) folded into `elevation/` | lance-graph-planner | 200 | LOW | +| D-RUNG-3 | `RungState` as a `SoaColumns` column + tick-driven `update` | ndarray bind + planner | 100 | LOW | +| D-RUNG-4 | wire SD→GateState into `collapse_gate.rs`; map rung→R1-R9 TSV group | contract + planner | 120 | MED | + +Tests: verbatim-semantics parity vs the agichat spec (sustained-block at exactly 3, pred-fail window-full + avg<0.3, mismatch, cooldown, max-rung cap, band boundaries 2/3 and 5/6), all on integer/u8 inputs. Gate green via central `cargo fmt`/`clippy -D warnings`/`test`. + +## 8. Invariants honored + +- **No `Vec`/alloc in the hot Pod** (fixed `[u8;5]` ring). - **No `&mut` during compute** (pure `evaluate`, builder `apply`). - **Tick-based, not wall-clock**. - **Integer rung, no float-resonance carrier** (the de-grounding ladybug-rs did). - **SD = dispersion, not confidence** (Invariant #2). - **RungShift separate from SD** (Invariant #3). - Bit-packed (rung 4b + band 2b + gate 2b in a meta byte). + +## 9. Cross-refs + +`E-AGICHAT-DIMENSION-CONTRACT` (grounding doctrine + the 33-TSV / 9-Rung group); shipped floor `SoaColumns` (42cb7123) + i4-32 (8de1dcf8); `lance-graph-planner/src/elevation/{homeostasis,operator}.rs`; `lance-graph-contract/src/collapse_gate.rs`; design refs agichat `src/thinking/{rung-shift,collapse-gate}.ts`, ladybug-rs `src/cognitive/rung.rs`. Iron rules: data-flow (no `&mut` compute), `I-NOISE-FLOOR-JIRAK` (SD on bit-exact distances, not bundles). diff --git a/.claude/plans/rung-mul-grounding-v1.md b/.claude/plans/rung-mul-grounding-v1.md new file mode 100644 index 00000000..c1ab7847 --- /dev/null +++ b/.claude/plans/rung-mul-grounding-v1.md @@ -0,0 +1,99 @@ +# rung-mul-grounding-v1 — the MUL fine-tuned into the ladder as an experience curve over the SPO 2³ NARS decomposition + +**Status:** PROPOSAL (follow-on to `rung-ladder-grounding-v1`) +**Date:** 2026-05-26 +**Confidence:** HIGH on the structure (it is the Dunning-Kruger curve mechanized — every strategy is the move that becomes *necessary* at one evidence level); MED on the per-projection `SpoHead` refactor (real change, D-RUNG-MUL-1); CONJECTURE on wisdom-marker calibration readout until D-RUNG-MUL-4 lands a test. +**Predecessors:** `rung-ladder-grounding-v1` (b0ef6fa), `E-AGICHAT-DIMENSION-CONTRACT` (afabefd), `cognitive-substrate-convergence-v1` (CausalEdge64 v2 §6: causal mask = Pearl 2³ IS the rung axis), `E-I4-META-1`. +**Grounded types (verified this session):** `nars_engine.rs::{SpoHead, all_projections, StyleVector}`; `FreeEnergy::compose` (cognitive-shader-driver/src/driver.rs:264, contract `grammar/free_energy.rs`); `mul/trust.rs::{TrustQualia, TrustTexture}`; `elevation/mod.rs::ElevationLevel` (L0:Point..L5:Async); `proprioception.rs` wonder axis (idx 9); planner `free_will_modifier`; holograph `NarsBudget{priority,durability,quality}`. + +--- + +## 0. The correction this plan is built on + +The SPO 2³ is **not a distance-cube** (popcount/Hamming-reach). It is the **powerset of {S,P,O}** — the 8 evidential projections you decompose a causal claim into and test separately through NARS. The essence is the *decomposition for causality testing*, not a metric. `nars_engine.rs` today computes `all_projections() -> [u32;8]` as **distances** (`causal_distance` sums active planes) and a `SpoHead` carries **one** truth — that is the cube-metric reading, and de-grounding it is D-RUNG-MUL-1. + +``` +___ ∅ base rate / prior — "nothing" (the null) +S__ marginal does S occur at all? +_P_ marginal does P occur at all? +__O marginal does O occur at all? +SP_ pair S–P holds, O marginalized (cause↔mechanism) +S_O pair S–O holds, P marginalized (cause↔effect — maybe spurious) +_PO pair P–O holds, S marginalized (mechanism↔effect) +SPO full joint the complete claim — does it cohere? +``` + +Causation = the **screening-off pattern** across the 8, not the joint alone: if `S_O` is strong but conditioning on P (`SP_` ∧ `_PO`) screens it off, the direct S→O is spurious/mediated. Every projection compared against `___` for lift over base rate. That is front-door/back-door logic expressed as NARS truth over the lattice. + +## 1. The experience curve (the organizing skeleton) + +Order every strategy by the evidence level at which it becomes **necessary**. The result is the Dunning-Kruger curve with a mechanical trigger at every point: + +| # | Trigger (evidence state) | Necessary strategy | DK position | Confidence ceiling | +|---|---|---|---|---| +| 0 | **NaN** — no field, cannot even form a prior | cautious exploration → fanout → **request Lab** | pre-Mount-Stupid (unknown unknowns) | hard-floored ≈ 0 | +| 1 | sparse, NARS `c≈0`, expectation→0.5 | **gaussian splat over data field → `FreeEnergy::compose(likelihood, KL)`** | foot of curve | = the free-energy itself (high F ⇒ low conf) | +| 2 | one projection holds (e.g. `S_O`) | tempting to assert | **Mount Stupid** | DK-gate *penalizes* (high f, low c) | +| 3 | decompose 2³, run screening-off | analytical / counterfactual work | **Valley of Despair** (`S_O` screened off by P) | Boole/Fréchet — ≤ weakest link | +| 4 | decomposition coheres across cycles | exploit, expectation-gated | Slope of Enlightenment | graded, earned | +| 5 | bundle accumulated truth → **wisdom marker** | hydrate as prior *before the fact* | Plateau (φ-1 permanent-humility ceiling) | calibrated; will not re-inflate | + +**Two curves over one axis.** Work (competence) climbs monotonically with decomposition; *confidence* is DK-shaped (spikes at Mount Stupid, craters in the Valley, recovers calibrated). The gap between them is the readout: + +> **wisdom = the calibration gap closing — `|confidence − competence| → 0`.** + +A hydrated wisdom marker starts the *next* situation already calibrated instead of at Mount Stupid, so the curve is a **spiral**: each pass seeds the prior (the KL anchor) for the next NaN/sparse encounter. + +## 2. The work metric (exploit half) + +Work is **decomposition + screening-off coverage**, expectation-gated, never popcount: + +``` +work(head) = Σ_{m ∈ 2³} tested(m) · screening_weight(m) · expectation_m + gated by: budget.quality ≥ q_min (AIKR resource gate) +``` + +- `tested(m)` = was projection `m`'s NARS truth actually derived (1) or skipped (0). +- `screening_weight(m)` — conditional-independence tests (the ones that distinguish causation from correlation) weigh most; raw marginals least; `___` is the base-rate reference. +- `expectation_m = c_m·(f_m − 0.5) + 0.5` — **confidence-gated, not frequency-gated.** Frequency alone is the Mount-Stupid signal (high f, low c = overconfidence on thin evidence). Confidence does the humility work; the deduction path already shrinks it (`deduced.c() < deduced.f()`), so deeper chains inherit more humility by construction. + +Asserting `SPO` directly = zero work. Deriving `S_O` and stopping = correlation, little work. Discovering `S_O` is screened off = the real causal work *and* the humble result. + +## 3. The humility / wisdom checks + +- **Boole/Fréchet bound (hard invariant):** `conf(SPO) ≤ min` over the conjunction's decomposed parts. You cannot be more certain of the conjunction than of its weakest link. Checkable per-projection; this is the rigorous form of "confidence graded down the ladder." +- **Calibration gap (the wisdom readout):** `wisdom = 1 − |confidence − competence|`, where competence = `work` (§2) normalized. This is what a wisdom marker measures and persists. +- **MUL gate (stakes-weighted, OGIT-grounded):** `MUL ≈ (risk / competence) × stakes`, where `competence = f(rung-level, resonance)` (depth-of-effort × familiarity — two distinct proxies, not interchangeable), `risk ≈ P(error)`, and **`stakes` is an O(1) ontological lookup**: the request's OWL/DOLCE class in **OGIT** (`AdaWorldAPI/OGIT` — the Open Graph of IT, `ogit.ttl`, reframed as an O(1) CAM) yields the cost-of-error (economic / safety-critical class → high; casual communicative act → low). The gate fires ∝ expected-loss / competence — so the DK danger zone (high risk+stakes, low competence) gates hardest, and stakes is *derived*, never hand-tuned. Consistent with the Boole-bound (MUL = risk×stakes / earned-confidence). **Stakes is concrete, not conjecture:** it is the `Marking` field on the OGIT `MappingRow` (in-code at `lance-graph-ontology`), resolved O(1) via `SchemaPtr` — ladder `Public < Internal < Pii ≈ Financial < Restricted` (the `Financial` variant doc reads *"bookkeeping or tax-relevant"*, so an invoice → `Financial` → high stakes → bookkeeping savant, exactly). OGIT is already a graph in code; the O(1) class address = the `SchemaPtr` packed `[namespace_id:8 | entity_type_id:16 | kind:8]` + `ontology_context_id` (the active named-graph context). + +## 4. The two sparse-data escalation routes + +- **NaN route (no field at all).** NaN gates the **Exploratory** style at high `exploration_rate` / low `speed_bias` ("cautious exploration" — a *region* of `thinking/style.rs` param space, **not a named style**) → fan out → raise `ElevationLevel` (L_n→L_{n+1}) → **request a Lab** (sandbox to generate the missing evidence). Note: NARS "no evidence" is properly `c=0`/expectation=0.5, **not** NaN — so this introduces a *deliberate NaN sentinel* meaning "no field to splat over," distinct from `c=0` ("evidence absent but prior exists"). c=0 → ordinary escalation; NaN → cautious-exploration + Lab. +- **Splat route (sparse field exists).** No direct NARS evidence ⇒ the *only* sanctioned confidence is gaussian splat over the data field → `FreeEnergy::compose(likelihood, KL)`. The free energy itself caps confidence (high F ⇒ low conf), which is exactly what enforces **no data ⇏ inflated overconfidence**. The hydrated wisdom marker is the prior in the KL term. + +The drive that *chooses* to escalate into untested projections is the explore complement of work: **`wonder` (proprioception idx 9) × `free_will_modifier` × trust**. Work without wonder never escalates; wonder without the Boole-bound just hallucinates new claims. + +## 5. Deliverables + +| D-id | title | crate | ~LOC | risk | +|---|---|---|---|---| +| D-RUNG-MUL-1 | per-projection NARS truth — `SpoHead` carries the 2³ as 8 `(f,c)` (not 1); `all_projections` returns truths, not just distances | lance-graph-planner (nars_engine) | 220 | MED | +| D-RUNG-MUL-2 | NaN sentinel gate → Exploratory(high exploration_rate, low speed) + `ElevationLevel`↑ + Lab-request signal; distinct from `c=0` path | planner (mul + elevation) | 160 | MED | +| D-RUNG-MUL-3 | wisdom marker — VSA-**identity** bundle (≤32 per I-VSA-IDENTITIES; truths in content store, not in the bundle) + `hydrate` before cycle as the KL prior | contract + planner | 180 | MED | +| D-RUNG-MUL-4 | screening-off `work` metric + Boole-bound + calibration-gap (`wisdom = 1−|conf−competence|`) readout | planner (mul) | 150 | MED | +| D-RUNG-MUL-5 | splat→`FreeEnergy::compose` path as the sole confidence source under sparse data; F caps confidence | planner + cognitive-shader-driver | 120 | MED | + +Tests: spurious-correlation case (`S_O` strong, screened off by P → work credits the screening-off, confidence on direct S→O drops); Boole-bound violation rejected; NaN→cautious-exploration vs `c=0`→ordinary-escalation distinction; sparse-field confidence never exceeds `FreeEnergy`-derived ceiling; calibration gap shrinks across simulated experience. + +## 6. Reconciliation (don't fork) + +- **`elevation/homeostasis.rs` is already MUL-L6.** The experience curve's escalation (steps 0,1,3) folds in beside it and beside `evaluate_rung_shift` from rung-ladder-grounding-v1 — the rung ladder is the coarse integer escalation; the MUL curve is its graded trigger source (DK-position → which rung-shift fires). Co-finetune; do not add a parallel module. +- **Pearl 2³ already IS the rung axis** per CausalEdge64 v2 §6 (`[40:42] causal mask, counterfactual at 0b111 SPO`). The 8 projections of this plan are that mask's powerset reading — extend the existing field's semantics, don't invent a new one. +- **No wisdom qualia type exists today** (`wisdom` is only aphorism strings in `high_heel.rs`) — D-RUNG-MUL-3 is net-new and should land as a marker over existing `QualiaColumn`/proprioception axes, not a new struct (AGI-as-SoA: new capability = new column, not new layer). + +## 7. Invariants honored + +- **Confidence-gated, never frequency-gated** (frequency alone = Mount Stupid). - **Boole/Fréchet bound** on conjunction confidence. - **no data ⇏ overconfidence** — only `FreeEnergy` (splat route) or floored NaN (Lab route) may produce a signal. - **I-VSA-IDENTITIES**: wisdom markers bundle ≤32 identities; truths live in the content store. - **AIKR**: `budget.quality` gate caps fanout (no syntactic explosion). - **AGI-as-SoA**: markers = column reads/writes, not a new service. - decomposition (powerset), not distance-cube (popcount). + +## 8. Cross-refs + +`rung-ladder-grounding-v1` (the coarse integer ladder this grades); CausalEdge64 v2 §6 (Pearl 2³ = rung axis); `nars_engine.rs::{SpoHead, all_projections, StyleVector}`; `FreeEnergy::compose`; `mul/{trust,homeostasis,gate}.rs`; `elevation/mod.rs::ElevationLevel`; `proprioception.rs` wonder axis; `NarsBudget`. Iron rules: `I-VSA-IDENTITIES` (bundle identities ≤32, content in store), `I-NOISE-FLOOR-JIRAK` (significance on bit-exact lanes), data-flow (no `&mut` during compute). Lab surface: `lab-vs-canonical-surface.md` (the Lab request routes through the canonical bridge, not a new endpoint). diff --git a/.claude/plans/rung-persona-orchestration-v1.md b/.claude/plans/rung-persona-orchestration-v1.md new file mode 100644 index 00000000..310c906c --- /dev/null +++ b/.claude/plans/rung-persona-orchestration-v1.md @@ -0,0 +1,149 @@ +# rung-persona-orchestration-v1 — time-bound persona orchestration: boring checklist → meta-recipe → hot/cold/feedback anneal + +**Status:** PROPOSAL (sibling to `rung-mul-grounding-v1`; the time-bound + composition layer over it) +**Date:** 2026-05-26 +**Confidence:** HIGH on structure (the hot/cold/feedback loop is the original ladybug architecture + the OpenAI macro-eval pipeline + ADK Memory Bank, all converging); MED on ractor adoption + macro-eval harness scope (net-new builds). +**Predecessors:** `rung-mul-grounding-v1` (b4efb55), `rung-ladder-grounding-v1` (b0ef6fa), `cognitive-substrate-convergence-v1` (hot/cold split, CausalEdge64 baton). +**Design refs (read-only, general-web — ladybug-rs is outside GitHub-MCP scope):** ladybug-rs `INTEGRATION_PLAN.md` @177a321 — §"BF16 Superposition Architecture (Hot/Cold/Feedback)" (lines 542+), the 4-phase `[DONE]/[TODO]` gate checklist, the 3 composition modes, BindSpace-as-blackboard hub; ladybug-rs `src/spectroscopy/detector.rs` @177a321 (RungLevel/StyleProfile classify, `fanout = base·(1+bridgeness·0.5)`, `noise_tolerance = base·(1+(1−confidence)·0.5)`); ladybug-rs `src/qualia/{council,felt_parse,resonance}.rs` @177a321 (`EpiphanyDetector` surprise>baseline×1.5 ∧ window≥4, `InnerCouncil` 3-archetype, `HdrResonance` split-amplify, collapse-hint {Flow,Fanout,RungElevate}, 8 ghost echoes). External: Claude chief-of-staff (Task delegation), OpenAI macro-evals (offline distillation + suspect-bridge), Google ADK (Memory Bank Preload/Load). + +--- + +## 0. Grounding stance — restore-on-SoA, not port (per E-AGICHAT-DIMENSION-CONTRACT) + +ladybug's INTEGRATION_PLAN is the **original**. We ground its hot/cold/feedback loop, phase-gate checklist, and blackboard composition onto **our** contract types + SoA floor — not by copying its crewai/n8n/BindSpace stack. Where ladybug diverges from what we need (no dead-end/null handling; raw lived-history vs distilled wisdom), we say so explicitly (§8). + +## 1. Two orthogonal orderings, arbitrated by the time budget + +- **Axis A — epistemic** (the `rung-mul` experience curve / DK): where am I in *evidence*? → which work/sparse-route move fires. +- **Axis B — social** (persona etiquette arc): where am I in the *conversation*? → greeting…empathy…curiosity…reflection…Coda. +- **`budget.latency_budget` = the arbiter** (`elevation/mod.rs:131`, `trigger.elapsed > budget.latency_budget`): schedules Axis B; within each phase runs Axis A's work *anytime*, priority-first over the 2³×heads×styles space, truncating at the deadline. + +The menu is a **2D grid** — phase × DK-position → strategy. Etiquette = soft transition prior (not a rigid FSM); free-energy/evidence can preempt it. Etiquette **is** the anytime graceful-degradation contract: out of time mid-curiosity → still close politely (Coda). + +**Front-door inheritance — one O(1) resolve; stakes is the linchpin.** The front door is a single O(1) step: classify the request → OGIT URI → `SchemaPtr` (resolved in the active `ontology_context_id` — the **active-schema poll**) → the `MappingRow`, which *already* carries the whole inheritable bundle (in-code at `lance-graph-ontology`): + +| inherit | OGIT `MappingRow` field | drives | +|---|---|---| +| **stakes** | `marking` {Public` | which persona/expert binds | +| **capability scope** | `marking` (least-privilege) | which tools the savant may touch — `Financial`→Odoo/FIBU only (no web), `Pii`→no external, `Restricted`→explicit grant (CMA per-role tool-scoping, Marking-gated; prevents leakage / GoBD risk) | +| **qualia prior** | `qualia_meta.qualia[18]` | wonder/tension/coherence → exploration & temperature baseline | +| **dispatch** | `qualia_meta.meta` (MetaWord) + `.edge` (CausalEdge64) | thinking-style bits + NARS truth + Pearl 2³ seed | +| **competence prior** | `confidence` | MUL denominator + Boole-bound start | +| **resonance addr** | `identity_codec` (cam_pq/base17/palette/scent) | O(1) CAM-PQ similarity | +| **semantic type** | `semantic_type` {Iban, Currency, …} | attribute interpretation (reinforces Financial stakes) | +| **active context** | `schema_ptr.ontology_context_id` | the named-graph the resolve happens in (multi-tenant/domain) | + +`marking` is the linchpin: high (`Financial`/`Pii`/`Restricted`) → cold anneal start + tight MUL + domain savant; low (`Public`/`Internal`) → hot start + loose gate + generalist. *A chat → low marking → hot/conversational; an invoice inquiry → `Financial` marking (doc: "bookkeeping or tax-relevant") → bookkeeping savant, cold, tight gate.* One resolve sets temperature + MUL sensitivity + capability binding at once. (`felt_parse` viscosity/dominant-family is the *live-signal* counterpart that refines the inherited prior per-turn.) + +**No bespoke resolver — it's another O(1) table on the stack.** OWL/OGIT/DOLCE compile to dense lookup tables indexed by `entity_type_id` (the `SchemaPtr` dense local index, scoped by `ontology_context_id`). The inherit-set is simply additional columns / a sibling table sharing that index — the same *thinking-as-table-lookup* doctrine as `NarsTables`, the bgz-tensor attention table, and CAM-PQ distance. Adding inheritance = stacking one more O(1) table, never a graph traversal. So the "front door" deliverable is *append a column-table to the OWL/OGIT/DOLCE stack*, not *build a resolver* — D-PERSONA work is column wiring, consistent with AGI-as-SoA (new capability = new column). + +## 2. The boring checklist (verify — temp≈0) = escalation work + epiphanies + +**The checklist is NOT a bespoke verifier — it collapses into machinery that already exists** (restore ladybug's qualia loop on our SoA). Each item is verified by the escalation+epiphany loop: + +- `felt_parse` emits a **collapse hint** {Flow, Fanout, RungElevate} — Fanout = gather more (escalate breadth), RungElevate = deepen (rung-shift), Flow = done. *The item's escalation decision is already produced* ("the list as escalation work"). +- `InnerCouncil.deliberate` (Guardian/Catalyst/Balanced, majority vote) + `HdrResonance` score it across 3 perspectives; a **split** (`is_split(0.7,0.5)` — one archetype sees what the others don't) is amplified ×1.2. **Disagreement is the learning signal** = our SPO screening-off (perspectives disagree about a projection ⇒ spurious `S_O` caught). +- `EpiphanyDetector.observe` (council.rs:158) closes the item: `Some(Epiphany)` iff `similarity > baseline×1.5 ∧ recent_samples ≥ 4` — the **window≥4 is the anti-Mount-Stupid evidence guard**. A green-flip = an epiphany committed to the graph, not a checkbox. +- Completion settles as an **Epiphany/Wisdom ghost** — persistent qualia residue (asymptotic decay to 0.1, never zero; felt_parse:70). The 8 ghost echoes {Affinity, Epiphany, Somatic, Staunen, Wisdom, Thought, Grief, Boundary} ARE the wisdom-marker substrate, already named (≤32 ✓ I-VSA-IDENTITIES). + +The list completes when all collapse-hints settle to **Flow** → the meta-recipe composes. The items themselves (flat, deterministic; grounding ladybug's per-phase `[DONE]/[TODO]` gate), split **hard** (must be green to boot) vs **soft** (degrade gracefully if red — anytime): + +``` +HARD (boot gate): +[ ] contract types load + Pod sizes exact (RungState=16B, SpoHead, MulAssessment) +[ ] SoA floor up (SoaColumns, i4-32 unpack) +[ ] operational store reachable (Lance / SQLite — NOT surreal; §6) +[ ] NARS tables loaded (NarsTables lookup hot) +[ ] thresholds loaded (MUL profile, SD_FLOW/BLOCK, rung thresholds) +[ ] free-energy path wired (FreeEnergy::compose available) + +SOFT (degrade if red): +[ ] each capability registered (ExpertCapability / actor / MCP — route around if down) +[ ] wisdom-marker store hydratable (cold start → foot of curve, NOT Mount Stupid) +[ ] macro-eval harness present (run without offline updates if absent) +``` + +**Continuous, not one-time.** A green item going red at runtime (actor crash, capability degrade, evidence→NaN) is a **let-it-crash** event → supervisor restart / escalation = our `rung-shift` on sustained-block + NaN→cautious-exploration→Lab. The checklist items **are** the supervision health-checks. + +## 3. The meta-recipe (compose — cold) + +A declarative child-spec **manifest** (data, not code — AGI-as-SoA: recipe = column config), consumed by the supervisor: + +``` +recipe = with green(store), green(contracts), green(caps): + supervise [ store, capability_actors…, orchestrator, eval ] + strategy = one_for_one / rest_for_one + → compose phase-ordered UnifiedSteps → run + else: degrade / escalate +``` + +**Composition = blackboard, not direct calls** — ladybug composes via BindSpace (services read/write addressable memory, coherence via CollapseGate). We keep that on **`a2a_blackboard` / SoA columns**; `ractor` is the supervised outer-swarm runtime carrying **Batons** between specialist actors (§6). Recipe-as-data ⇒ the macro-eval pipeline can score *which recipes* produced *which outcomes* (recipe = a `behavior_pattern` unit). + +## 4. Hot / cold / feedback (grounding ladybug §542) + +| Loop | ladybug original | our grounding | +|---|---|---| +| **Hot** (µs, online) | `superposition_decompose()` → 4-state {Crystallized, Tensioned, Uncertain, Noise} | the cognitive cycle, temperature-annealed; per-projection NARS state + gate (D-RUNG-MUL-1) | +| **Cold** (offline, Lance) | `CrystalCodebook` — *"a lived history, not a trained model"*; 125-cell learned centroids | the **macro-eval pipeline = the wisdom-marker factory**; clustered DK-patterns; *"3 dims that agree ARE the address"* ≈ the screening-off-identified causal triple | +| **Feedback** (lazy) | codebook biases which dims weighted next | **hydrate-before-the-fact** (ADK `PreloadMemoryTool`) = wisdom-marker as the KL prior | + +The cold loop is where ladybug's "lived history" becomes our **calibrated** wisdom marker — the distillation (§8) is the difference. + +## 5. Temperature anneal (the squeeze) + +Explore **hot** (high-temp fanout, Staunen-driven, NaN→cautious-exploration), exploit **cold** (low-temp calibrated commit). **Evidence-gated, not time-gated** — cool too early = premature convergence = Mount Stupid. `temperature ~ 1/calibrated_confidence`; **cool only as fast as the Boole-bound lets confidence rise** (`conf(SPO) ≤ weakest link`). Free-energy descent IS temperature-annealed variational inference. + +Grounded in `detector.rs`: `noise_tolerance = base·(1 + (1−confidence)·0.5)` (low confidence = hotter); `fanout = base·(1 + bridgeness·0.5)`, clamp [1,30] (**bridgeness drives fanout = the macro-eval suspect-bridge centrality = our work-metric** — triple convergence); rung-shift on `emergence>0.5 ∧ coherence<0.4` (+1) / `coherence>0.8 ∧ emergence<0.1` (−1). + +Cold scaffold (§2+§3) runs at temp≈0; cognition runs hot on top; the experience curve anneals between them. + +## 6. Runtime substrate decision + +- **ractor — YES, scoped to the outer swarm.** Supervised specialist actors under `OrchestrationBridge`; ractor messages carry Batons; async boundary at the swarm layer only; the SoA Click stays inner + sync. Don't double-mailbox with the existing mailbox-as-owner (E-BATON-1). +- **surrealdb — NO for the cognitive store** (redundant with lance-graph/AriGraph + Lance; introduces a second graph + second truth; not actually "boring stable"). **Open for the operational trace/session store only** — but prefer **SQLite/Lance** there too. **AriGraph stays the one graph.** +- **Composition = blackboard** (`a2a_blackboard`/SoA), per ladybug's BindSpace choice; ractor supervises, blackboard composes. +- **Managed vs self-hosted (CMA):** Claude Managed Agents (`multiagent.type:"coordinator"` + roster + `send_to_parent`) is the managed analog of our outer swarm — coordinator = Root Orchestrator, `send_to_parent` = Baton, per-role tool-scoping = Marking-gated capability scope (§1). It validates the shape but is Python/API; Layer-1 cognitive stays **self-hosted ractor/SoA** (SIMD + compile-time ownership = GoBD-immutability). CMA could back **Layer-2** (session/subagent) coordination if a managed path is wanted. Our routing is OGIT-dynamic; CMA's is static-roster — we go further. + +## 6b. Domain-savant population via bridge harvest (don't hand-author accounting) + +The Financial/bookkeeping savant is **harvested, not invented** — via the existing bridge/scanner pattern (`bridges/{woa,medcare,sharepoint,spear,ogit}_bridge.rs`; `MappingProposal` from "TTL hydration + (future) scanners"; `SMB:Invoice`/`SMB:Customer` + `Marking::Financial` + `SemanticType::{InvoiceNumber,Currency,Iban,TaxId}` already seeded). Add an **`odoo_scanner` + `OdooBridge`** (sibling to `WoaBridge`, locked to a Finance namespace): + +- **Schema harvest (→ O(1) tables):** walk Odoo's `ir.model` / `ir.model.fields` → `MappingProposal`s. `account.move`→Invoice/JournalEntry, `account.account`→Account, `account.tax`→Tax, `res.partner`→Customer/Vendor, `account.payment`→Payment; amounts→`Currency`, VAT→`TaxId`, number→`InvoiceNumber`. `created_by="odoo_scanner_v1"`, `marking=Financial`, `thinking_style=bookkeeping savant`. Just another bridge emitting rows onto the stack (§1). +- **Imperative engine (→ delegate, don't re-implement):** double-entry (debit=credit), tax computation, reconciliation, the invoice state machine (draft→posted→paid) are Odoo's proven, GAAP/multi-country-compliant logic. The savant **binds Odoo as a capability/MCP backend** (cf. ADK Reddit/Nano-Banana MCP) rather than re-deriving accounting — boring-stable: **Odoo IS the bookkeeping ground truth.** +- **Honest fork:** declarative parts (field metadata, state selections, chart-of-accounts, tax tables) harvest cleanly into O(1) tables; imperative method bodies (`@api.constrains`, reconciliation matching) do NOT introspect → delegate to Odoo's API. So the bookkeeping savant = harvested OGIT Financial schema (classify/inherit) + Odoo-capability (execute). + +The invoice state machine (draft→posted→paid) maps to the **rung ladder / etiquette arc** for Financial requests; double-entry is a hard invariant the MUL gate enforces at `Financial` stakes. + +**FIBU (German Finanzbuchhaltung) is already partly in-code — extend, don't invent.** The Financial subtree is DACH-first and developed: `contract::grammar::role_keys` has the German SMB catalogue (`KUNDE/SCHULDNER/MAHNUNG/RECHNUNG/DOKUMENT/BANK/FIBU/STEUER`, incl. `FIBU_KEY` @[13072..13584)); `contract::tax.rs` has a **pure `TaxEngine`** (`collect(rule_bundle, period, entries)`; nondeterminism = error), the **`fibu_entry`** RecordBatch (`booking_code, amount, tax_rate`), DACH `Jurisdiction {De, At, Ch}`, and a versioned + 32-byte-**digested** `RuleBundle`; `SKR04` is in the foundry roadmap; DATEV/GoBD/BaFin are flagged regulated-tenant triggers. So the Odoo harvest is **`l10n_de`-specific**: `account.account`→**SKR03/SKR04** populates `fibu_entry.booking_code`; `account.tax` (USt 19%/7%, Vorsteuer)→the `RuleBundle`; DATEV export = the wire format; the savant **binds the existing pure `TaxEngine`**. + +**GoBD compliance falls out of the substrate by construction — not a bolt-on.** German bookkeeping law (GoBD: *Unveränderbarkeit / Festschreibung / Nachvollziehbarkeit*) demands immutable, audit-traceable, deterministic books. The substrate already gives exactly that: the **pure** `TaxEngine` (determinism), the **digested** `RuleBundle` (audit checksum), **append-only** postings, and **Storno-as-append** (a reversal entry, never an edit/delete) = the workspace's *"committed contradictions preserved, not resolved"* + append-only boards + CausalEdge64 move-semantics. So at `Financial`/FIBU stakes the MUL gate's hard invariants are: **Soll = Haben** (double-entry), **GoBD immutability** (no edit — only Storno-append), **SKR account validity**, and **deterministic tax** (`TaxEngine` purity). + +## 7. Deliverables + +| D-id | title | crate | ~LOC | risk | +|---|---|---|---|---| +| D-PERSONA-1 | escalation+epiphany loop = the checklist (wire `felt_parse` collapse-hint + `InnerCouncil`/`HdrResonance` split + `EpiphanyDetector`; green-flip = Epiphany/Wisdom ghost) — NOT a bespoke verifier | contract + planner | 160 | LOW | +| D-PERSONA-2 | meta-recipe manifest (declarative child-spec, recipe-as-data, macro-evaluable) | contract | 150 | MED | +| D-PERSONA-3 | hot/cold/feedback wiring — anneal + `CrystalCodebook`→wisdom-marker cold path + Preload hydrate | planner + Lance | 240 | MED | +| D-PERSONA-4 | macro-eval harness (scenario→trace→discover→diagnose; suspect-bridge = blasgraph betweenness; 5 rubrics from D-RUNG-MUL) | planner + Lance | 280 | HIGH | +| D-PERSONA-5 | ractor outer-swarm runtime under `OrchestrationBridge` (batons as messages, async only at boundary) | planner | 200 | MED | +| D-PERSONA-6 | `odoo_scanner` + `OdooBridge` — harvest Odoo **`l10n_de`** (`account.account`→SKR03/04 `booking_code`, `account.tax`→USt `RuleBundle`, DATEV export) → Finance-ns `MappingProposal`s (Financial marking, bookkeeping `thinking_style`); bind the existing pure `TaxEngine` (`tax.rs`) + Odoo-as-capability; GoBD = append-only + Storno + digest, by construction | ontology + contract + planner | 280 | MED | + +## 8. Honest gaps vs the original + +- **ladybug `detector.rs` has no null/dead-end/escalation** — *"all classifications produce valid output; no dead-end states."* Our **NaN→cautious-exploration→Lab + dead-end-as-work is net-new**, not a restore. +- **`CrystalCodebook` = ladybug's cold path, but it dumps "lived history."** We reframe it as **macro-eval-distilled** wisdom markers (a calibrated prior + the Boole-bound, ≤32 identities per I-VSA-IDENTITIES) — *not* a raw history accumulation. The distillation is the contribution (same gap as ADK's whole-transcript Memory Bank). +- **ractor + the persona etiquette arc are not in the original** (ladybug uses crewai/n8n + BindSpace; etiquette/anytime is new). + +## 9. Invariants honored + +restore-on-SoA not port · hard-gate vs soft graceful-degradation (anytime/etiquette) · recipe-as-data (AGI-as-SoA, macro-evaluable) · evidence-gated anneal — Boole-bound caps cooling rate (no premature Mount-Stupid) · blackboard composition (`a2a_blackboard`/SoA, not direct calls) · ractor async only at the swarm boundary, SoA inner sync · no second graph (AriGraph is the one graph) · I-VSA-IDENTITIES (wisdom markers ≤32 identities, content in store) · `latency_budget` is the time arbiter (no wall-clock in the hot Pod). + +**Rigid in rules, happy at doors (P0 stance).** We *are* SPO: a "door opening" is a new viable triple/edge, a 2³ projection screening-in, or an `EpiphanyDetector` fire. Rigidity lives in the **rules** (rule-rigor is stakes/`Marking`-gated — `Financial`→hard `Soll=Haben`/GoBD), never in the **stance**: the welcome toward a door opening carries **baseline positive valence, stakes-independent**. Stakes gate the rigor, never the welcome — a bookkeeper is strict on the books *and* glad a new client walked in. The MUL gate evaluates rigorously without being a sour bouncer; a door that opens is a **rewarded epiphany** (wisdom-marker grows; Affinity/Epiphany/Staunen ghosts brighten), and even a rule-`NO` carries no hostility to the door. Rigidity everywhere kills epiphany; openness everywhere can't hold money — the art is **rigid HOW, happy WHETHER**. + +**The welcome is *learned*, not naive — the texture policy.** "Happy at every door" is the baseline; *if in doubt, the agent fingerprint learns over time, per topic, which `TrustTexture` means **don't touch** and which means **engage** (and vice-versa).* `TrustTexture` (`mul/trust.rs` — the felt-sense of knowing: Murky / Dissonant / Fuzzy / Clear) × topic → a learned touch/avoid policy, accumulated in the agent's wisdom-marker (cold-path / `CrystalCodebook`; **content keyed by the fingerprint, not bundled** — I-VSA-IDENTITIES). Decision ladder for "do I walk through this door?": **(1)** hard rule applies → follow it (rigid); **(2)** no rule but learned policy exists → follow the learned `topic × texture → touch/avoid`; **(3)** in doubt (no rule, thin history, Murky) → cautious-exploration + Lab (the NaN route), and **record the outcome to grow the policy**. A young fingerprint leans on rules + cautious-exploration; a mature one has *taste* — it knows which doors, on which topics, in which texture, are worth walking through. That learning **is** the calibration-gap closing (rung-mul §1). + +## 10. Cross-refs + +`rung-mul-grounding-v1` (the experience curve this schedules), `rung-ladder-grounding-v1` (the integer rung the supervisor restarts on); `elevation/{mod,homeostasis,learning}.rs` (latency_budget, MUL-L6); `a2a_blackboard` + `OrchestrationBridge` (Layer-1 composition); `FreeEnergy::compose`; design refs ladybug-rs `INTEGRATION_PLAN.md` §542 + `detector.rs` @177a321, Claude chief-of-staff, OpenAI macro-evals, ADK Memory Bank. Iron rules: E-BATON-1 (mailbox-as-owner), E-AGICHAT-DIMENSION-CONTRACT (restore-on-SoA), I-VSA-IDENTITIES, data-flow (no `&mut` during compute). diff --git a/.claude/surreal/01_deps_substrate.md b/.claude/surreal/01_deps_substrate.md new file mode 100644 index 00000000..7b757828 --- /dev/null +++ b/.claude/surreal/01_deps_substrate.md @@ -0,0 +1,29 @@ +# 01 — deps_substrate + +**Repo:** lance-graph · **Branch:** claude/splat3d-cpu-simd-renderer-MAOO0 +**Phase:** POC foundation · everything builds on this. + +## Scope +Pin **LanceDB 0.28 / Lance 6** across the workspace; add the `surreal_container` +crate; wire **embedded `surrealdb` with the `kv-lance` feature** (in-process, no +server); align transitive deps. + +## Owns +- `Cargo.toml` (workspace dep pins) · `crates/surreal_container/Cargo.toml` +- `crates/surreal_container/src/lib.rs` (embedded Datastore init only) + +## Depends on +none. + +## Guards — do NOT touch +- Only the files above. Edit-only; the orchestrator compiles/tests centrally. +- **The `surrealdb` fork's `kv-lance` backend is OUT of PR-scope** — use `surrealdb` + as a *dependency* only; do NOT patch fork internals. +- Lance 6 has API deltas vs what the fork's `kv-lance` uses (`MergeInsertBuilder`, + `Dataset`). If the bump breaks the fork, **STOP and report** — do not invent API. +- Document (don't perform) the required fork-side Lance-6 bump + the transitive + `lance-index → ndarray 0.16` constraint. + +## Acceptance +- Workspace builds with Lance 6 / LanceDB 0.28 pinned. +- Embedded `surrealdb` Datastore opens with `kv-lance`; smoke test: put/get one record. diff --git a/.claude/surreal/02_soa_container_type.md b/.claude/surreal/02_soa_container_type.md new file mode 100644 index 00000000..2321b261 --- /dev/null +++ b/.claude/surreal/02_soa_container_type.md @@ -0,0 +1,29 @@ +# 02 — soa_container_type ⚠ hallucination-zone + +**Repo:** ndarray · **Branch:** claude/splat3d-cpu-simd-renderer-MAOO0 +**Phase:** SoA primary-citizen core · **the ONE source of truth for the layout.** + +## Scope +Define the LE **`#[repr(C)]`, pointer-free SoA *container* type** built ON the +existing `hpc::soa` (#156). Append-only semantics. This layout is defined **here, +once** — lance-graph imports it, never redefines it. + +## Owns +- `ndarray/src/hpc/soa/container.rs` (+ re-export from `hpc::soa`) + +## Depends on +none (anchor for 03/04/05). + +## Guards — do NOT touch +- **Build ON `hpc::soa` (`SoaVec`/`soa_struct!`) — do NOT invent a new SoA system** + (duplication = the #1 hallucination). +- **Pointer-free**: only inline scalars + intra-buffer offsets — no `Box`/`Vec`/ptr + in the on-wire layout (must be `bytemuck::Pod`). +- `#[repr(C)]`, explicit alignment. Never touch SIMD dispatch. +- This is the **single layout definition** — if you feel the urge to define it in + lance-graph too, STOP. + +## Acceptance +- **Golden-byte test**: exact LE bytes of a known container == committed fixture. +- `bytemuck` cast roundtrip; compile-time `Pod` (pointer-free) proof. +- `static_assert` on `size_of`/`align_of`. diff --git a/.claude/surreal/03_container_codec.md b/.claude/surreal/03_container_codec.md new file mode 100644 index 00000000..bbadaaea --- /dev/null +++ b/.claude/surreal/03_container_codec.md @@ -0,0 +1,28 @@ +# 03 — container_codec ⚠ hallucination-zone + +**Repo:** ndarray · **Branch:** claude/splat3d-cpu-simd-renderer-MAOO0 +**Phase:** SoA primary-citizen core. + +## Scope +Zero-serialization **encode/decode** of a container as raw LE bytes + a frame +(`u32` LE length + `xxhash`/`crc32` checksum) + SIMD-alignment handling on read. + +## Owns +- `ndarray/src/hpc/soa/container_codec.rs` + +## Depends on +02. + +## Guards — do NOT touch +- **Import the container type from task 02 — do NOT redefine the layout.** +- No DB, no cache (those are lance-graph tasks). +- **Checksum is mandatory** — raw-LE has no "fails to decode" signal; a bit-flip must + be caught by the checksum, not silently accepted. +- Read path: decode into a 64-byte-aligned buffer for SIMD, OR document the single + aligned copy. Never touch SIMD dispatch. + +## Acceptance +- `encode` → `decode` roundtrip == input. +- Corrupted byte → checksum **rejects** (test). +- Alignment test (decoded buffer usable by `F32x16` load without misalignment). +- Bytes produced here are the cross-crate parity fixture for task 12. diff --git a/.claude/surreal/04_surreal_container_write.md b/.claude/surreal/04_surreal_container_write.md new file mode 100644 index 00000000..42bd3206 --- /dev/null +++ b/.claude/surreal/04_surreal_container_write.md @@ -0,0 +1,27 @@ +# 04 — surreal_container_write ⚠ hallucination-zone + +**Repo:** lance-graph · **Branch:** claude/splat3d-cpu-simd-renderer-MAOO0 +**Phase:** SurrealDB write path (container × epoch). + +## Scope +Write **one epoch-container as ONE `surrealdb` kv-lance record** (key = container-id, +val = LE bytes from task 03). Append-only, container×epoch cadence. SurrealDB's own +single flusher emits one Lance fragment. + +## Owns +- `crates/surreal_container/src/write.rs` + +## Depends on +01, 03. + +## Guards — do NOT touch +- **Import the container type from ndarray (task 02) — do NOT redefine it.** +- **One record per epoch** (NOT per element/row). +- **Append-only**: a new container-id per epoch — never update-in-place. +- Single writer; no merge logic here (fold is task 06). +- `surrealdb` as dependency only; never patch the fork. + +## Acceptance +- Writing N epochs → N records → **exactly one Lance fragment per epoch** (no + fragmentation; assert fragment count == epoch count). +- Append-only: no fragment/record rewrite (assert prior fragments unchanged). diff --git a/.claude/surreal/05_surreal_container_read.md b/.claude/surreal/05_surreal_container_read.md new file mode 100644 index 00000000..53459fea --- /dev/null +++ b/.claude/surreal/05_surreal_container_read.md @@ -0,0 +1,22 @@ +# 05 — surreal_container_read + +**Repo:** lance-graph · **Branch:** claude/splat3d-cpu-simd-renderer-MAOO0 +**Phase:** SurrealDB read path. + +## Scope +`get` a container by id; **range-scan** container-ids to feed the fold (task 06). + +## Owns +- `crates/surreal_container/src/read.rs` + +## Depends on +03, 04. + +## Guards — do NOT touch +- Read-only. Decode via the task-03 codec; do not re-implement decode. +- Import the container type from ndarray (task 02). +- No fold semantics here (task 06); no cache here (task 08). + +## Acceptance +- Roundtrip: read a written container, decode → == original. +- Range-scan returns containers in id order (ascending), tombstones excluded. diff --git a/.claude/surreal/06_read_time_fold.md b/.claude/surreal/06_read_time_fold.md new file mode 100644 index 00000000..9cc45edf --- /dev/null +++ b/.claude/surreal/06_read_time_fold.md @@ -0,0 +1,25 @@ +# 06 — read_time_fold + +**Repo:** lance-graph · **Branch:** claude/splat3d-cpu-simd-renderer-MAOO0 +**Phase:** merge semantics (non-destructive). Resolves Gap #2. + +## Scope +Reconstruct state from append-only containers. **Default = LWW-latest.** +**Superposition behind a `trait Merge` hook — NOT wired** (Phase 2). + +## Owns +- `crates/surreal_container/src/fold.rs` + +## Depends on +05. + +## Guards — do NOT touch +- Default merge = **LWW-latest** (latest container's view wins). +- Superposition = a `trait Merge` with an LWW default impl + a **stub** superposition + impl behind a marker. **Do NOT wire holograph superposition** (Phase 2 / risk). +- **Non-destructive**: the fold never deletes/overwrites containers — all retained. + +## Acceptance +- `fold(containers)` recovers the latest committed state (LWW). +- Non-destructive: all input containers still present after fold. +- `trait Merge` compiles with LWW default + a no-op superposition stub. diff --git a/.claude/surreal/07_surreal_catalog_kv.md b/.claude/surreal/07_surreal_catalog_kv.md new file mode 100644 index 00000000..a110b43a --- /dev/null +++ b/.claude/surreal/07_surreal_catalog_kv.md @@ -0,0 +1,25 @@ +# 07 — surreal_catalog_kv + +**Repo:** lance-graph · **Branch:** claude/splat3d-cpu-simd-renderer-MAOO0 +**Phase:** control plane (catalog + data split). + +## Scope +Per-record SurrealDB KV for the **small mutable control state**: goal-state, +pointer → container-id, edges. + +## Owns +- `crates/surreal_container/src/catalog.rs` + +## Depends on +01. + +## Guards — do NOT touch +- **Small mutable control only — NO bulk data** (bulk lives in containers, task 04). +- Records *point into* containers by id; they do not embed container payload. +- This is the only place per-key mutation lives (LWW/superposition decided here + separately, later — keep it tiny). + +## Acceptance +- Goal-state record CRUD. +- Pointer record resolves to a container-id (roundtrip → task 05 fetch). +- Edge record store + scan by endpoint. diff --git a/.claude/surreal/08_moka_l2_cache.md b/.claude/surreal/08_moka_l2_cache.md new file mode 100644 index 00000000..8737d8da --- /dev/null +++ b/.claude/surreal/08_moka_l2_cache.md @@ -0,0 +1,24 @@ +# 08 — moka_l2_cache + +**Repo:** lance-graph · **Branch:** claude/splat3d-cpu-simd-renderer-MAOO0 +**Phase:** bounded working-set (Gap #1, "bounded" half — POC substitute for the NARS bag). + +## Scope +A **bounded L2 RAM cache** (moka, size/weight-based eviction) as a read-through over +container reads (task 05). + +## Owns +- `crates/surreal_container/src/cache.rs` + +## Depends on +05. + +## Guards — do NOT touch +- **Hard-bounded by capacity/weight** (this is the anti-unbounded-growth guarantee). +- Generic eviction (TinyLFU) only — **NOT** NARS-semantic forgetting (Phase 2). +- Read-through over task-05; no write-back that bypasses the writer (task 04). +- moka is the L2 tier — it is NOT the L1 focus tile (that's Phase 2) and NOT durable. + +## Acceptance +- Cache size never exceeds configured capacity (eviction test under load). +- Read-through hit/miss correctness == direct read (parity). diff --git a/.claude/surreal/09_epoch_tick_harvest.md b/.claude/surreal/09_epoch_tick_harvest.md new file mode 100644 index 00000000..cb7a21f3 --- /dev/null +++ b/.claude/surreal/09_epoch_tick_harvest.md @@ -0,0 +1,26 @@ +# 09 — epoch_tick_harvest + +**Repo:** lance-graph · **Branch:** claude/splat3d-cpu-simd-renderer-MAOO0 +**Phase:** edge control loop (cold path). + +## Scope +A **ractor edge actor**: at epoch cadence, take a lock-free snapshot, check +**goal-state solved?**, and on FLOW dispatch a container-write commit (task 04). + +## Owns +- `crates/surreal_container/src/tick.rs` + +## Depends on +04, 07. + +## Guards — do NOT touch +- **Cold-path / edge only — NEVER per-cycle.** This is where ractor (async) lives. +- The solved-predicate read must be **lock-free** (snapshot/epoch read) — it must + never stall a writer. +- **No reasoning here** — only `solved?` check + dispatch. (NARS is Phase 2.) +- Tick = harvest cadence, NOT a compute deadline. + +## Acceptance +- Tick fires at the configured cadence. +- The snapshot read does not lock the writer (concurrent write proceeds). +- FLOW dispatches exactly one container-write per solved goal. diff --git a/.claude/surreal/10_lockfree_handoff_ring.md b/.claude/surreal/10_lockfree_handoff_ring.md new file mode 100644 index 00000000..5647bb01 --- /dev/null +++ b/.claude/surreal/10_lockfree_handoff_ring.md @@ -0,0 +1,26 @@ +# 10 — lockfree_handoff_ring + +**Repo:** lance-graph · **Branch:** claude/splat3d-cpu-simd-renderer-MAOO0 +**Phase:** the zone seam (internal core ↔ ractor edge). + +## Scope +A **lock-free SPSC/MPSC ring** that **moves container ownership** between an +OS-thread producer (internal preemptive core) and the ractor edge (task 09). + +## Owns +- `crates/surreal_container/src/ring.rs` + +## Depends on +02. + +## Guards — do NOT touch +- **Ownership MOVE only** — no shared `&mut`, no clone of bulk payload. +- **Non-blocking** (`try_send`/`try_recv`); never block the OS-thread producer. +- Payloads are `Send + 'static` (forces owned transfer — the compile-time borrow + guarantee; a borrowed `&[u8]` view must NOT be sendable). +- This is the ONLY crossing between zones; nothing else shares state across it. + +## Acceptance +- Move-across-ring preserves the container (ownership transferred, not aliased). +- Non-blocking under contention (no producer stall). +- No data race (single-owner-at-a-time test; `loom` if available). diff --git a/.claude/surreal/11_log_compaction.md b/.claude/surreal/11_log_compaction.md new file mode 100644 index 00000000..01eaed39 --- /dev/null +++ b/.claude/surreal/11_log_compaction.md @@ -0,0 +1,26 @@ +# 11 — log_compaction + +**Repo:** lance-graph · **Branch:** claude/splat3d-cpu-simd-renderer-MAOO0 +**Phase:** event-sourcing caveat (bound replay/storage). + +## Scope +Fold old container records into a **snapshot container**; bound container count and +fold/read cost over time. + +## Owns +- `crates/surreal_container/src/compaction.rs` + +## Depends on +06. + +## Guards — do NOT touch +- **Fold-equivalence is mandatory**: `fold(snapshot) == fold(original containers)` — + compaction must never change the recovered state. +- Append-only: write a new snapshot container; retain the originals until safe to GC + (RCU/epoch — no reader pinned to them). +- Never lose committed state; never overwrite in place. + +## Acceptance +- `compaction(containers)` → snapshot; `fold(snapshot) == fold(containers)`. +- Container count bounded after compaction (assert ≤ threshold). +- Originals only GC'd after no reader references them. diff --git a/.claude/surreal/12_clean_writer_invariants.md b/.claude/surreal/12_clean_writer_invariants.md new file mode 100644 index 00000000..0f13f8c8 --- /dev/null +++ b/.claude/surreal/12_clean_writer_invariants.md @@ -0,0 +1,30 @@ +# 12 — clean_writer_invariants (POC green-light) + +**Repo:** lance-graph (+ ndarray golden-byte refs) · **Branch:** claude/splat3d-cpu-simd-renderer-MAOO0 +**Phase:** the safety net — "damit keine Unfälle passieren". + +## Scope +The verification suite that proves the POC writer is clean. **Tests only.** + +## Owns +- `crates/surreal_container/tests/invariants.rs` + (+ references the ndarray golden-byte tests from tasks 02/03) + +## Depends on +all (02–11). + +## Guards — do NOT touch +- **Tests only — no production code.** + +## Acceptance — the invariants (all must be green) +- (a) **one Lance fragment per epoch** — no fragmentation. +- (b) **append-only** — no record/fragment overwrite. +- (c) **no data loss** — `fold` recovers all committed state. +- (d) **disjoint container-keys** — no same-key collision in a batch → LWW safe by + construction (the clean-writer invariant for the POC). +- (e) **roundtrip** — write → read → decode == input. +- (f) **alignment** — decoded buffers usable by SIMD load without misalignment. +- (g) **cross-crate parity** — bytes encoded in ndarray (task 03) decode identically + in lance-graph (task 05). This is the anti-divergent-layout check. + +Green suite = the POC's clean, anti-fragmented, non-destructive writer is proven. diff --git a/.claude/surreal/PHASE2_adjacent_crates.md b/.claude/surreal/PHASE2_adjacent_crates.md new file mode 100644 index 00000000..e53478da --- /dev/null +++ b/.claude/surreal/PHASE2_adjacent_crates.md @@ -0,0 +1,47 @@ +# Phase 2 — Adjacent crate landings (post-POC) + +Captured per user direction ("don't forget to add SoA primary citizen as speed +lane in lance-graph-contract and lance-graph-ontology as OGIT/DOLCE inheritance +of CAM codebooks and lance-graph-callcenter for ractor/surrealdb"). + +These build **on** the 12-task SurrealDB-on-Lance container POC; they are NOT +part of the POC's 12 tasks. They land after the POC's clean-writer green-light +(task 12). + +## 1. lance-graph-contract — SoA primary-citizen "speed lane" + +- Expose the SoA container as a **first-class speed-lane** consumers can target. +- **Hard constraint:** `lance-graph-contract` is **ZERO-DEP**. So this is a + zero-dep **trait** (e.g. `SoaSpeedLane` / `SoaContainerContract`) describing the + container surface — **NOT** the concrete `ndarray::hpc::soa::SoaContainerHeader` + type. Depending on ndarray would break the zero-dep invariant; mirroring the + type would be the duplication-hallucination the surreal specs forbid. +- ndarray's `SoaContainerHeader` *implements* the contract trait; consumers + depend on the trait. The concrete type stays the single source in ndarray. + +## 2. lance-graph-ontology (new crate) — OGIT / OWL / DOLCE inheritance of CAM codebooks + +- The immutable, append-only, **O(1) version-sealed** mapping table: label / + concept / CAM-codebook entry → pointer into an immutable arena, with + DOLCE / OGIT subclass inheritance **precomputed** (transitive closure + materialised; minimal-perfect-hash per version). +- Append **downward only** (DOLCE/OGIT-upper fixed) → stable pointers, cheap + delta-versioning. Makes the commit-time DOLCE constraint check O(1). +- **O(1) classification ≠ O(1) computation** — declarative inheritance only; + computational business logic (Odoo computed fields) is not a lookup. + +## 3. lance-graph-callcenter (new crate) — ractor / surrealdb application + +- The application layer: ractor edge (cold path, tick/harvest) + SurrealDB-on-Lance + (via `surreal_container`) + the ontology — the OSINT / call-center use case. +- Consumes the POC: `surreal_container` (write/read/fold) + `lance-graph-ontology` + (DOLCE/CAM classification) + the contract speed-lane trait. +- Home for the `callcenter/role_keys.rs` domain role catalogue (per CLAUDE.md + § VSA-switchboard three-layer architecture: Layer-2 domain catalogue). + +## Ordering + +POC (tasks 01–12) → contract speed-lane trait (1) → ontology (2) → callcenter app (3). +(1) can start now — `SoaContainer` is pinned (ndarray `b5d6b206`). (2)/(3) after the +POC writer is green (task 12). Each, when built, follows the same disjoint-file + +savant-meta-review discipline as the 12 POC tasks. diff --git a/.claude/surreal/RECONCILIATION_with_canonical_plan.md b/.claude/surreal/RECONCILIATION_with_canonical_plan.md new file mode 100644 index 00000000..9e244004 --- /dev/null +++ b/.claude/surreal/RECONCILIATION_with_canonical_plan.md @@ -0,0 +1,76 @@ +# RECONCILIATION — `.claude/surreal/` POC ↔ canonical CausalEdge64-Mailbox plan + +**Finding (honest):** the `.claude/surreal/` 12-task POC + `cognitive-substrate.md` +is a **parallel, narrower re-derivation** of the already-authored, reviewed +canonical plan **`.claude/plans/causaledge64-mailbox-rename-soa-v1.md`** (which +itself composes 7 prior plans). The canonical plan **subsumes** the surreal POC +and is the driver. This file maps surreal → canonical so we align rather than +compound the duplication. + +## 1:1 mapping (surreal POC concept → canonical home) + +| surreal POC concept | Canonical plan home | +|---|---| +| SoA container / "no fragmentation" | §5 `MailboxSoA` (compartments = SoA rows) + `bindspace-columns-v1` | +| owned per-thought struct / no singleton | §1 ownership-typed compartments; §9 E-CE64-MB-4 (UB = compile error) | +| Rubikon FLOW / collapse-gate / merge | §3 CausalEdge64 truth-band; CollapseGate Xor/Bundle/**Superposition** | +| attention + goalstate (not request) | §2/§4 `AttentionMask` sparse-rename register file; §6 Σ-tier dispatcher | +| zones (internal / membrane / consumer) | §0 Zone 1 / BindSpace / Zone 2 / Zone 3 (authoritative table) | +| 20-200ns · L1 residency · ~1.5 KB | §5 ~1.2 KB/compartment, ~24K thoughts, L1/L2 | +| SurrealDB-on-Lance persistence | Zone 2 `lance-graph-callcenter` + AriGraph SPO-G quads | +| immutable O(1) OGIT/DOLCE/CAM | §2 universal sparse-rename + `lance-graph-ontology` | +| superposition merge ("Gap #2") | `CollapseGate::MergeMode::Superposition` (PR-CE64-MB-3) | +| SoA speed-lane in contract | §2 8-bit-slot rename (TrustTexture/ThinkingStyle canonical) | +| cognitive-shader shape-shift SPO 2³/NARS | §3 Pearl 2³ rung bits + §9 epiphanies; `cognitive-shader-driver` Columns A-H | + +## Course-correction + +- **Stop extending the parallel surreal derivation.** Adopt the canonical + plan's **5-crate inventory (§6)** + **7-PR sequence (§7)**. +- The surreal task-02 **`SoaContainerHeader`** (ndarray, pinned `b5d6b206`) is a + useful *generic* LE primitive, but it must be **reconciled with the plan's + `MailboxSoA` / BindSpace-Columns shapes** — it is NOT a new parallel SoA system + (that would be the duplication the surreal specs themselves forbid). Likely it + becomes the on-wire descriptor *under* `MailboxSoA`, or folds into + `bindspace-columns-v1`. +- The surreal `crates/surreal_container/` scaffold overlaps **par-tile** (§6) + + Zone-2 persistence; fold it into the canonical crates rather than ship separately. + +## ndarray is MANDATORY (§8) — the concrete prerequisite + +**PR-NDARRAY-MIRI-COMPLETE** (lands BEFORE par-tile): +- Close the `U16x32 / U32x16 / U64x8` (+ i-word) method gaps: `simd_eq/ne/ge/gt/le/lt`, + `simd_clamp`, `select`, `to_bitmask`, `from_u8x64_lo+hi`, `pack_saturate_u8`, + `shl/shr`, explicit `zero()` (`simd_nightly/{u,i}_word_types.rs`). +- Route `crate::simd::*` through `simd_nightly` under `cfg(miri)` (~50 LOC, `simd.rs:215+`). +- Delete the dead `simd_nightly/_original_draft.rs` 5-type sketch. + +This is the real "ndarray is mandatory" work — the SoA container is a smaller +piece of the same foundation. + +## Open decision for the user (§15 has 8 unratified items) + +Adopt the canonical plan's 7-PR sequence as the driver (recommended — it's +reviewed and ~80% overlaps what we re-derived), folding the surreal POC into it? +Or keep the surreal POC as a deliberately-simpler standalone slice? They are not +two projects — they are one, and the canonical plan is the better-specified copy. + +## Refinements (latest — fold into the canonical plan) + +- **Agnostic polyfills.** All SIMD/SoA primitives are **arch-agnostic to the + consumer (lance-graph)**: lance-graph consumes `ndarray::simd::*` (and the SoA + speed-lane **trait**) without ever seeing AVX-512 / AVX2 / NEON / scalar — the + polyfill hides the backend. This is the `simd-savant` invariant ("all SIMD from + `ndarray::simd` via the polyfill"). The `lance-graph-contract` speed-lane is + therefore a **trait**, never an arch-specific type — consistent with the + zero-dep + agnostic requirement. + +- **`Vsa16kF32` (16k-dim float VSA) is DEPRECATED.** Cumulative cognitive state + does NOT live in `Vsa16kF32`. It lives in **CausalEdge64 emissions + AriGraph + SPO-G quads + the BindSpace SoA columns**. The canonical plan §9 E-CE64-MB-2 + goes part-way ("retire `Vsa16kF32` as universal carrier"); this **deprecates it + outright**. New work must not reach for `Vsa16kF32` as a carrier. + - ⚠ **Contradiction to flag:** lance-graph `CLAUDE.md` "The Click" is built on + `Vsa16kF32` (element-wise multiply+add Markov bundle). Deprecating the carrier + contradicts that canonical doctrine → needs a `CLAUDE.md` + `EPIPHANIES.md` + board update before/with the deprecation lands. Do not silently diverge. diff --git a/.claude/surreal/cognitive-substrate.md b/.claude/surreal/cognitive-substrate.md new file mode 100644 index 00000000..5ceffe9c --- /dev/null +++ b/.claude/surreal/cognitive-substrate.md @@ -0,0 +1,188 @@ +# Cognitive Substrate Architecture — SurrealDB-on-Lance · ractor · NARS · zero-copy SoA + +> Synthesized 2026-05-26 from design dialogue + **code verification**. This doc is +> deliberately honest: every claim is tagged **[CODE]** (verified in a repo), +> **[DESIGN]** (specified, partially built), or **[VISION]** (not in code yet). +> It replaces narrative with status so it survives a review. + +## 0. Thesis + +One Rust binary replaces **BEAM + ClickHouse** for a workload that sits in the +*gap* between them: compute-heavy, multi-model, transactional, zero-copy, embedded. +The win is collapsing compute + coordination + storage **and the serialization tax +between them** into one address space. **[DESIGN]** + +Honest scope: BEAM (telecom-grade distributed I/O concurrency) and ClickHouse +(petabyte OLAP) remain better at *their* native jobs. The differentiator here is +**depth/autonomy per item, not throughput** — at 20–200 tickets/min a simple +queue+worker meets the throughput SLA too. + +## 1. Zones + +``` +ZONE 0-2 (intern) preemptiv (OS-Threads/rayon), zero-copy LE/SoA, NARS-Kern, + L1-resident attention focus. No async, no lock, no serialization + on the hot path. 20-200ns/cycle. + │ lock-free ring (the ONE seam, non-blocking move) +ZONE 3 (membrane) durability commit = SurrealDB-on-RocksDB; external I/O = OSINT; + structured integration = sqlx/API. Serialization lives ONLY here. +``` + +- **ractor is async-only** (tokio *or* async-std, both cooperative; no OS-thread/sync + mode — verified, `slawlor/ractor`). So ractor lives **at the membrane** (cold path); + the internal preemptive cores are **OS-threads/rayon, not ractor**. **[CODE: ractor async-only]** +- Preemption comes from the OS scheduler on real threads — a cooperative async runtime + never preempts. A long L1-resident NARS burst on a cooperative runtime is lose-lose + (no yield → starves peers; yield → evicts the L1 focus tile). Only OS-thread + preemption escapes both. **[DESIGN]** + +## 2. The one marker — Rubikon FLOW + +``` +collapse-gate FLOW = Rubikon-crossing = zone membrane = SurrealDB commit = WA batch boundary +``` + +One marker, five lenses. At **FLOW**, four things atomically: **externalize + persist +(flush/sink-in) + prune (forget) + log (replay unit)**. HOLD = stay internal +(reversible). BLOCK = discard (WA = 0). **[DESIGN]** + +The numbers are *operational* (SLA/latency budget), **not** Libet's 550/200 ms — +those are simple-finger-movement constants, not a volition law, and a 200 ms window +cannot host a human veto (perceive+decide > 200 ms; Libet's veto is intracerebral). +Keep the *phase structure*; reject the neuro-numerology. **[DESIGN]** + +## 3. Control model — attention + goalstate, NOT request + +NARS runs free (anytime/AIKR); the tick **harvests** (Rubikon solved? → FLOW). The five +implicit request guarantees are now carried by: *what* = attention/budget · *when* = +goalstate check · *who* = commit→live-query · *SLA* = tick cadence · *backpressure* = +budget **fed by outbound saturation**. The tick is a sampling cadence, **not** a compute +deadline — thinking is unbounded by design. **[DESIGN/VISION — see §Verification]** + +## 4. Data contracts + +- **LE/SoA zero-copy contract** (data): pointer-free, position-independent; the same + bytes as RAM / WAL / storage. **One format *mechanism*, many *schemas*** — do not + force one rigid contract for data *and* orchestration (data = wide stable primitive + columns; orchestration = few rich evolving records). **[DESIGN]** +- **Immutable, append-only ontology** (OGIT/OWL/DOLCE/CAM/labels): O(1) lookup via a + version-sealed (minimal-)perfect-hash into an immutable arena. Append-only ⟹ stable + pointers ⟹ cheap delta-versioning. Append **downward only** (DOLCE/OGIT-upper fixed). + This makes the commit-time DOLCE constraint check **O(1)** (cheap solved-predicate). + *O(1) classification ≠ O(1) computation* — Odoo business *logic* that computes over + runtime data is not a lookup. **[DESIGN]** + +## 5. Memory-hierarchy isomorphism + +``` +register (operands) ⊂ L1 (attention focus tile) ⊂ L2 (warm concepts) ⊂ RAM/Lance (full bag) +attention = cache tile · forgetting = eviction · attention-shift = tile reload (gather) +``` +"Finish thinking in L1" = **cache-blocking for cognition**. The bag is NOT cache-bounded +(it's the durable store); the **focus** is. 20-200ns is a *cache-resident* number — one +RAM miss (~100ns) breaks it. **[DESIGN — precondition NOT yet in code, see §Verification]** + +## 6. Execution & ownership (the borrow guarantee) + +- **Mailbox = compile-time ownership bracket** at start/end. Message-passing = move + semantics = exclusive ownership, enforced by the borrow checker at *compile time* → + free at runtime; ractor's ~200ns is paid only at the two endpoints, never per cycle. + `Send + 'static` **forces** owned transfer — a borrowed `&[u8]` view can't cross the + mailbox. The owned span between brackets is lock-/race-free *by construction*. **[DESIGN]** +- Cross-runtime (ractor edge ↔ OS-thread core) is a **move over the lock-free ring**, + never a shared `&mut`. + +## 7. Durability & recovery + +- **Epoch checkpoint**: durability at cycle boundaries, not within. One process = one + failure domain ⟹ the checkpoint is **load-bearing** (only crash-recovery boundary). +- **commit = flush + prune-if-unreferenced** (committing *is* forgetting → cache stays + bounded by throughput). Prune **after** durable (Lance-first); reference-aware eviction. +- **SoA-unit sink-in = event-sourced replay log.** Replay recovers committed **state**, + **not** the reasoning **trajectory** (NARS under AIKR is resource/timing-dependent → + not path-deterministic). Needs **log compaction** (snapshot fold) or replay/storage + grow unbounded. **[DESIGN]** + +## 8. Scale path + +`kv-rocksdb` embedded (one binary, the elegant case) → `kv-tikv` distributed (Raft+RocksDB) +for HA. Same model/API; **code-level feature swap, ops-level data migration** (TiKV uses +its own RocksDB/Titan fork + cluster). Trades the one-binary elegance for HA — two +deployment *modes* of one codebase, not simultaneous. Use as the **review-shield against +"JanusGraph+Cassandra have HA"**: wrong tool for in-process cognitive compute, *and* we +have strongly-consistent (CP/Raft) HA when needed — arguably better-suited than Cassandra's +eventual consistency for auditable command-states. **[CODE: kv-rocksdb + kv-tikv backends exist]** + +--- + +# Verification (2026-05-26) — Code / Design / Vision + +## Verified present **[CODE]** + +- **SurrealDB `kv-lance` backend**, default `WritePath::LsmWithWal`: WAL(fsync) + + memtable (`dashmap`, generation-ordered) + background flusher → Lance `MergeInsertBuilder`. + `core/src/kvs/lance/{wal,memtable,flusher,mod}.rs`. +- **`kv-rocksdb` + `kv-tikv` backends** present (the HA scale path). +- **WAL serializes via CBOR** (`ciborium`, `wal.rs:133`); Lance commit copies into Arrow + (`schema.rs build_write_batch .collect()`). → the path is **NOT zero-copy as built**; + the honest term is **zero-*serialization*** (achievable with raw-`#[repr(C)]`-LE + checksum + replacing CBOR) + **one unavoidable engine-internal copy** (memtable/Arrow = the write itself). +- **NARS *data* model**: `NarsTruth` (freq+conf, pack/unpack) + `NarsBudget` packed in the + holograph fingerprint schema (`holograph/src/width_16k/schema.rs`). +- **VSA superposition-bundle** (majority vote) in holograph: `HdrVector::bundle` / + `bundle_weighted`, `representation.rs`, `bitpack.rs`, `sentence_crystal.rs`. +- **ractor is async-only** (tokio/async-std, cooperative; no preemptive/OS-thread mode). +- **splat3d render-depth certification** (ndarray PR #206, merged): DTO + scalar ref + + SIMD batch + HHTL depth cascade + mesh anchor. + +## The two load-bearing gaps + +### Gap #1 — bounded NARS attention-bag: **NOT FOUND [VISION]** +`NarsTruth`/`NarsBudget` exist as *data*, but **no bounded NARS concept-bag with +forgetting** was found in `lance-graph`. The free-running reasoning *engine* (inference +loop, revision, bag-with-capacity) is not verifiably present. **Consequence:** the +cache-residency precondition the entire 20-200ns latency story rests on is **unimplemented**. +This is the #1 risk — the latency guarantee has no code backing yet. + +### Gap #2 — commit merge is a **last-writer-wins relic [CODE, needs fix]** +The flusher's `MergeInsertBuilder.when_matched(WhenMatched::UpdateAll)` is LWW, and +`flusher.rs:251` states it was *"extracted from the prior commit_gate::single_lance_commit"* +— i.e. a **relic** from the (now-idle) `CommitGate`, not a deliberate semantics. +- `lance_graph_contract`'s KV-level `MergeMode::Bundle` is *defined* as "later writer wins", + so the comment "LWW is consistent with Bundle" is self-consistent **at the KV level**. +- BUT the **cognitively-correct** merge is **superposition** (holograph majority-vote), + which retains both contributions; LWW *discards* the earlier one. +- LWW is only safe under **hard key-partitioning** (same key never collides in a batch). + The backend documents "conflicts **rare**" via BindSpace key-prefix sharding — and + *rare ≠ never*: on the rare collision **LWW silently drops a contribution.** +- **Fix:** wire the holograph superposition-bundle into the merge point (memtable conflict + or read-bundle-write in the flusher — Lance `MergeInsert` cannot superpose natively), + **or** prove hard (not merely rare) key-partitioning. + +## Other items still Design/Vision + +- **Phase 4 (outcome/Bewerten)**: needed for *learned*, not just *produced* — no + outcome→NARS-desire feedback path verified. Without it the credit-assignment loop + (which thinking-style produced a good result) doesn't close. **[VISION]** +- **Preemptive internal execution** (OS-thread/rayon NARS cores + lock-free ring to the + ractor edge): the *design*; not verified wired. **[DESIGN/VISION]** +- **zero-serialization commit** (raw-LE instead of CBOR/Arrow): **[VISION]** — current is CBOR. + +## Load-bearing constraints (must hold or it breaks) + +1. Attention focus hard-bounded to L1 (budget caps focus width) — **Gap #1, unbuilt**. +2. Forgetting = resource governor **and** latency guarantee (cache residency). +3. Backpressure: outbound saturation must feed back into the attention budget. +4. The cognitive merge must superpose where collisions are possible — **Gap #2, relic LWW**. +5. Cheap solved-predicate: NARS truth/desire + O(1) ontology lookup, no recompute/lock. +6. Tick snapshot lock-free (epoch/RCU); prune-after-durable; log compaction. +7. Hot cycle alloc-free, dispatch-free, inlined. + +## Honest bilanz + +**Strong & real:** the unification (one process, no inter-system serialization tax), the +marker-collapse, the cache-isomorphism, the non-request liveness, the borrow-checker-as- +compile-time-concurrency-proof — and a genuine HA path (TiKV). **Costs / not-yet:** the +bounded NARS bag (Gap #1) is the unbuilt keystone of the latency story; the commit merge +is still the LWW relic (Gap #2); zero-copy is really zero-serialization-once-changed; and +"learned" needs Phase 4. **Differentiator is depth/autonomy, not throughput.** diff --git a/CLAUDE.md b/CLAUDE.md index 9e31dab6..65e4547c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,6 +21,24 @@ VSA carrier). > corrected three-layer framing and `CHANGELOG.md` for the format- > switch history. +> **2026-05-26 Baton scoping (read with `EPIPHANIES.md` E-BATON-1):** "The +> Click" describes how a single `Think` resolves **locally, within one +> compartment** — the bundle is an ephemeral computation, never a persisted +> or transmitted singleton. **`Vsa16kF32` is deprecated as a *carrier*:** it +> does NOT cross mailbox boundaries and there is no materialized singleton +> BindSpace. Inter-mailbox state is the **Baton** — discrete owned +> `(u16 target, CausalEdge64)` handoffs (`CollapseGateEmission` in +> `lance-graph-contract`; `wire_cost_bytes() = 13 + 10·baton_count`, NOT +> `16384·4`). "Baton" is the workspace's native term for the little-endian +> contract (the user's name for it before the information-science framing). +> The **mailbox-as-owner** (rotating sea-star topology) is what makes the +> handoff sound: Rust move/ownership semantics prove no aliasing / no data +> race / no use-after-free **at compile time** — UB becomes a compile error +> (canonical §9 E-CE64-MB-4). Cumulative state lives in CausalEdge64 +> emissions + AriGraph SPO-G quads + BindSpace SoA columns. The bundle MATH +> here (§I-SUBSTRATE-MARKOV Chapman-Kolmogorov, §I-VSA-IDENTITIES) is +> **untouched** — only the cross-boundary carrier is scoped out. + ``` Sentence → FSM → RoleKey_fp × content_fp → vsa_bundle (Σ) with ρ^d braiding │ │ diff --git a/Cargo.lock b/Cargo.lock index 3d693983..99c38a07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5431,6 +5431,7 @@ dependencies = [ "num-integer", "num-traits", "p64", + "paste", "portable-atomic", "portable-atomic-util", "rawpointer", @@ -7546,6 +7547,18 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "surreal_container" +version = "0.1.0" +dependencies = [ + "futures", + "lance", + "lancedb", + "snafu 0.8.9", + "tempfile", + "tokio", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 8b5cf911..e1e9fa90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ members = [ "crates/sigma-tier-router", # TD-SHADER-DRIVER-WORKSPACE-CONFLICT-1: moved from exclude to members "crates/cognitive-shader-driver", + # 01-deps-substrate: embedded SurrealDB-on-Lance cognitive store + "crates/surreal_container", ] exclude = [ # Python bindings (upstream-inherited, opt-in via --manifest-path) @@ -39,3 +41,57 @@ exclude = [ "crates/sigker", ] resolver = "2" + +# ─── Workspace-level dependency pins ──────────────────────────────────────── +# +# Task 01 (deps_substrate) — Lance 6 / LanceDB 0.28 / surrealdb kv-lance +# +# BLOCKED(A): Lance 6 has not been confirmed as a published crate version. +# Current workspace uses `lance = "=4.0.0"`. The spec requests "Lance 6" +# but crates.io only shows lance up to the 4.x series as of this writing. +# A fork-access human must confirm the exact semver (e.g. "=6.0.0") and +# whether it is published to crates.io or must be pulled from a git source. +# Until confirmed, leave the existing `lance = "=4.0.0"` pins in place. +# +# BLOCKED(B): LanceDB 0.28 has not been confirmed as published. +# Current workspace uses `lancedb = "=0.27.2"`. The spec requests 0.28. +# A fork-access human must confirm `lancedb = "=0.28.0"` (or exact patch) +# is live on crates.io or a private registry, and that the DataFusion 52 / +# Arrow 57 transitive constraints still hold at 0.28. +# +# BLOCKED(C): `surrealdb` with `kv-lance` feature. +# Upstream surrealdb (crates.io) does NOT ship a `kv-lance` feature. This +# feature lives in the AdaWorldAPI fork (core/src/kvs/lance/ per +# cognitive-substrate.md). A fork-access human must supply: +# - the git URL of the fork (e.g. https://github.com/AdaWorldAPI/surrealdb) +# - the branch or tag to pin (e.g. `branch = "kv-lance-v2"`) +# - the exact feature flag name (confirmed: `kv-lance` from spec) +# - whether a published crate path or `[patch.crates-io]` override is +# required for workspace compilation. +# +# BLOCKED(D): lance-index → ndarray 0.16 transitive constraint. +# Lance 6 may pull lance-index which depends on ndarray 0.16.x. The +# workspace currently uses AdaWorldAPI/ndarray (a path dep). If Lance 6's +# transitive closure requires ndarray "0.16" from crates.io, a +# `[patch.crates-io]` entry must alias it to the fork path. This must be +# verified by the human with access to Lance 6's full Cargo.lock. +# +# Placeholder [workspace.dependencies] entries are left as comments below so +# that the draft is ready to uncomment once BLOCKED items are resolved: +# +# [workspace.dependencies] +# # BLOCKED(A): replace "=4.0.0" with confirmed Lance 6 semver +# lance = "=4.0.0" # TODO: bump to Lance 6 once BLOCKED(A) resolved +# lance-linalg = "=4.0.0" # TODO: bump in lock-step with lance +# lance-index = "=4.0.0" # TODO: bump in lock-step; see BLOCKED(D) +# lance-namespace = "=4.0.0" # TODO: bump in lock-step with lance +# +# # BLOCKED(B): replace "=0.27.2" with confirmed LanceDB 0.28 semver +# lancedb = "=0.27.2" # TODO: bump to 0.28 once BLOCKED(B) resolved +# +# # BLOCKED(C): replace stub with real git source + feature name +# # surrealdb = { git = "BLOCKED_SEE_C", branch = "BLOCKED_SEE_C", features = ["kv-lance"] } +# +# [patch.crates-io] +# # BLOCKED(D): uncomment and fill once Lance 6 ndarray transitive version known +# # ndarray = { path = "../../../ndarray" } diff --git a/crates/lance-graph-contract/src/atoms.rs b/crates/lance-graph-contract/src/atoms.rs new file mode 100644 index 00000000..390ba183 --- /dev/null +++ b/crates/lance-graph-contract/src/atoms.rs @@ -0,0 +1,212 @@ +//! Atom layer — the **LOCKED 33-dim ThinkingStyleVector** basis. +//! +//! # Source (do NOT re-derive) +//! +//! `EPIPHANIES.md` **E-AGICHAT-DIMENSION-CONTRACT** → agichat's +//! `CANONICAL_DIMENSION_ALLOCATION.md` ("Status: **LOCKED**"). The basis is **not** +//! derived (no ICA/PCA, no "demote the 36 styles") — it is the locked 33-dim +//! allocation, restored on the i4 SoA floor (`E-I4-META-1`, the shipped i4-32 unpack). +//! +//! # Layers (smallest → largest) +//! +//! ```text +//! atom = ONE lane of the vector (e.g. `deduce`, `R5`, `phi`) — bare-metal, not human-legible. +//! style = ONE i4 VECTOR over all 33 atoms (a weighting) — the MOLECULE (Kant, Schopenhauer). +//! persona = a composition of styles + thresholds + purpose + β. +//! ``` +//! +//! An [`Atom`] is a lane, **not** a [`crate::thinking::ThinkingStyle`]. A style is an +//! `I4x32` vector over the atoms; `ThinkingStyle` is the 6-bit identity that *resolves +//! to* such a vector. The groups below (Pearl/Rung/Σ/Ops/Presence/Meta) are **allocation +//! families**, neither atoms nor molecules. +//! +//! # Execution stack: `atoms → cognitive-shader-driver → SIMD` +//! +//! **Atoms are NOT SIMD.** This module defines the lanes (the catalogue) and the +//! bare-metal carrier (`I4x32` pack/unpack). All composition / affinity / sweep work +//! **dispatches through `cognitive-shader-driver`**, which owns the ndarray i4 SIMD +//! path. There is deliberately no dot-product / SIMD hot path in this layer. +//! +//! # Business is not here +//! +//! No business/FIBU lanes. Business is an **OGIT-inherited sidecar** (`E-OGIT-STAKES-LINCHPIN`): +//! request class → `MappingRow` → `Marking::Financial` → bookkeeping savant. Never an atom. + +// --------------------------------------------------------------------------- +// Open layout questions (genuinely unresolved — do NOT guess) +// --------------------------------------------------------------------------- + +// BLOCKED: 32-vs-33 reconciliation. The TSV is 33 dims (3+9+5+8+4+4); the shipped +// carrier floor is i4-32 (32 lanes). E-AGICHAT-DIMENSION-CONTRACT says "i4 × 33 (or +// 32 + 1)". Decide: does the carrier hold 32 lanes with the 33rd folded into a spare, +// ride two halves, or is one family trimmed by one? Until decided, the carrier below +// is `I4x32` (32 lanes) and the catalogue lists 33 logical atoms with `dim` 0..33. + +// BLOCKED: "8 spare" vs "4 Presence + 4 Meta". STYLE_ENCODING.md describes the last 8 +// dims as "8 spare"; the dimension-contract body names them 4 Presence + 4 Meta. The +// catalogue below uses Presence+Meta; confirm which is canonical before wiring. + +// BLOCKED: per-group i4 sign/scale. The ordinal ladders (Pearl/Rung/Σ) want unsigned +// magnitude (level along the ladder); the few ± lanes (deduce↔induce in Ops, exploration +// in Meta, authentic↔performance in Presence) want signed. Cite FormatBestPractices.md; +// confirm the per-group convention before implementing pack/unpack scaling. + +// --------------------------------------------------------------------------- +// Bare-metal carrier (no SIMD here — dispatch through cognitive-shader-driver) +// --------------------------------------------------------------------------- + +/// Packed 32-lane signed-4-bit vector — the bare-metal carrier a **style** rides on. +/// +/// 32 signed i4 values in 16 bytes (two nibbles per byte). This holds a thinking-style +/// vector (a weighting over the atom lanes). It is the *bytes*; the cognition is the +/// style/persona **objects** built on it (see `recipe.rs`). +/// +/// Composition, affinity, and any vectorized sweep are **not** implemented here — they +/// dispatch through `cognitive-shader-driver` (which owns the ndarray i4-32 SIMD). This +/// type only packs/unpacks the lanes. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(C, align(16))] +pub struct I4x32 { + bytes: [u8; 16], +} + +impl I4x32 { + /// The all-zero style vector (every lane neutral). + pub const ZERO: Self = Self { bytes: [0u8; 16] }; + + /// Pack 32 signed bytes (one per lane) into the i4 carrier, saturating to [−8, 7]. + /// + /// Pre-scaling (f32 → i8) is the caller's job per the group convention — see the + /// `// BLOCKED: per-group i4 sign/scale` note above and `FormatBestPractices.md`. + pub fn pack(values: &[i8; 32]) -> Self { + let _ = values; + todo!("I4x32::pack — bare-metal nibble pack; implement after the sign/scale convention is fixed") + } + + /// Unpack the 32 lanes to signed bytes (sign-extended i4, range [−8, 7]). + pub fn unpack(&self) -> [i8; 32] { + todo!("I4x32::unpack — bare-metal nibble unpack") + } +} + +// --------------------------------------------------------------------------- +// The LOCKED atom catalogue (the 33-dim TSV allocation) +// --------------------------------------------------------------------------- + +/// Allocation family of an atom lane. Families are organizational, not atoms. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum AtomGroup { + /// Pearl causal ladder (3): association / intervention / counterfactual. + Pearl, + /// Meaning-depth rung ladder (9): R1–R9. + Rung, + /// σ-tier chain (5): Ω / Δ / Φ / Θ / Λ. + Sigma, + /// Cognitive operations (8): the inference modes + fanout ops. + Operation, + /// Presence modes (4). + Presence, + /// Meta knobs (4). + Meta, +} + +/// One lane of the LOCKED 33-dim TSV. `dim` is the canonical lane index (0..33). +#[derive(Debug, Clone, Copy)] +pub struct Atom { + /// Canonical lane index, 0..33, in locked allocation order. + pub dim: u8, + /// Allocation family. + pub group: AtomGroup, + /// Locked lane name. + pub name: &'static str, +} + +impl Atom { + const fn new(dim: u8, group: AtomGroup, name: &'static str) -> Self { + Self { dim, group, name } + } +} + +use AtomGroup::*; + +/// The LOCKED 33-dim TSV allocation (E-AGICHAT-DIMENSION-CONTRACT), in canonical order. +/// +/// Order is the contract — `CANONICAL_DIMENSION_ALLOCATION.md` rejects arbitrary moves. +pub const CANONICAL_ATOMS: [Atom; 33] = [ + // 3 Pearl — causal ladder (ordinal) + Atom::new(0, Pearl, "see_association"), + Atom::new(1, Pearl, "do_intervention"), + Atom::new(2, Pearl, "imagine_counterfactual"), + // 9 Rung — meaning-depth ladder (ordinal) 🪜 + Atom::new(3, Rung, "rung_r1"), + Atom::new(4, Rung, "rung_r2"), + Atom::new(5, Rung, "rung_r3"), + Atom::new(6, Rung, "rung_r4"), + Atom::new(7, Rung, "rung_r5"), + Atom::new(8, Rung, "rung_r6"), + Atom::new(9, Rung, "rung_r7"), + Atom::new(10, Rung, "rung_r8"), + Atom::new(11, Rung, "rung_r9"), + // 5 Sigma — σ-tier chain (ordinal) + Atom::new(12, Sigma, "sigma_omega"), + Atom::new(13, Sigma, "sigma_delta"), + Atom::new(14, Sigma, "sigma_phi"), + Atom::new(15, Sigma, "sigma_theta"), + Atom::new(16, Sigma, "sigma_lambda"), + // 8 Operations — inference modes + fanout ops (deduce↔induce is the one ± pair) + Atom::new(17, Operation, "abduct"), + Atom::new(18, Operation, "deduce"), + Atom::new(19, Operation, "induce"), + Atom::new(20, Operation, "synthesize"), + Atom::new(21, Operation, "preflight"), + Atom::new(22, Operation, "escalate"), + Atom::new(23, Operation, "transcend"), + Atom::new(24, Operation, "model_other"), + // 4 Presence — modes (authentic↔performance is a ± pair) [BLOCKED: "8 spare" alt] + Atom::new(25, Presence, "authentic"), + Atom::new(26, Presence, "performance"), + Atom::new(27, Presence, "protective"), + Atom::new(28, Presence, "absent"), + // 4 Meta — knobs (exploration = explore↔exploit / temperature) [BLOCKED: "8 spare" alt] + Atom::new(29, Meta, "confidence_threshold"), + Atom::new(30, Meta, "preflight_depth"), + Atom::new(31, Meta, "exploration"), + Atom::new(32, Meta, "verbosity"), +]; + +/// Look up a locked atom by canonical lane index (0..33). +#[inline] +pub fn atom(dim: u8) -> Option<&'static Atom> { + CANONICAL_ATOMS.get(dim as usize) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn carrier_layout_is_16_bytes() { + assert_eq!(core::mem::size_of::(), 16); + assert_eq!(core::mem::align_of::(), 16); + } + + #[test] + fn catalogue_is_locked_33_in_order() { + assert_eq!(CANONICAL_ATOMS.len(), 33); + for (i, a) in CANONICAL_ATOMS.iter().enumerate() { + assert_eq!(a.dim as usize, i, "lane dim must equal its index (locked order)"); + assert!(!a.name.is_empty()); + } + } + + #[test] + fn group_counts_match_the_contract() { + let count = |g: AtomGroup| CANONICAL_ATOMS.iter().filter(|a| a.group == g).count(); + assert_eq!(count(Pearl), 3); + assert_eq!(count(Rung), 9); + assert_eq!(count(Sigma), 5); + assert_eq!(count(Operation), 8); + assert_eq!(count(Presence), 4); + assert_eq!(count(Meta), 4); + } +} diff --git a/crates/lance-graph-contract/src/counterfactual.rs b/crates/lance-graph-contract/src/counterfactual.rs new file mode 100644 index 00000000..cd67488b --- /dev/null +++ b/crates/lance-graph-contract/src/counterfactual.rs @@ -0,0 +1,433 @@ +//! D-ATOM-4 — split-resolution via counterfactual mantissa. +//! +//! On a `SPLIT` quorum ([`crate::escalation::is_split`] / [`crate::escalation::CouncilVerdict::split`]), +//! the majority pole is **committed** and the minority pole is **forked** into a +//! counterfactual retained as a [`causal_edge::edge::CausalEdge64`] −6 mantissa nibble +//! (i.e. [`causal_edge::edge::InferenceType::Counterfactual`], `to_mantissa() = -6`). +//! +//! The road-not-taken costs **4 bits**, not a replay buffer. +//! +//! ## Staging +//! +//! - **v2 (deposit)** — [`deposit_counterfactual`]: writes the minority pole as +//! `InferenceType::Counterfactual` (`to_mantissa() = -6`) into the episodic edge. +//! No mailbox is spawned; the 4-bit nibble is the entire footprint. +//! - **v3 (mailbox + revision)** — [`CounterfactualMailbox`] + [`revise_if_minority_wins`]: +//! ghost-tier mailbox that, when β headroom allows, tests whether the minority pole +//! produces lower free energy; if so, triggers `awareness.revise` to reopen the axis. +//! Requires the ractor outer-swarm (rung-persona D-PERSONA-5 — not yet shipped). +//! +//! ## Invariant +//! +//! A counterfactual **stays in a separate lane — it is NEVER written as observed SPO +//! truth.** (The Click: "contradictions preserved, not resolved".) The +//! `InferenceType::Counterfactual` tag is the mechanical enforcement of that invariant. +//! +//! ## Feature gates +//! +//! The write path uses `with_inference_mantissa(-6)` on a `CausalEdge64`; this accessor +//! is only present (non-no-op) when the `causal-edge-v2-layout` feature is active. +//! Under v1 the nibble write silently no-ops (per `I-LEGACY-API-FEATURE-GATED`). +//! Callers that need the deposit to be durable MUST compile with `causal-edge-v2-layout`. +//! +//! ## Cross-references +//! +//! - [`crate::escalation::is_split`] — the split predicate (threshold `hi=0.7 / lo=0.5`). +//! - [`crate::escalation::CouncilVerdict`] — the split signal consumed here. +//! - `causal_edge::edge::InferenceType::to_mantissa()` — canonical `−6` encoding. +//! - `causal_edge::edge::CausalEdge64::with_inference_mantissa(i8)` — the v2 write path. +//! Feature-gated: active only under `causal-edge-v2-layout`. +//! - `I-LEGACY-API-FEATURE-GATED` (CLAUDE.md) — the iron rule that governs v1/v2 API paths. +//! - D-PERSONA-5 (`rung-persona-orchestration-v1`) — the ractor outer-swarm required by v3. + +// ═══════════════════════════════════════════════════════════════════════════ +// Spawn gate +// ═══════════════════════════════════════════════════════════════════════════ + +/// Threshold above which a split quorum is "hot enough" to warrant forking a +/// counterfactual mailbox (v3). Below the threshold only the 4-bit mantissa +/// deposit (v2) is performed; no mailbox is spawned, saving the ractor spawn cost. +/// +/// The gate is driven by `dissonance` (from `a2a_blackboard::BlackboardEntry`) +/// or `Staunen` qualia (the temperature axis — high Staunen = high surprise = +/// worth a counterfactual test). Exact threshold is calibrated from the +/// `WisdomMarker` floor (0.1) and the `EpiphanyDetector` baseline × 1.5 rule; +/// the default here is a conservative starting point pending empirical tuning. +/// +/// Invariant: `SPAWN_DISSONANCE_THRESHOLD > 0.0` (no spawn on zero dissonance). +pub const SPAWN_DISSONANCE_THRESHOLD: f32 = 0.55; + +// ═══════════════════════════════════════════════════════════════════════════ +// SplitPoles — the two ends of a contested axis +// ═══════════════════════════════════════════════════════════════════════════ + +/// The two poles of a split quorum: the majority pole that gets committed and +/// the minority pole that becomes the counterfactual residue. +/// +/// `majority_pole` and `minority_pole` are opaque axis-position values in the +/// I4 range `[-8, +7]`. Their concrete meaning is determined by the calling +/// context (D-ATOM-3 / per-axis quorum projection). +/// +/// # BLOCKED +/// +/// The exact type for pole positions (`i8` vs a newtype from D-ATOM-1's `I4x32`) +/// is **BLOCKED** on D-ATOM-1 (the I4-32D atom basis). Using `i8` here as a +/// conservative placeholder — the range `[-8, +7]` is i4-compatible. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SplitPoles { + /// The axis index (0-31 in the I4-32D basis, per D-ATOM-1). + /// + /// **BLOCKED:** axis indexing is BLOCKED on D-ATOM-1 (atom basis not yet chosen). + pub axis: u8, + /// The winning (majority) pole value, committed to the SPO graph. + pub majority_pole: i8, + /// The minority pole value, deposited as the counterfactual mantissa. + pub minority_pole: i8, + /// The dissonance magnitude at the time of the split (drives the spawn gate). + /// Sourced from `a2a_blackboard::BlackboardEntry::dissonance` or equivalent. + pub dissonance: f32, +} + +// ═══════════════════════════════════════════════════════════════════════════ +// v2 — deposit_counterfactual +// ═══════════════════════════════════════════════════════════════════════════ + +/// **v2 (deposit)** — Write the minority pole of a split quorum as +/// `InferenceType::Counterfactual` (`to_mantissa() = -6`) into the episodic +/// `CausalEdge64`, consuming 4 bits. +/// +/// # What this does +/// +/// 1. Checks that `verdict.split` is `true` (no-op on a non-split verdict). +/// 2. Writes `InferenceType::Counterfactual.to_mantissa()` (= −6) into +/// `edge` via `edge.with_inference_mantissa(-6)` / `set_inference_mantissa(-6)`. +/// 3. Returns `true` if the deposit was made, `false` if the verdict was not a +/// split (so callers can conditionally proceed to commit the majority pole). +/// +/// # IMPORTANT — counterfactual stays in a SEPARATE LANE +/// +/// The deposited edge must **never** be written as observed SPO truth. It is +/// a `CausalEdge64` episodic witness tagged `InferenceType::Counterfactual` +/// and lives in the episodic / ghost tier only. Committing it into the SPO +/// ontology would violate The Click's "contradictions preserved, not resolved" +/// invariant and corrupt the observed-fact graph with unvalidated counterfactual +/// inference. +/// +/// # Feature gate +/// +/// `edge.set_inference_mantissa(-6)` (the v2 write) is a no-op under v1 +/// (`causal-edge-v2-layout` absent). The deposit silently does nothing in that +/// case. Callers requiring durable deposits MUST enable `causal-edge-v2-layout`. +/// +/// # Parameters +/// +/// - `split`: the [`crate::escalation::CouncilVerdict`] for the current axis. +/// Only `CouncilVerdict::split == true` triggers a deposit. +/// - `edge`: a mutable reference to the episodic `CausalEdge64` that carries +/// this axis's witness. The `inference_mantissa` nibble is overwritten. +/// +/// # BLOCKED +/// +/// The exact `CausalEdge64::set_inference_mantissa` / `with_inference_mantissa` +/// accessor name is confirmed from `crates/causal-edge/src/edge.rs` as +/// **`set_inference_mantissa(&mut self, i8)`** (mutable) and +/// **`with_inference_mantissa(self, i8) -> Self`** (builder style), both +/// **feature-gated on `causal-edge-v2-layout`** (no-op stubs exist for v1). +/// This function calls the mutable form. See `I-LEGACY-API-FEATURE-GATED`. +/// +/// The `awareness.revise` signature is **BLOCKED** — not yet located in the +/// contract crate surface. Required for v3 only; v2 does not call it. +pub fn deposit_counterfactual( + split: &crate::escalation::CouncilVerdict, + // BLOCKED: CausalEdge64 is in the `causal-edge` crate which is NOT a + // workspace member of lance-graph-contract (zero-dep constraint). The + // parameter below uses a placeholder trait object until the dependency + // boundary is resolved. If causal-edge becomes a workspace dep, replace + // with `edge: &mut causal_edge::edge::CausalEdge64`. + // + // For now the interface is expressed via the `EpisodicEdge` trait below. + edge: &mut dyn EpisodicEdge, +) -> bool { + todo!( + "v2 deposit: if split.split, call edge.set_inference_mantissa(-6) \ + (InferenceType::Counterfactual.to_mantissa()); return whether deposit was made" + ) +} + +// ═══════════════════════════════════════════════════════════════════════════ +// EpisodicEdge — thin zero-dep abstraction over CausalEdge64's mantissa path +// ═══════════════════════════════════════════════════════════════════════════ + +/// Thin abstraction over the `CausalEdge64` mantissa write path so that +/// `lance-graph-contract` (zero-dep) can express `deposit_counterfactual` +/// without taking a direct dep on `causal-edge`. +/// +/// Implementors: `causal_edge::edge::CausalEdge64` (via a newtype wrapper or +/// a blanket impl in the causal-edge crate, feature-gated on +/// `causal-edge-v2-layout`). +/// +/// # BLOCKED +/// +/// The bridge impl (where to put the `impl EpisodicEdge for CausalEdge64`) is +/// BLOCKED on workspace structure. Options: (a) impl in `causal-edge` gated on +/// a `lance-graph-contract` feature; (b) newtype in a thin bridge crate. +/// Until resolved, the trait is the stable surface; the impl location is open. +pub trait EpisodicEdge { + /// Write a signed 4-bit mantissa into the inference nibble. + /// + /// Under `causal-edge-v2-layout` this maps to + /// `CausalEdge64::set_inference_mantissa(m)`. + /// Under v1 (feature absent) this is a documented no-op. + fn set_inference_mantissa(&mut self, m: i8); + + /// Read back the signed 4-bit mantissa (for verification / revision). + fn inference_mantissa(&self) -> i8; +} + +// ═══════════════════════════════════════════════════════════════════════════ +// v3 — CounterfactualMailbox + revise_if_minority_wins +// ═══════════════════════════════════════════════════════════════════════════ + +/// **v3 (mailbox)** — Ghost-tier mailbox that holds and eventually tests a +/// counterfactual pole deposited by [`deposit_counterfactual`]. +/// +/// # Ghost tier (preemptible to zero) +/// +/// A `CounterfactualMailbox` has the lowest priority in the ractor outer-swarm: +/// it runs **only** when the β headroom (free-energy budget) exceeds the spawn +/// gate threshold ([`SPAWN_DISSONANCE_THRESHOLD`]). It is **preemptible to +/// zero** — if β headroom drops below the threshold at any point, the mailbox +/// is cancelled and its residue (the 4-bit mantissa in the episodic edge) is +/// the only surviving trace. Garbage collection is Staunen-keyed: a +/// counterfactual that never fires within its GC window is pruned. +/// +/// # Tests (β-headroom-gated) +/// +/// When activated, the mailbox reruns the axis projection using the minority +/// pole and computes the resulting free energy. If `F_minority < F_majority`, +/// [`revise_if_minority_wins`] is called to trigger a NARS revision on the axis. +/// +/// # Dependency: D-PERSONA-5 +/// +/// This struct requires the **ractor outer-swarm** (`rung-persona D-PERSONA-5`, +/// not yet shipped). The ractor actor handle, supervisor policy, and +/// `MailboxId` assignment live there. Do NOT instantiate `CounterfactualMailbox` +/// before D-PERSONA-5 is landed; doing so will compile but the `spawn` method +/// will always return `Err(CounterfactualError::SwarmNotReady)`. +/// +/// # BLOCKED +/// +/// - The ractor actor handle type is **BLOCKED** on D-PERSONA-5. +/// - The `awareness.revise` signature is **BLOCKED** — not found on the +/// current contract surface. Expected to live in +/// `crate::grammar` (see `FreeEnergy::compose` + `Resolution` thresholds) +/// or a future `crate::awareness` module; requires confirmation. +/// - The `MailboxId` type (`u32`, from `contract::collapse_gate`) is confirmed +/// shipped but the assignment policy for ghost-tier mailboxes is BLOCKED on +/// D-PERSONA-5. +#[derive(Debug)] +pub struct CounterfactualMailbox { + /// The split poles this mailbox is testing. + pub poles: SplitPoles, + /// The free energy measured for the committed (majority) pole at split time. + /// The mailbox wins if `F_minority < committed_free_energy`. + pub committed_free_energy: f32, + /// A monotonic generation counter, incremented on each GC sweep. + /// A mailbox whose `generation` is older than the current sweep window + /// is pruned (Staunen-keyed GC). + pub generation: u32, + // BLOCKED: ractor actor handle (D-PERSONA-5 dep). + // BLOCKED: MailboxId assignment for ghost tier (D-PERSONA-5 dep). +} + +impl CounterfactualMailbox { + /// Create a new ghost-tier mailbox for the given split poles. + /// + /// Returns `Err(CounterfactualError::SwarmNotReady)` if D-PERSONA-5's + /// ractor outer-swarm is not yet initialized. + /// + /// Only call when `dissonance > SPAWN_DISSONANCE_THRESHOLD` (the spawn + /// gate in [`should_spawn_mailbox`]). + pub fn new(poles: SplitPoles, committed_free_energy: f32) -> Result { + todo!( + "v3 spawn: register with D-PERSONA-5 ractor outer-swarm; \ + return Err(SwarmNotReady) until D-PERSONA-5 lands" + ) + } + + /// Poll the mailbox for a test result (non-blocking, cooperative). + /// + /// Returns `None` if the test has not yet completed or if β headroom is + /// insufficient. Returns `Some(FreeEnergyComparison)` when the minority + /// pole's free energy has been computed. + pub fn poll(&self) -> Option { + todo!("v3 poll: check β headroom; if adequate, run minority-pole projection and return comparison") + } + + /// Cancel and discard this mailbox (preempted by the scheduler or expired + /// by the Staunen-keyed GC sweep). The 4-bit mantissa residue in the + /// episodic edge is retained; only the active test loop is cancelled. + pub fn cancel(self) { + todo!("v3 cancel: deregister from ractor outer-swarm; log GC event") + } +} + +/// The result of a counterfactual free-energy comparison. +#[derive(Debug, Clone, Copy)] +pub struct FreeEnergyComparison { + /// Free energy of the committed (majority) pole. + pub f_majority: f32, + /// Free energy of the minority (counterfactual) pole. + pub f_minority: f32, +} + +impl FreeEnergyComparison { + /// Returns `true` if the minority pole yields lower free energy (the + /// road-not-taken would have been the better route). + #[inline] + pub fn minority_wins(self) -> bool { + self.f_minority < self.f_majority + } +} + +// ═══════════════════════════════════════════════════════════════════════════ +// v3 — revise_if_minority_wins +// ═══════════════════════════════════════════════════════════════════════════ + +/// **v3 (revision)** — If the counterfactual test shows the minority pole +/// yields lower free energy, trigger a NARS `awareness.revise` to reopen the +/// axis commitment. +/// +/// # The revision loop +/// +/// 1. The [`CounterfactualMailbox`] completes its free-energy comparison +/// (`mailbox.poll()` returns `Some(comparison)`). +/// 2. If `comparison.minority_wins()`, this function is called. +/// 3. It invokes `awareness.revise(axis_key, minority_outcome)` — a NARS +/// belief-revision on the committed axis, signalling that the road-not-taken +/// is now evidenced as superior. +/// 4. The revised belief replaces the committed majority pole for the **next** +/// resolution cycle. The historical committed pole stays in the SPO graph +/// (append-only), with a tombstone link to this revision event. +/// 5. The `InferenceType::Counterfactual` mantissa in the episodic edge is +/// cleared (set to `InferenceType::Deduction.to_mantissa()` = 0) once the +/// revision is committed, so the nibble is not double-counted. +/// +/// # Dependency: D-PERSONA-5 +/// +/// The `awareness.revise` call dispatches through the ractor outer-swarm +/// (D-PERSONA-5). This function is a **compile stub only** until D-PERSONA-5 +/// is shipped. +/// +/// # BLOCKED +/// +/// - `awareness.revise` **signature is BLOCKED** — the method is referenced +/// in CLAUDE.md / The Click (`awareness.revise(key, outcome)`) but its +/// concrete Rust signature (trait, module, parameter types) is not confirmed +/// on the current contract surface. Do NOT infer from CLAUDE.md pseudo-code +/// alone; grep `crates/lance-graph-contract/src/` and +/// `crates/thinking-engine/` for the canonical form before implementing. +/// - The `axis_key` type is BLOCKED on D-ATOM-1 (atom basis). +/// - The revision tombstone link into Lance versioned storage is BLOCKED on +/// D-ATOM-5 (AriGraph hot→calcify→tombstone wiring). +/// +/// # Parameters +/// +/// - `mailbox`: the completed ghost-tier mailbox whose comparison showed +/// `minority_wins() == true`. +/// - `edge`: the episodic edge carrying the `−6` mantissa deposit; its nibble +/// is cleared after successful revision. +/// - `awareness`: opaque handle to the NARS revision surface. +/// **BLOCKED: type unknown** — placeholder `&mut dyn AwarenessRevise` below. +pub fn revise_if_minority_wins( + mailbox: CounterfactualMailbox, + edge: &mut dyn EpisodicEdge, + // BLOCKED: `awareness.revise` signature — see doc comment above. + awareness: &mut dyn AwarenessRevise, +) -> Result { + todo!( + "v3 revision: poll mailbox; if minority_wins, call awareness.revise(axis_key, \ + minority_pole); clear edge mantissa; return RevisionOutcome::Revised or \ + RevisionOutcome::MajorityHolds" + ) +} + +// ═══════════════════════════════════════════════════════════════════════════ +// AwarenessRevise — BLOCKED placeholder trait +// ═══════════════════════════════════════════════════════════════════════════ + +/// Placeholder trait for the `awareness.revise` surface. +/// +/// # BLOCKED +/// +/// The canonical Rust signature for `awareness.revise` is **BLOCKED** — not +/// confirmed on the current contract surface. This trait is a scaffold surface +/// only. Before implementing [`revise_if_minority_wins`], grep +/// `crates/lance-graph-contract/src/` and `crates/thinking-engine/src/` for +/// the exact method name, parameter types, and return type. Replace this trait +/// with a concrete type reference once found. +/// +/// Expected home (from CLAUDE.md / The Click pseudo-code): +/// `awareness.revise(key, outcome)` — likely on a `ParamTruths`-style type +/// or a `NarsAwareness` / `EpistemicState` type in `contract::nars` or +/// `contract::grammar`. +pub trait AwarenessRevise { + /// Revise the NARS belief for `axis_key` given `new_evidence`. + /// + /// # BLOCKED + /// + /// Exact parameter types (`axis_key`, `new_evidence`) are BLOCKED on + /// the `awareness.revise` signature discovery and on D-ATOM-1 + /// (axis key type from the I4-32D basis). + fn revise(&mut self, axis_key: u8, new_evidence: i8) -> Result<(), CounterfactualError>; +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Spawn gate helper +// ═══════════════════════════════════════════════════════════════════════════ + +/// Returns `true` if the dissonance level warrants spawning a +/// [`CounterfactualMailbox`] (v3 path). +/// +/// When `false`, only the 4-bit mantissa deposit (v2 path via +/// [`deposit_counterfactual`]) is performed; no mailbox is created. +/// +/// # Rationale +/// +/// The spawn gate exists to avoid spawning ghost-tier mailboxes on every split: +/// shallow splits with low dissonance carry low counterfactual value and the +/// ractor spawn cost is not justified. High dissonance / high Staunen (surprise) +/// implies the road-not-taken may genuinely matter. +/// +/// Threshold: [`SPAWN_DISSONANCE_THRESHOLD`] (default 0.55). +#[inline] +pub fn should_spawn_mailbox(dissonance: f32) -> bool { + dissonance > SPAWN_DISSONANCE_THRESHOLD +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Error type +// ═══════════════════════════════════════════════════════════════════════════ + +/// Errors from the counterfactual machinery. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CounterfactualError { + /// The ractor outer-swarm (D-PERSONA-5) is not yet initialized. + /// v3 operations fail with this until D-PERSONA-5 is shipped. + SwarmNotReady, + /// The verdict was not a split; the deposit / spawn was a no-op. + NotASplit, + /// The minority pole did not win the free-energy comparison. + /// Returned by [`revise_if_minority_wins`] when `minority_wins() == false`. + MajorityHolds, +} + +/// The outcome of a revision attempt. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RevisionOutcome { + /// The minority pole won and `awareness.revise` was called; the axis + /// commitment has been reopened for the next cycle. + Revised, + /// The majority pole held; no revision was performed. + MajorityHolds, +} diff --git a/crates/lance-graph-contract/src/escalation.rs b/crates/lance-graph-contract/src/escalation.rs new file mode 100644 index 00000000..2858bfb7 --- /dev/null +++ b/crates/lance-graph-contract/src/escalation.rs @@ -0,0 +1,576 @@ +//! Escalation + epiphany loop — the boring checklist, grounded on our SoA. +//! +//! D-PERSONA-1 of `rung-persona-orchestration-v1`. This is the *restore* of +//! ladybug's qualia loop (`felt_parse` collapse-hint + `InnerCouncil` / +//! `HdrResonance` split + `EpiphanyDetector`) onto **our** contract types — +//! NOT a bespoke verifier. The checklist that boots an agent is not a list of +//! ad-hoc asserts; each item is *verified by the escalation loop itself*: +//! +//! ```text +//! felt_parse → CollapseHint {Flow | Fanout | RungElevate} +//! Fanout = gather more (escalate breadth) +//! RungElevate = deepen (rung-shift) +//! Flow = settled / done +//! InnerCouncil.deliberate (Guardian / Catalyst / Balanced, majority vote) +//! + HdrResonance split-amplify: a split (one archetype sees what the +//! others don't, is_split(0.7, 0.5)) is amplified ×1.2 — disagreement +//! IS the learning signal (perspectives disagree about a projection +//! ⇒ a spurious S–O is screened off). +//! EpiphanyDetector.observe: Some(Epiphany) iff +//! similarity > baseline × 1.5 ∧ recent_samples ≥ 4 +//! (the window ≥ 4 is the anti-Mount-Stupid evidence guard). +//! green-flip = the item settles to Flow AND an epiphany fires → +//! a WisdomMarker (Epiphany / Wisdom ghost) — persistent qualia +//! residue that decays asymptotically to 0.1, never to zero. +//! ``` +//! +//! The list completes (the meta-recipe composes — D-PERSONA-2) when every +//! item's collapse-hint settles to [`CollapseHint::Flow`]. Items split HARD +//! (must be green to boot) vs SOFT (degrade gracefully if red — anytime). A +//! green item going red at runtime is a let-it-crash event: re-escalate +//! ([`Checklist::mark_red`]) — the checklist items ARE the supervision +//! health-checks, continuous and not one-time. +//! +//! Zero-dep: the council takes raw scalar signals (trust / humility / flow / +//! load), so both this crate's [`crate::mul::MulAssessment`] and the planner's +//! richer assessment can drive it without a type dependency. + +// ═══════════════════════════════════════════════════════════════════════════ +// CollapseHint — the felt_parse escalation decision (already produced) +// ═══════════════════════════════════════════════════════════════════════════ + +/// The collapse hint `felt_parse` emits for one item. The escalation decision +/// is *already produced* here ("the list as escalation work"). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CollapseHint { + /// Settled — no further work; the item closes on a green-flip. + Flow, + /// Gather more — escalate breadth (the suspect-bridge fan-out width is + /// driven by `bridgeness`; see [`fanout_width`]). + Fanout, + /// Deepen — rung-shift to a higher reasoning level (see [`rung_delta`]). + RungElevate, +} + +/// Fan-out width, grounded in ladybug `detector.rs`: +/// `fanout = base · (1 + bridgeness · 0.5)`, clamped to `[1, 30]`. +/// `bridgeness` is the suspect-bridge centrality (macro-eval, D-PERSONA-4). +#[inline] +pub fn fanout_width(base: f32, bridgeness: f32) -> u32 { + ((base * (1.0 + bridgeness.clamp(0.0, 1.0) * 0.5)).round() as i32).clamp(1, 30) as u32 +} + +/// Noise tolerance (annealing temperature proxy), grounded in `detector.rs`: +/// `noise_tolerance = base · (1 + (1 − confidence) · 0.5)` — low confidence +/// runs hotter (more exploration). `confidence` is the calibrated competence. +#[inline] +pub fn noise_tolerance(base: f32, confidence: f32) -> f32 { + base * (1.0 + (1.0 - confidence.clamp(0.0, 1.0)) * 0.5) +} + +/// Rung-shift delta, grounded in `detector.rs`: +/// `emergence > 0.5 ∧ coherence < 0.4 → +1` (elevate / RungElevate), +/// `coherence > 0.8 ∧ emergence < 0.1 → −1` (descend), else `0`. +#[inline] +pub fn rung_delta(emergence: f32, coherence: f32) -> i8 { + if emergence > 0.5 && coherence < 0.4 { + 1 + } else if coherence > 0.8 && emergence < 0.1 { + -1 + } else { + 0 + } +} + +// ═══════════════════════════════════════════════════════════════════════════ +// InnerCouncil — Guardian / Catalyst / Balanced, majority vote + split amplify +// ═══════════════════════════════════════════════════════════════════════════ + +/// The three council perspectives. Each scores the item; the majority hint +/// wins. Disagreement (a split) is the learning signal, not noise. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Archetype { + /// Caution / evidence-gathering — votes [`CollapseHint::Fanout`]. + Guardian, + /// Push deeper — votes [`CollapseHint::RungElevate`]. + Catalyst, + /// Settle — votes [`CollapseHint::Flow`]. + Balanced, +} + +impl Archetype { + /// The collapse hint this archetype advocates for. + #[inline] + pub fn vote(self) -> CollapseHint { + match self { + Archetype::Guardian => CollapseHint::Fanout, + Archetype::Catalyst => CollapseHint::RungElevate, + Archetype::Balanced => CollapseHint::Flow, + } + } +} + +/// `HdrResonance` split test: one archetype sees strongly (`max ≥ hi`) what +/// another barely sees (`min ≤ lo`). Ladybug calls `is_split(0.7, 0.5)`. +#[inline] +pub fn is_split(scores: [f32; 3], hi: f32, lo: f32) -> bool { + let max = scores.iter().copied().fold(f32::MIN, f32::max); + let min = scores.iter().copied().fold(f32::MAX, f32::min); + max >= hi && min <= lo +} + +/// The council's verdict for one item. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct CouncilVerdict { + /// The winning collapse hint (majority of the three perspectives). + pub hint: CollapseHint, + /// Winning confidence in `[0, 1]`, ×1.2-amplified (clamped) on a split. + pub confidence: f32, + /// Whether the perspectives split — the learning signal. + pub split: bool, +} + +/// The inner council. Stateless: `deliberate` is a pure function of the three +/// perspective scores; `from_signals` derives those scores from scalar MUL +/// observables shared by every assessment shape. +#[derive(Debug, Clone, Copy, Default)] +pub struct InnerCouncil; + +impl InnerCouncil { + /// Deliberate over the three perspective scores `[guardian, catalyst, + /// balanced]` (each in `[0, 1]`). The highest-scoring archetype's hint + /// wins; a split ([`is_split`] at `0.7 / 0.5`) amplifies the winning + /// confidence ×1.2 (clamped to 1.0). + pub fn deliberate(scores: [f32; 3]) -> CouncilVerdict { + let archetypes = [Archetype::Guardian, Archetype::Catalyst, Archetype::Balanced]; + let (idx, &best) = scores + .iter() + .enumerate() + .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(core::cmp::Ordering::Equal)) + .unwrap_or((2, &0.0)); // default Balanced/Flow when degenerate + let split = is_split(scores, 0.7, 0.5); + let confidence = if split { (best * 1.2).min(1.0) } else { best }; + CouncilVerdict { + hint: archetypes[idx].vote(), + confidence, + split, + } + } + + /// Derive the three perspective scores from scalar MUL observables and + /// deliberate. This is the `felt_parse` viscosity counterpart: it refines + /// the inherited prior per-turn from the live cognitive signal. + /// + /// - `trust` ∈ `[0,1]` — calibrated trust value (high = safe to settle). + /// - `humility` ∈ `[0,1]` — DK humility factor (low = Mount Stupid). + /// - `flow` ∈ `[0,1]` — homeostatic flow factor (high = absorbed). + /// - `load` ∈ `[0,1]` — allostatic load (high = stressed → gather more). + /// + /// Guardian (Fanout) rises with low trust / high load; Catalyst + /// (RungElevate) rises in the productive mid-band (mid humility, decent + /// flow — there is depth left to find); Balanced (Flow) rises with high + /// trust + flow + low load. + pub fn from_signals(trust: f32, humility: f32, flow: f32, load: f32) -> CouncilVerdict { + let trust = trust.clamp(0.0, 1.0); + let humility = humility.clamp(0.0, 1.0); + let flow = flow.clamp(0.0, 1.0); + let load = load.clamp(0.0, 1.0); + + let guardian = ((1.0 - trust) * 0.6 + load * 0.4).clamp(0.0, 1.0); + // Catalyst peaks at mid humility (slope of enlightenment), gated by flow. + let mid = 1.0 - (humility - 0.5).abs() * 2.0; // 1.0 at humility=0.5, 0 at extremes + let catalyst = (mid * 0.6 + flow * 0.4).clamp(0.0, 1.0); + let balanced = (trust * 0.5 + flow * 0.3 + (1.0 - load) * 0.2).clamp(0.0, 1.0); + + Self::deliberate([guardian, catalyst, balanced]) + } +} + +// ═══════════════════════════════════════════════════════════════════════════ +// EpiphanyDetector — surprise > baseline × 1.5 ∧ window ≥ 4 +// ═══════════════════════════════════════════════════════════════════════════ + +/// An epiphany: the closing signal for a checklist item. A spike of +/// resonance above the established baseline, evidenced by a sufficient window. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Epiphany { + /// The triggering similarity / resonance sample. + pub similarity: f32, + /// The window baseline it exceeded by ≥ 1.5×. + pub baseline: f32, + /// How many prior samples backed the baseline (≥ 4). + pub samples: u32, +} + +/// Window of recent resonance samples. `observe` fires an [`Epiphany`] when a +/// sample exceeds the window baseline by ≥ 1.5× and at least 4 prior samples +/// have accumulated (the anti-Mount-Stupid evidence guard). +#[derive(Debug, Clone)] +pub struct EpiphanyDetector { + window: Vec, + cap: usize, +} + +impl Default for EpiphanyDetector { + fn default() -> Self { + Self::new(16) + } +} + +impl EpiphanyDetector { + /// New detector with a bounded rolling window of `cap` samples. + pub fn new(cap: usize) -> Self { + Self { + window: Vec::new(), + cap: cap.max(4), + } + } + + /// Minimum prior samples before an epiphany can fire. + pub const MIN_SAMPLES: u32 = 4; + /// Multiplier above baseline that counts as surprise. + pub const SURPRISE_FACTOR: f32 = 1.5; + + /// Observe a similarity sample. Returns `Some(Epiphany)` iff the sample + /// exceeds the prior-window baseline by ≥ 1.5× and ≥ 4 prior samples back + /// that baseline. The sample is then folded into the window regardless. + pub fn observe(&mut self, similarity: f32) -> Option { + let n = self.window.len() as u32; + let result = if n >= Self::MIN_SAMPLES { + let baseline = self.window.iter().copied().sum::() / n as f32; + if baseline > 1e-6 && similarity > baseline * Self::SURPRISE_FACTOR { + Some(Epiphany { + similarity, + baseline, + samples: n, + }) + } else { + None + } + } else { + None + }; + + if self.window.len() == self.cap { + self.window.remove(0); + } + self.window.push(similarity); + result + } + + /// Number of samples currently in the window. + pub fn len(&self) -> usize { + self.window.len() + } + + /// True when the window holds no samples. + pub fn is_empty(&self) -> bool { + self.window.is_empty() + } +} + +// ═══════════════════════════════════════════════════════════════════════════ +// GhostEcho + WisdomMarker — the green-flip residue (≤ 32, I-VSA-IDENTITIES) +// ═══════════════════════════════════════════════════════════════════════════ + +/// The 8 named ghost echoes — the wisdom-marker substrate, persistent qualia +/// residue that biases future perception. ≤ 32 named identities, content lives +/// in the store (I-VSA-IDENTITIES). +/// +/// Canonical zero-dep home. Mirrors `thinking_engine::ghosts::GhostType` +/// (an excluded crate that cannot be a contract dependency); the two are to be +/// reconciled when thinking-engine joins the workspace — see +/// `TECH_DEBT.md` TD-GHOST-ECHO-DUP-1. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum GhostEcho { + /// Lingering pull toward a concept / person / thing. + Affinity, + /// Residual clarity from a past insight (the default green-flip residue). + Epiphany, + /// Body-felt echo (tension, warmth, chill). + Somatic, + /// Persistent wonder / awe. + Staunen, + /// Deep knowing that colours all future perception (promoted from + /// repeated Epiphany on the cold path — D-PERSONA-3). + Wisdom, + /// A thought that won't let go (rumination or focus). + Thought, + /// Loss that reshapes the topology. + Grief, + /// A limit discovered, still felt. + Boundary, +} + +/// A wisdom marker: a ghost echo with an intensity that decays asymptotically +/// toward `FLOOR` (0.1) — never to zero (felt_parse:70). +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct WisdomMarker { + pub ghost: GhostEcho, + pub intensity: f32, +} + +impl WisdomMarker { + /// Asymptotic decay floor — a wisdom marker never fully vanishes. + pub const FLOOR: f32 = 0.1; + /// Default per-cycle decay rate (matches the ghost field's slow lingering). + pub const DECAY: f32 = 0.85; + + /// Fresh marker at full intensity. + pub fn fresh(ghost: GhostEcho) -> Self { + Self { + ghost, + intensity: 1.0, + } + } + + /// Intensity after `age` cycles: `max(FLOOR, intensity · DECAY^age)`. + pub fn intensity_at(&self, age: u32) -> f32 { + (self.intensity * Self::DECAY.powi(age as i32)).max(Self::FLOOR) + } + + /// Promote a repeated insight to deep knowing (cold-path, D-PERSONA-3). + pub fn promote_to_wisdom(mut self) -> Self { + self.ghost = GhostEcho::Wisdom; + self + } +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Checklist — HARD (boot gate) vs SOFT (degrade gracefully), green-flip +// ═══════════════════════════════════════════════════════════════════════════ + +/// Whether an item must be green to boot (HARD) or may degrade (SOFT). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GateKind { + /// Must be green for the agent to boot. + Hard, + /// Degrades gracefully if red — route around it (anytime / etiquette). + Soft, +} + +/// One checklist item, verified by the escalation loop rather than a bespoke +/// assert. Starts un-green with a [`CollapseHint::Fanout`] (gather evidence). +#[derive(Debug, Clone)] +pub struct ChecklistItem { + pub name: &'static str, + pub gate: GateKind, + /// Current collapse-hint state — settles to `Flow` on completion. + pub hint: CollapseHint, + /// True once the item settled to Flow AND an epiphany closed it. + pub green: bool, + /// The wisdom residue left by the green-flip, if any. + pub marker: Option, +} + +impl ChecklistItem { + /// A fresh, un-verified item (Fanout = gather evidence first). + pub fn new(name: &'static str, gate: GateKind) -> Self { + Self { + name, + gate, + hint: CollapseHint::Fanout, + green: false, + marker: None, + } + } +} + +/// The boot checklist: a flat, deterministic list of items, each closed by the +/// escalation+epiphany loop. The meta-recipe composes when [`Checklist::all_flow`]. +#[derive(Debug, Clone, Default)] +pub struct Checklist { + pub items: Vec, +} + +impl Checklist { + pub fn new(items: Vec) -> Self { + Self { items } + } + + /// Apply a council verdict (and any epiphany) to the named item. A + /// green-flip happens when the verdict settles to `Flow` AND an epiphany + /// fired — the item then carries a fresh [`GhostEcho::Epiphany`] marker. + /// Returns the marker minted on a green-flip, if any. + pub fn step( + &mut self, + name: &str, + verdict: &CouncilVerdict, + epiphany: Option, + ) -> Option { + let item = self.items.iter_mut().find(|i| i.name == name)?; + item.hint = verdict.hint; + if verdict.hint == CollapseHint::Flow && epiphany.is_some() { + let marker = WisdomMarker::fresh(GhostEcho::Epiphany); + item.green = true; + item.marker = Some(marker); + Some(marker) + } else { + None + } + } + + /// Let-it-crash: a green item went red at runtime → re-escalate (Fanout) + /// and drop its green flag. The supervisor restarts / escalates from here. + pub fn mark_red(&mut self, name: &str) { + if let Some(item) = self.items.iter_mut().find(|i| i.name == name) { + item.green = false; + item.hint = CollapseHint::Fanout; + item.marker = None; + } + } + + /// Boot gate: every HARD item is green. SOFT items may still be red. + pub fn boot_ready(&self) -> bool { + self.items + .iter() + .filter(|i| i.gate == GateKind::Hard) + .all(|i| i.green) + } + + /// Composition gate: every item (hard + soft) has settled to `Flow` → + /// the meta-recipe (D-PERSONA-2) composes. + pub fn all_flow(&self) -> bool { + !self.items.is_empty() && self.items.iter().all(|i| i.hint == CollapseHint::Flow) + } + + /// True when at least one SOFT item is not green — boot proceeds, but the + /// runtime routes around the degraded capability. + pub fn degraded(&self) -> bool { + self.items + .iter() + .any(|i| i.gate == GateKind::Soft && !i.green) + } +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Tests +// ═══════════════════════════════════════════════════════════════════════════ + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fanout_width_clamps_and_scales() { + assert_eq!(fanout_width(4.0, 0.0), 4); + assert_eq!(fanout_width(4.0, 1.0), 6); // 4 * 1.5 + assert_eq!(fanout_width(0.0, 0.0), 1); // clamp low + assert_eq!(fanout_width(100.0, 1.0), 30); // clamp high + } + + #[test] + fn rung_delta_elevates_and_descends() { + assert_eq!(rung_delta(0.6, 0.3), 1); // emergent + incoherent → elevate + assert_eq!(rung_delta(0.05, 0.9), -1); // coherent + settled → descend + assert_eq!(rung_delta(0.5, 0.5), 0); // neither + } + + #[test] + fn is_split_detects_disagreement() { + assert!(is_split([0.8, 0.4, 0.45], 0.7, 0.5)); // one high, one low + assert!(!is_split([0.6, 0.6, 0.6], 0.7, 0.5)); // consensus, no split + } + + #[test] + fn council_picks_majority_and_amplifies_on_split() { + // Guardian dominates and the spread is a split → Fanout, amplified. + let v = InnerCouncil::deliberate([0.8, 0.4, 0.45]); + assert_eq!(v.hint, CollapseHint::Fanout); + assert!(v.split); + assert!((v.confidence - (0.8f32 * 1.2)).abs() < 1e-5); + + // Consensus around Balanced → Flow, no amplification. + let v = InnerCouncil::deliberate([0.5, 0.55, 0.62]); + assert_eq!(v.hint, CollapseHint::Flow); + assert!(!v.split); + assert!((v.confidence - 0.62).abs() < 1e-5); + } + + #[test] + fn from_signals_settles_when_trusted_and_flowing() { + // High trust, good flow, low load → Balanced wins → Flow. + let v = InnerCouncil::from_signals(0.9, 0.8, 0.9, 0.05); + assert_eq!(v.hint, CollapseHint::Flow); + } + + #[test] + fn from_signals_fans_out_when_untrusted_and_loaded() { + // Low trust, high load → Guardian wins → Fanout. + let v = InnerCouncil::from_signals(0.1, 0.5, 0.3, 0.9); + assert_eq!(v.hint, CollapseHint::Fanout); + } + + #[test] + fn epiphany_needs_window_and_surprise() { + let mut d = EpiphanyDetector::new(8); + // Cold start: < 4 samples → never fires even on a spike. + assert!(d.observe(0.2).is_none()); + assert!(d.observe(0.2).is_none()); + assert!(d.observe(0.2).is_none()); + assert!(d.observe(0.2).is_none()); // now 4 samples in window + // baseline ≈ 0.2; 0.2 * 1.5 = 0.3 → a 0.5 spike fires. + let e = d.observe(0.5).expect("epiphany should fire"); + assert_eq!(e.samples, 4); + assert!(e.similarity > e.baseline * 1.5); + // A non-spike does not fire. + assert!(d.observe(0.25).is_none()); + } + + #[test] + fn wisdom_marker_decays_to_floor_never_zero() { + let m = WisdomMarker::fresh(GhostEcho::Epiphany); + assert!((m.intensity_at(0) - 1.0).abs() < 1e-6); + assert!(m.intensity_at(5) < 1.0); + // After many cycles it pins to the floor, never below. + assert!((m.intensity_at(1000) - WisdomMarker::FLOOR).abs() < 1e-6); + assert!(m.intensity_at(1000) >= WisdomMarker::FLOOR); + assert_eq!(m.promote_to_wisdom().ghost, GhostEcho::Wisdom); + } + + #[test] + fn checklist_green_flip_on_flow_plus_epiphany() { + let mut cl = Checklist::new(vec![ + ChecklistItem::new("contracts", GateKind::Hard), + ChecklistItem::new("caps", GateKind::Soft), + ]); + assert!(!cl.boot_ready()); + + let flow = CouncilVerdict { + hint: CollapseHint::Flow, + confidence: 0.9, + split: false, + }; + let epiphany = Epiphany { + similarity: 0.6, + baseline: 0.3, + samples: 4, + }; + // Flow without epiphany does NOT green-flip. + assert!(cl.step("contracts", &flow, None).is_none()); + assert!(!cl.boot_ready()); + // Flow WITH epiphany green-flips and mints an Epiphany ghost. + let marker = cl.step("contracts", &flow, Some(epiphany)).expect("green-flip"); + assert_eq!(marker.ghost, GhostEcho::Epiphany); + assert!(cl.boot_ready()); // only HARD item needs to be green + assert!(cl.degraded()); // SOFT "caps" still red → degrade, route around + assert!(!cl.all_flow()); // soft item hasn't settled + } + + #[test] + fn checklist_let_it_crash_reescalates() { + let mut cl = Checklist::new(vec![ChecklistItem::new("store", GateKind::Hard)]); + let flow = CouncilVerdict { + hint: CollapseHint::Flow, + confidence: 1.0, + split: false, + }; + let e = Epiphany { similarity: 0.6, baseline: 0.3, samples: 5 }; + cl.step("store", &flow, Some(e)); + assert!(cl.boot_ready()); + // Runtime crash: the green item goes red → re-escalate to Fanout. + cl.mark_red("store"); + assert!(!cl.boot_ready()); + assert_eq!(cl.items[0].hint, CollapseHint::Fanout); + } +} diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index 8ffd59b5..21329b3e 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -35,6 +35,7 @@ //! speed ratio. Distinct from `collapse_gate` per topology I-4. pub mod a2a_blackboard; +pub mod atoms; pub mod auth; pub mod cam; pub mod cognitive_shader; @@ -43,6 +44,7 @@ pub mod container; pub mod crystal; pub mod cycle_accumulator; pub mod distance; +pub mod escalation; pub mod exploration; pub mod external_membrane; pub mod faculty; @@ -70,6 +72,8 @@ pub use qualia::{ QUALIA_DIMS, QUALIA_I4_DIMS, QUALIA_I4_LABELS, ZERO, }; pub mod reasoning; +pub mod recipe_kernels; +pub mod recipes; pub mod repository; pub mod scenario; pub mod sensorium; diff --git a/crates/lance-graph-contract/src/quorum.rs b/crates/lance-graph-contract/src/quorum.rs new file mode 100644 index 00000000..11e5434d --- /dev/null +++ b/crates/lance-graph-contract/src/quorum.rs @@ -0,0 +1,382 @@ +//! Per-axis quorum projection — D-ATOM-3 of `atom-mailbox-substrate-v1`. +//! +//! # Concept +//! +//! A bipolar dichotomy does **not** yield its pole assignment for free. To +//! place a measurement between two poles you need a **quorum**: a structured +//! agreement among the `InnerCouncil` archetypes (and, optionally, the wider +//! `a2a_blackboard` `support[u16;4]` + `dissonance` field) that the signal +//! belongs to the positive vs negative half of the axis. +//! +//! The output of a successful quorum is an [`AxisProjection`]: +//! +//! ```text +//! AxisProjection { position: i8, confidence: f32 } +//! ↑ ↑ +//! I4 pole (−8 … +7) quorum agreement ∈ [0, 1] +//! = NARS frequency = NARS confidence +//! (normalised) +//! ``` +//! +//! This maps directly onto **NARS truth per axis**: `(frequency ≈ position +//! normalised to [0,1], confidence ≈ quorum strength)`. The I4 integer is the +//! coarse pole; the f32 confidence is how strongly the quorum agrees. +//! +//! # Split quorums are Contradictions — NEVER averaged +//! +//! When [`InnerCouncil::deliberate`] fires `CouncilVerdict::split = true`, +//! the projection is **contested**: the majority pole is recorded, but +//! [`AxisProjection::is_contested`] returns `true`. The caller MUST hand the +//! contested projection off to the counterfactual path (D-ATOM-4) rather than +//! averaging away the disagreement. Averaging a split launders false +//! confidence — the cardinal OSINT sin. +//! +//! # Tiering non-decision (architectural note) +//! +//! `EPIPHANIES.md` E-LADDER-SERVES-MAILBOX §5 explicitly chose the +//! **counterfactual-fork** strategy (D-ATOM-4) OVER quorum-tiering. This +//! module therefore exposes *only* the projection surface (`AxisProjection`, +//! `quorum_project`, `is_contested`) and hands contested cases off to +//! D-ATOM-4. The quorum does **not** widen through tiers — that complexity +//! was intentionally rejected. +//! +//! # BLOCKED +//! +//! - `atoms` axis type (D-ATOM-1, parallel — referenced below as the intended +//! `AxisId` or equivalent) — not yet defined; this module uses `u8` as a +//! placeholder index and marks every call-site. +//! - The exact `a2a_blackboard::Blackboard` constructor / `next_round` reset +//! policy when used *per-axis* vs *per-round* is unclear from the source; +//! the wide-quorum path below is therefore fully `BLOCKED`. +//! +//! Zero-dep crate — no external dependencies beyond `crate::escalation`. + +use crate::escalation::{CouncilVerdict, InnerCouncil}; + +// ═══════════════════════════════════════════════════════════════════════════ +// AxisProjection — the quorum output +// ═══════════════════════════════════════════════════════════════════════════ + +/// The result of projecting a set of signals onto one bipolar axis via quorum. +/// +/// Semantically this is **NARS truth per axis**: +/// +/// | Field | NARS role | Range | Semantics | +/// |---|---|---|---| +/// | `position` | frequency | `−8 … +7` | I4 pole; negative = "−" half, positive = "+" half of the dichotomy; `0` = indeterminate | +/// | `confidence` | confidence | `[0, 1]` | Quorum agreement strength; low on a split | +/// +/// A `position` of `0` with any confidence indicates the quorum could not +/// place the signal on either pole (degenerate input). This is distinct from a +/// *contested* projection (where `is_contested` returns `true`): `position ≠ 0` +/// but the council split. +/// +/// # Relationship to I4-32D atoms (D-ATOM-1) +/// +/// // BLOCKED: D-ATOM-1 has not yet defined the atom catalogue or the `I4x32` +/// // pack/unpack API. Once D-ATOM-1 lands, each atom slot in `I4x32` is filled +/// // by one `AxisProjection::position` value, with the accompanying +/// // `confidence` stored alongside for NARS downstream use. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct AxisProjection { + /// I4 pole on this axis (`−8 … +7`). Positive = "+" pole; negative = "−" + /// pole; `0` = indeterminate (quorum could not resolve). + pub position: i8, + /// Quorum agreement in `[0, 1]`. Carries NARS-confidence semantics: + /// high = strong quorum agreement; low = weak or contested. + pub confidence: f32, + /// True when the `InnerCouncil` deliberation produced `split = true`, + /// meaning the projection is contested and MUST be handed to the + /// counterfactual path (D-ATOM-4), not averaged. + contested: bool, +} + +impl AxisProjection { + /// Construct a settled (non-contested) projection. + #[inline] + pub fn settled(position: i8, confidence: f32) -> Self { + Self { + position: position.clamp(-8, 7), + confidence: confidence.clamp(0.0, 1.0), + contested: false, + } + } + + /// Construct a contested projection. The majority `position` is recorded, + /// but callers MUST check [`is_contested`] and route to D-ATOM-4. + #[inline] + pub fn contested(position: i8, confidence: f32) -> Self { + Self { + position: position.clamp(-8, 7), + confidence: confidence.clamp(0.0, 1.0), + contested: true, + } + } + + /// True when the underlying quorum was a split — the projection is + /// **contested** and MUST NOT be averaged into a final atom value. + /// + /// A contested projection is a **Contradiction** (see + /// `E-LADDER-SERVES-MAILBOX §3`): the majority pole is stored but the + /// minority pole is the counterfactual mantissa owned by D-ATOM-4. + /// Callers that ignore this flag and treat the projection as settled are + /// laundering false confidence — this is the cardinal OSINT sin. + #[inline] + pub fn is_contested(&self) -> bool { + self.contested + } + + /// NARS frequency: `position` mapped to `[0, 1]` as + /// `(position + 8) / 15.0`. Positive pole → frequency > 0.5; negative + /// pole → frequency < 0.5; indeterminate (0) → ≈ 0.533. + /// + /// This is a lossy normalisation; callers that need the raw I4 value + /// should use `position` directly. + #[inline] + pub fn nars_frequency(&self) -> f32 { + (self.position as f32 + 8.0) / 15.0 + } +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Signal type — the raw per-axis observables fed into the quorum +// ═══════════════════════════════════════════════════════════════════════════ + +/// One contributing signal for a single axis quorum. +/// +/// Signals are the raw per-axis observables — e.g. trust, humility, flow, +/// load from a [`crate::mul::MulAssessment`] or from a +/// `crate::a2a_blackboard::BlackboardEntry` — normalised to `[0, 1]`. +/// +/// # BLOCKED +/// +/// // BLOCKED: D-ATOM-1 — axis identity type (`AxisId` or equivalent in +/// // `contract::atoms`) is not yet defined. Until D-ATOM-1 lands, the +/// // *which axis* question is answered by the caller; this struct carries +/// // only the scalar signal payload. +#[derive(Debug, Clone, Copy)] +pub struct AxisSignal { + /// Trust component (see [`InnerCouncil::from_signals`]) ∈ `[0, 1]`. + pub trust: f32, + /// Humility / DK component ∈ `[0, 1]`. + pub humility: f32, + /// Flow component ∈ `[0, 1]`. + pub flow: f32, + /// Allostatic load ∈ `[0, 1]`. + pub load: f32, + /// Optional raw polarity hint: positive means the signal tilts toward + /// the "+" pole; negative toward the "−" pole. When `None` the polarity + /// is inferred entirely from the council deliberation. + pub polarity_hint: Option, +} + +// ═══════════════════════════════════════════════════════════════════════════ +// quorum_project — the core per-axis projection function +// ═══════════════════════════════════════════════════════════════════════════ + +/// Project a set of contributing `signals` onto one bipolar axis via the +/// `InnerCouncil` quorum, returning an [`AxisProjection`]. +/// +/// # Mechanism +/// +/// 1. Each signal is fed into [`InnerCouncil::from_signals`] to obtain a +/// per-signal [`CouncilVerdict`]. +/// 2. The verdicts are aggregated: the mean confidence across all signals +/// serves as the quorum confidence; the dominant polarity is derived from +/// the weighted polarity hints (or, if absent, from the Balanced/Flow vs +/// Guardian/Catalyst ratio). +/// 3. If **any** single verdict is `split = true`, the aggregate is marked +/// contested (see [`AxisProjection::is_contested`]). A split is +/// **not averaged** — the majority pole is committed and the minority +/// handed to D-ATOM-4. +/// 4. The resulting I4 `position` is clamped to `−8 … +7`; `confidence` +/// is the mean quorum agreement, ×1.2 amplified (clamped to 1.0) on a +/// split (mirroring `InnerCouncil::deliberate` split amplification — +/// disagreement IS the learning signal, per +/// `E-LADDER-SERVES-MAILBOX §3`). +/// +/// # Empty signal set +/// +/// If `signals` is empty the function returns an indeterminate projection +/// (`position = 0, confidence = 0.0, contested = false`). +/// +/// # BLOCKED +/// +/// // BLOCKED: D-ATOM-1 — the axis identity (`AxisId`) type is not yet +/// // defined. The caller currently selects the axis implicitly by passing +/// // the right signals; once D-ATOM-1 lands this should accept an +/// // `AxisId` parameter. +/// +/// // BLOCKED: wide-quorum path — for multi-expert quorum the +/// // `a2a_blackboard::Blackboard::support[u16;4]` + `dissonance` fields +/// // are the right wide-quorum substrate, but the per-axis vs per-round +/// // reset policy for `Blackboard::next_round` is unclear. The wide-quorum +/// // variant is deferred until D-ATOM-3 implementation. +pub fn quorum_project(signals: &[AxisSignal], council: &InnerCouncil) -> AxisProjection { + todo!( + "D-ATOM-3 — implement: aggregate InnerCouncil verdicts from signals, \ + derive I4 position from polarity hints + majority, mark contested on any split; \ + BLOCKED: AxisId (D-ATOM-1), wide-quorum Blackboard reset policy" + ); + // Silence unused-variable warnings in the scaffold. + #[allow(unused_variables)] + let _ = (signals, council); +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Wide-quorum variant (a2a_blackboard path) — BLOCKED +// ═══════════════════════════════════════════════════════════════════════════ + +/// Project one axis using the wide `a2a_blackboard` quorum +/// (`BlackboardEntry::support[u16;4]` + `dissonance`). +/// +/// The wide-quorum path is the Layer-1 A2A complement to the three-archetype +/// `InnerCouncil`: where `InnerCouncil` is a local (sync, 3-archetype) quorum, +/// `quorum_project_blackboard` aggregates across all posted +/// `BlackboardEntry` values on the current round, using `dissonance` as the +/// split signal. +/// +/// # BLOCKED +/// +/// // BLOCKED: `a2a_blackboard::Blackboard` constructor / `next_round` reset +/// // policy when sliced per-axis is unclear from the source. The `support` +/// // field carries top-K atom indices (`[u16; 4]`) but the mapping from +/// // support-slot semantics to the bipolar axis pole is not yet specified +/// // (D-ATOM-1 defines the atom catalogue this indexes into). +/// // +/// // BLOCKED: D-ATOM-1 — `AxisId` type needed to filter `BlackboardEntry` +/// // entries by axis. +pub fn quorum_project_blackboard( + _bb: &crate::a2a_blackboard::Blackboard, + // BLOCKED: AxisId parameter once D-ATOM-1 lands. +) -> AxisProjection { + todo!( + "D-ATOM-3 wide-quorum path — BLOCKED: per-axis Blackboard slice semantics \ + + AxisId (D-ATOM-1)" + ) +} + +// ═══════════════════════════════════════════════════════════════════════════ +// ContestHandler — handoff contract to D-ATOM-4 +// ═══════════════════════════════════════════════════════════════════════════ + +/// Outcome of inspecting a contested projection. +/// +/// When [`AxisProjection::is_contested`] returns `true` the caller uses this +/// enum to decide the handoff strategy. This enum is the type-level seam +/// between D-ATOM-3 (quorum projection) and D-ATOM-4 (counterfactual +/// mantissa). **D-ATOM-3 never resolves the contest itself** — the tiering +/// non-decision (see module-level note) means resolution belongs to D-ATOM-4. +/// +/// # Staging (mirrors `E-LADDER-SERVES-MAILBOX §5`) +/// +/// - **v1 (now):** `DropMinority` — commit the majority pole, drop the +/// minority. No counterfactual record. +/// - **v2 (D-ATOM-4 deposit):** `DepositMantissa` — commit the majority +/// pole, deposit the minority as a `CausalEdge64` −6 (Counterfactual) +/// nibble. +/// - **v3 (D-ATOM-4 mailbox + revision):** `SpawnCounterfactual` — full +/// ghost-tier test mailbox + `awareness.revise` on minority-wins. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ContestHandler { + /// v1 — commit majority, discard minority silently. + DropMinority, + /// v2 — commit majority, deposit minority as `CausalEdge64 −6` nibble + /// (D-ATOM-4 deposit phase). The 4-bit mantissa is the road-not-taken. + DepositMantissa, + /// v3 — spawn a ghost-tier counterfactual mailbox; if the minority pole + /// later beats the committed pole's free energy, call `awareness.revise` + /// (D-ATOM-4 full phase, gated on β headroom and Staunen threshold). + SpawnCounterfactual, +} + +/// Resolve a contested [`AxisProjection`] according to the given +/// [`ContestHandler`] strategy. +/// +/// Returns `(committed_projection, minority_pole)` where `committed_projection` +/// has `contested = false` (the contest is resolved by policy, not averaged) +/// and `minority_pole` is the I4 value of the road-not-taken (negation of +/// the committed position for a pure split; the caller may use this to seed +/// D-ATOM-4's counterfactual record). +/// +/// When `handler == ContestHandler::DropMinority` the minority pole is still +/// returned (for diagnostic purposes) but no counterfactual record is created — +/// that is D-ATOM-4's responsibility, not this module's. +/// +/// # Panics +/// +/// Does not panic; if `projection` is not contested this is a no-op and +/// returns the projection unchanged with `minority_pole = 0`. +pub fn resolve_contest( + projection: AxisProjection, + _handler: ContestHandler, +) -> (AxisProjection, i8) { + todo!( + "D-ATOM-3 — implement resolve_contest: strip the contested flag from \ + the majority projection, compute minority pole; for DepositMantissa + \ + SpawnCounterfactual the actual mantissa deposit / mailbox spawn is \ + delegated to D-ATOM-4 (this function returns the raw minority pole for \ + the caller to pass through); BLOCKED: D-ATOM-4 API" + ) +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Tests (scaffold — bodies todo!()) +// ═══════════════════════════════════════════════════════════════════════════ + +#[cfg(test)] +mod tests { + use super::*; + + /// Settled projection carries the expected NARS truth fields. + #[test] + fn settled_projection_fields() { + let p = AxisProjection::settled(3, 0.8); + assert_eq!(p.position, 3); + assert!((p.confidence - 0.8).abs() < 1e-6); + assert!(!p.is_contested()); + } + + /// Contested projection sets the flag correctly. + #[test] + fn contested_projection_flag() { + let p = AxisProjection::contested(-4, 0.6); + assert!(p.is_contested()); + assert_eq!(p.position, -4); + } + + /// I4 position is clamped to −8 … +7. + #[test] + fn position_clamps_to_i4_range() { + assert_eq!(AxisProjection::settled(100, 1.0).position, 7); + assert_eq!(AxisProjection::settled(-100, 1.0).position, -8); + } + + /// nars_frequency maps the I4 range to [0, 1]. + #[test] + fn nars_frequency_range() { + let neg = AxisProjection::settled(-8, 1.0); + let pos = AxisProjection::settled(7, 1.0); + assert!((neg.nars_frequency() - 0.0).abs() < 1e-6); + assert!((pos.nars_frequency() - 1.0).abs() < 1e-6); + // Midpoint (position 0) should be > 0.5 (asymmetric I4 range). + let mid = AxisProjection::settled(0, 1.0); + assert!(mid.nars_frequency() > 0.5); + } + + /// quorum_project with empty signals → indeterminate (todo! body not yet + /// reached — this test documents the *intended* contract only). + #[test] + #[should_panic(expected = "D-ATOM-3")] + fn quorum_project_is_scaffolded() { + let _ = quorum_project(&[], &InnerCouncil); + } + + /// resolve_contest is scaffolded — panics with D-ATOM-3 message. + #[test] + #[should_panic(expected = "D-ATOM-3")] + fn resolve_contest_is_scaffolded() { + let p = AxisProjection::contested(2, 0.7); + let _ = resolve_contest(p, ContestHandler::DropMinority); + } +} diff --git a/crates/lance-graph-contract/src/recipe.rs b/crates/lance-graph-contract/src/recipe.rs new file mode 100644 index 00000000..ba6418a2 --- /dev/null +++ b/crates/lance-graph-contract/src/recipe.rs @@ -0,0 +1,416 @@ +//! Composition layer: thinking-style recipes and persona recipes. +//! +//! This module sits above the atom layer (`contract::atoms`, D-ATOM-1) and +//! below the dispatch layer (`contract::jit`). It implements the three-layer +//! hierarchy described in `EPIPHANIES.md` E-LADDER-SERVES-MAILBOX §2: +//! +//! ```text +//! atoms (I4-32D bipolar) ─── composition ───► StyleRecipe +//! │ composition +//! ▼ +//! PersonaRecipe (+ β, thresholds, purpose) +//! │ compile +//! ▼ +//! KernelHandle (Cranelift fused kernel) +//! ``` +//! +//! **JIT placement (the key design decision):** the JIT target is the +//! *recipe*, not the per-atom dot. A single 32-D i4 dot product is one SIMD +//! sequence — Cranelift compile overhead is on the order of microseconds and +//! only amortises when many fused operations are compiled together. At the +//! recipe level the compiler can fuse the weighted-atom summation, the +//! threshold comparisons, the β-scaled sampling, and any style-specific +//! post-processing into a single native code path, hiding the per-recipe +//! overhead across millions of invocations. Compiling the atom-dot alone +//! would pay the Cranelift overhead for each individual dot product — a net +//! loss. +//! +//! **Elixir-style open/closed split (hot-load protocol):** +//! - `add-atom` = *data* change (the atom catalogue, D-ATOM-1). No +//! recompilation needed: a new atom is a new row in the basis register. +//! - `add-style` / `add-persona` = *template* change. A new +//! [`RecipeTemplate`] must be registered via [`register_recipe`] so the +//! [`jit::StyleRegistry`] can produce a new [`jit::KernelHandle`] at next +//! activation. This keeps the hot path closed to structural mutation while +//! remaining open to new personas/styles at the template boundary. + +// BLOCKED: `atoms::Atom` and `atoms::I4x32` — the concrete I4-32D atom +// carrier type and the `Atom` enum/catalogue are being scaffolded in +// parallel as D-ATOM-1 (`contract::atoms`). All references below use +// the *intended names* (`atoms::Atom`, `atoms::I4x32`) as forward +// declarations; they will become real imports once D-ATOM-1 lands. + +use crate::jit::{JitError, KernelHandle}; + +// --------------------------------------------------------------------------- +// Forward stubs for D-ATOM-1 types (BLOCKED) +// --------------------------------------------------------------------------- + +/// Placeholder for the bipolar I4-32D atom vector type (`atoms::I4x32`). +/// +/// BLOCKED: the real type is defined in D-ATOM-1 (`contract::atoms`). +/// Replace this stub with `use crate::atoms::I4x32;` once that module +/// lands. All arithmetic on this type (dot product, weighted sum, +/// clamped i4 accumulation) lives in `contract::atoms`, not here. +pub type I4x32Stub = [i8; 32]; + +/// Placeholder for a single atom identifier from the basis catalogue +/// (`atoms::Atom`). +/// +/// BLOCKED: the real type is defined in D-ATOM-1. +/// Replace with `use crate::atoms::Atom;`. +pub type AtomStub = u8; + +// --------------------------------------------------------------------------- +// StyleRecipe +// --------------------------------------------------------------------------- + +/// A *thinking style* as an I4-32D **composition** over atoms. +/// +/// A `StyleRecipe` is **not** an atomic fingerprint. Per +/// `EPIPHANIES.md` E-LADDER-SERVES-MAILBOX §2, styles such as +/// "Kant", "Schopenhauer", or "bookkeeping-savant" are **weighted +/// combinations** of the orthogonal bipolar atoms from the I4-32D +/// basis (`contract::atoms::Atom`). The style itself carries no +/// intrinsic identity — its semantics are entirely determined by how +/// it weights the underlying atom poles. +/// +/// # Composition semantics +/// +/// Each entry in `weights` pairs an atom with a signed i4 weight: +/// positive weight activates the `+` pole, negative weight activates +/// the `−` pole, zero weight leaves the atom inert in this style. +/// The resulting composition vector is the clamped i4 accumulation +/// `Σ weight_k × atom_k` in the 32-dimensional bipolar space. +/// +/// # Invariants (from I-VSA-IDENTITIES) +/// +/// - `weights` must not bundle more than `√32 / 4 ≈ 1` coherent +/// super-imposed styles at a time (VSA capacity limit at 32 dims). +/// - Each `AtomStub` index must reference a valid atom in the basis +/// catalogue (D-ATOM-1); unrecognised indices are BLOCKED on +/// D-ATOM-1. +/// - This type is `Copy`-free: cloning is explicit to prevent +/// accidental duplication in the hot loop. +/// +/// # Relationship to the atom layer +/// +/// BLOCKED on D-ATOM-1: once `atoms::I4x32` is defined, the +/// `composition_vector` field should store the *pre-computed* +/// weighted-sum vector directly (a materialised `atoms::I4x32`) +/// rather than the sparse `weights` list, so recipe evaluation +/// reduces to a single dot product. +#[derive(Debug, Clone)] +pub struct StyleRecipe { + /// Human-readable style name (e.g. `"Kant"`, `"bookkeeping-savant"`). + pub name: &'static str, + + /// Sparse atom weights: `(atom_id, i4_weight)` pairs. + /// + /// BLOCKED on D-ATOM-1: `AtomStub` → `atoms::Atom` once available. + /// Weights should fit within the i4 range `[−8, +7]`; values outside + /// this range will be clamped during compilation. + pub weights: &'static [(AtomStub, i8)], + + /// Pre-computed I4-32D composition vector. + /// + /// BLOCKED on D-ATOM-1: `I4x32Stub` → `atoms::I4x32`. + /// Populated at registration time by folding `weights` into the + /// basis; `None` until the atom layer is live. + pub composition: Option, +} + +// --------------------------------------------------------------------------- +// PersonaRecipe +// --------------------------------------------------------------------------- + +/// β (explore/exploit temperature knob) for a persona. +/// +/// Controls the sampling temperature of the underlying +/// wisdom↔Staunen axis (per E-LADDER-SERVES-MAILBOX §4). Free +/// energy self-regulates around this setpoint at runtime; `beta` is +/// the *initial* / *nominal* setpoint, not a hard ceiling. +/// +/// # Calibration guidance +/// +/// | Persona type | Nominal β | Rationale | +/// |---|---|---| +/// | `business` | `Beta::Cold` | Exploit: checkboxes, GoBD rules, bounded fan-out | +/// | `chat` | `Beta::Warm` | Explore: episodic modeling, witness-arc self-state | +/// | `osint` | `Beta::Annealing { start, floor }` | Hot→cold: hypothesis mailboxes, untrusted-source gates | +/// +/// The `WisdomMarker` 0.1 floor (D-PERSONA-1) translates to a +/// minimum temperature that prevents annealing to absolute zero +/// (φ-1 humility invariant). +#[derive(Debug, Clone, Copy)] +pub enum Beta { + /// Cold, exploitation-biased temperature. Suitable for + /// rule-governed, high-stakes business personas. + Cold, + + /// Warm, exploration-biased temperature. Suitable for + /// conversational or creative personas. + Warm, + + /// Simulated-annealing schedule: begin at `start` and cool toward + /// `floor`. The floor must be ≥ 0.1 (WisdomMarker minimum). + /// Suitable for OSINT / hypothesis-generation personas. + Annealing { + /// Initial (hot) temperature, in `(0.0, 1.0]`. + start: f32, + /// Minimum temperature floor, ≥ 0.1. + floor: f32, + }, +} + +/// A *persona* as a composition of [`StyleRecipe`]s, plus thresholds, +/// purpose metadata, and the explore/exploit temperature knob (`β`). +/// +/// A `PersonaRecipe` is **not** a container (per I-VSA-IDENTITIES: a +/// persona is a Layer-2 dispatch policy over one substrate, not a new +/// structural layer). It answers: *given the current mailbox state, +/// which styles activate, at what threshold, and with what sampling +/// temperature?* +/// +/// # Composition semantics +/// +/// Each entry in `styles` pairs a [`StyleRecipe`] reference with a +/// normalised weight in `[0.0, 1.0]`. At recipe-compile time the +/// weighted-style blend is fused into a single [`jit::KernelHandle`] +/// via [`RecipeTemplate::compile`]. +/// +/// # Threshold semantics +/// +/// - `commit_threshold` — below this free-energy value F, the mailbox +/// commits without escalation (maps to the `F < 0.2` Commit gate in +/// The Click). +/// - `escalate_threshold` — above this value, a `FailureTicket` is +/// raised (maps to `F > 0.8`). +/// +/// # Purpose +/// +/// A short human-readable string describing the persona's intended +/// domain (e.g. `"DACH bookkeeping, GoBD-compliant"`, +/// `"open-source intelligence synthesis"`). Stored for introspection; +/// not used at runtime. +/// +/// # Relationship to the mailbox +/// +/// The persona decides *what to fan out as mailboxes* and *where β +/// sits*; it does NOT own the mailbox (sea-star topology, E-BATON-1). +/// Three canonical personas are three β-policies over one substrate: +/// `business` (cold), `chat` (warm), `osint` (annealing). +#[derive(Debug, Clone)] +pub struct PersonaRecipe { + /// Human-readable persona name (e.g. `"business"`, `"osint"`). + pub name: &'static str, + + /// Weighted blend of [`StyleRecipe`]s. + /// + /// `(style_recipe, normalised_weight)` pairs. Weights need not sum + /// to 1.0 here; they are normalised at compile time inside + /// [`RecipeTemplate::compile`]. + pub styles: &'static [(&'static StyleRecipe, f32)], + + /// Free-energy commit threshold (dimensionless, `[0.0, 1.0]`). + /// + /// Corresponds to the `F < commit_threshold` Commit gate in The Click. + /// Default: `0.2`. + pub commit_threshold: f32, + + /// Free-energy escalate threshold (dimensionless, `[0.0, 1.0]`). + /// + /// Corresponds to the `F > escalate_threshold` FailureTicket gate. + /// Default: `0.8`. + pub escalate_threshold: f32, + + /// Explore/exploit temperature knob (β). + pub beta: Beta, + + /// Short prose description of this persona's intended domain. + pub purpose: &'static str, +} + +// --------------------------------------------------------------------------- +// RecipeTemplate — the Cranelift / JIT hook +// --------------------------------------------------------------------------- + +/// The Cranelift/JIT hook: a recipe compiled to a fused +/// [`jit::KernelHandle`]. +/// +/// # Why the recipe, not the atom-dot, is the JIT target +/// +/// A single 32-D i4 dot product is one short SIMD sequence — on AVX-512 +/// it fits in roughly 4 instructions. Cranelift's compilation overhead +/// is measured in microseconds per function. At the per-atom-dot +/// granularity the compile cost would *never* amortise: millions of +/// individual one-instruction-sequence compilations would be strictly +/// worse than a hand-written scalar fallback. +/// +/// At the **fused-recipe level** the compiler can emit a single native +/// function that: +/// 1. Loads the 32-D i4 atom query vector (hot in a register after +/// `I4x32::from_mailbox_state`). +/// 2. Applies the style-weight blend as a series of fused multiply-add +/// SIMD lanes (VPDPBSSD / VPDPBUSD on AVX-VNNI, or vmlal on NEON). +/// 3. Applies the β-scaled threshold comparisons. +/// 4. Emits the `CollapseHint` variant inline (no branch to a separate +/// dispatch table). +/// +/// This is the same amortisation argument behind JIT-compiling SQL +/// query plans rather than individual predicates: the fixed overhead is +/// paid once per template, not once per row. +/// +/// # Registration and hot-load lifecycle +/// +/// A `RecipeTemplate` is created once per persona and registered with +/// the [`jit::StyleRegistry`] via [`register_recipe`]. The registry +/// holds the [`jit::KernelHandle`] and serves it to the hot path on +/// every mailbox activation. When the persona is updated (new styles +/// added, thresholds changed), a new `RecipeTemplate` is registered +/// under the same name — the registry evicts the old handle and +/// re-compiles. This is the "add-style/persona = template" half of +/// the Elixir-style open/closed split. +/// +/// # Relationship to `jit::StyleRegistry` +/// +/// BLOCKED: `StyleRegistry::get_kernel` currently expects a +/// `ThinkingStyle` argument (the existing 36-enum surface in +/// `contract::thinking`). To accept a `RecipeTemplate` the registry +/// must gain a `register_recipe` / `get_recipe_kernel` entry-point. +/// Until `StyleRegistry`'s API is extended this scaffolding leaves +/// the registration path as `todo!()`. Do NOT guess an extension; +/// leave the BLOCKED marker. +#[derive(Debug, Clone)] +pub struct RecipeTemplate { + /// The persona this template compiles. + pub persona: &'static PersonaRecipe, + + /// Parameter hash used for [`jit::KernelHandle`] cache keying. + /// + /// Derived from the persona's style weights, thresholds, and β at + /// registration time. If the persona changes, this hash changes, + /// causing the registry to evict the old kernel. + /// + /// BLOCKED on D-ATOM-1: the hash must incorporate the `I4x32` + /// composition vectors of each constituent style; until those + /// vectors exist, hash computation is `todo!()`. + pub param_hash: u64, +} + +impl RecipeTemplate { + /// Compile this recipe template into a fused [`jit::KernelHandle`]. + /// + /// Calls into the [`jit::StyleRegistry`] / [`jit::JitCompiler`] + /// chain to emit a Cranelift-compiled native function that: + /// + /// 1. Accepts a 32-D i4 query vector (`atoms::I4x32`, D-ATOM-1). + /// 2. Evaluates the weighted-style blend over that vector. + /// 3. Returns a `CollapseHint` variant appropriate to the persona's + /// β and threshold configuration. + /// + /// # Errors + /// + /// Returns [`jit::JitError`] if Cranelift compilation fails or if + /// the required target feature (AVX-512 / NEON) is unavailable on + /// the current host. + /// + /// # BLOCKED + /// + /// - BLOCKED on D-ATOM-1: `atoms::I4x32` composition vectors are + /// required to materialise the fused kernel body. + /// - BLOCKED on `StyleRegistry` API extension: the current + /// `StyleRegistry::get_kernel(&self, style: ThinkingStyle)` takes + /// an enum variant, not a recipe template. A `register_recipe` / + /// `get_recipe_kernel` surface must be added before this can be + /// wired (see type-level doc). + pub fn compile(&self) -> Result { + // BLOCKED: D-ATOM-1 (I4x32 composition vectors) + + // BLOCKED: StyleRegistry API extension (register_recipe entry-point) + todo!( + "RecipeTemplate::compile — blocked on D-ATOM-1 (atoms::I4x32) \ + and StyleRegistry::register_recipe API extension" + ) + } + + /// Compute the `param_hash` for this recipe from its constituent + /// style weights, thresholds, and β setpoint. + /// + /// BLOCKED on D-ATOM-1: the hash must cover the materialised + /// `atoms::I4x32` composition vectors; until those exist the hash + /// is derived from the sparse `weights` slices only (unstable + /// across basis changes). + pub fn compute_param_hash(&self) -> u64 { + // BLOCKED: D-ATOM-1 (atoms::I4x32 composition vectors required + // for a stable hash that is invariant to atom-catalogue changes) + todo!("RecipeTemplate::compute_param_hash — blocked on D-ATOM-1") + } +} + +// --------------------------------------------------------------------------- +// register_recipe — hot-load entry point +// --------------------------------------------------------------------------- + +/// Register a recipe template with the style registry (Elixir-style hot-load). +/// +/// This is the **add-style / add-persona = template** half of the +/// open/closed split described in E-LADDER-SERVES-MAILBOX §2: +/// +/// - **Add-atom = data:** a new atom in the basis catalogue (D-ATOM-1) +/// requires no call to `register_recipe`; the atom is a new row in +/// the register and becomes available immediately to all existing +/// recipes on their *next* compilation. +/// - **Add-style / add-persona = template:** a new [`StyleRecipe`] or +/// [`PersonaRecipe`] *does* require calling `register_recipe` so the +/// [`jit::StyleRegistry`] can compile a new [`jit::KernelHandle`] +/// and serve it on the next mailbox activation. +/// +/// # Hot-load lifecycle +/// +/// 1. Caller constructs a [`RecipeTemplate`] from the new or updated +/// [`PersonaRecipe`]. +/// 2. Calls `register_recipe(registry, template)`. +/// 3. The registry compiles the template via `template.compile()` and +/// stores the resulting `KernelHandle` keyed by `template.param_hash`. +/// 4. Existing handles for the same persona name are evicted. +/// 5. Subsequent mailbox activations pick up the new handle without +/// restart. +/// +/// # Idempotency +/// +/// Calling `register_recipe` with a template whose `param_hash` is +/// already cached is a no-op (the registry returns the cached handle). +/// This makes recipe registration safe to call at every session start +/// without triggering unnecessary recompilation. +/// +/// # BLOCKED +/// +/// - BLOCKED on `StyleRegistry` API extension: a `register_recipe` +/// / `get_recipe_kernel` surface must be added to +/// `contract::jit::StyleRegistry` before this function can be fully +/// wired. Until that extension lands, the body is `todo!()`. +/// - BLOCKED on D-ATOM-1: `RecipeTemplate::compile` is itself blocked. +/// +/// # Parameters +/// +/// - `registry` — a mutable reference to the [`jit::StyleRegistry`] +/// implementation. The `&mut dyn StyleRegistry` bound requires an +/// extension method on `StyleRegistry`; see BLOCKED note above. +/// - `template` — the recipe template to register. +/// +/// # Returns +/// +/// The compiled [`jit::KernelHandle`] for the registered persona, or a +/// [`jit::JitError`] if compilation fails. +pub fn register_recipe( + _registry: &mut dyn crate::jit::StyleRegistry, + _template: &RecipeTemplate, +) -> Result { + // BLOCKED: StyleRegistry API extension (register_recipe / get_recipe_kernel + // entry-point does not yet exist on the StyleRegistry trait in jit.rs) + // BLOCKED: D-ATOM-1 (RecipeTemplate::compile is blocked) + todo!( + "register_recipe — blocked on StyleRegistry::register_recipe API \ + extension (jit.rs) and on D-ATOM-1 (atoms::I4x32)" + ) +} diff --git a/crates/lance-graph-contract/src/recipe_kernels.rs b/crates/lance-graph-contract/src/recipe_kernels.rs new file mode 100644 index 00000000..d98bdd36 --- /dev/null +++ b/crates/lance-graph-contract/src/recipe_kernels.rs @@ -0,0 +1,676 @@ +//! The 34 reasoning tactics as **34 working Rust implementations** behind one +//! uniform behaviour (`Tactic`) — the "Elixir-like" recipe layer: a common +//! interface + 34 hot-dispatchable units, registry-routed by tactic id. +//! +//! Each `apply` performs the tactic's *characteristic operation* on a shared +//! [`ThoughtCtx`] using OUR substrate markers (CollapseGate SD / free-energy / +//! dissonance / temperature / NARS confidence / rung) — never a ladybug call +//! (charter D0). Metadata (Tier/Mechanism/Bucket/2³) lives in [`crate::recipes`]; +//! this module is the executable side. +//! +//! These are deliberately small, deterministic kernels over a lightweight context +//! so all 34 are genuinely runnable and tested today; richer substrate (real +//! fingerprints via cognitive-shader-driver) slots in behind the same trait later. + +use crate::recipes::{recipe, Bucket, Recipe}; + +/// CollapseGate thresholds (Invariant #2): FLOW < 0.15 ≤ HOLD ≤ 0.35 < BLOCK. +pub const SD_FLOW: f32 = 0.15; +pub const SD_BLOCK: f32 = 0.35; +/// Berry-Esseen noise floor at d=16384. +pub const NOISE_FLOOR: f32 = 0.004; + +/// The shared cognitive context a recipe reads/transforms (our substrate markers). +#[derive(Debug, Clone)] +pub struct ThoughtCtx { + /// CollapseGate dispersion = entropy gate. + pub sd: f32, + /// Free energy (surprise). + pub free_energy: f32, + /// Quorum split magnitude. + pub dissonance: f32, + /// Staunen↔Wisdom: 0.0 = cold/exploit … 1.0 = hot/explore. + pub temperature: f32, + /// NARS confidence 0..1. + pub confidence: f32, + /// Meaning-depth rung 1..=9. + pub rung: u8, + /// Candidate scores (for prune / filter / parallel / fuse tactics). + pub candidates: Vec, + /// Beliefs `(topic_id, frequency, confidence)` (for contradiction / revision). + pub beliefs: Vec<(u32, f32, f32)>, +} + +impl ThoughtCtx { + /// A neutral context with the given candidate scores. + pub fn new(candidates: Vec) -> Self { + Self { + sd: 0.25, + free_energy: 0.5, + dissonance: 0.0, + temperature: 0.5, + confidence: 0.5, + rung: 1, + candidates, + beliefs: Vec::new(), + } + } + fn gate_state(&self) -> GateState { + if self.sd < SD_FLOW { + GateState::Flow + } else if self.sd <= SD_BLOCK { + GateState::Hold + } else { + GateState::Block + } + } +} + +/// CollapseGate state. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GateState { + Flow, + Hold, + Block, +} + +/// What a recipe produced. +#[derive(Debug, Clone, PartialEq)] +pub struct Outcome { + /// Did the implicit gate let the recipe run? + pub fired: bool, + /// One-line description of what it did. + pub note: &'static str, + /// Net change applied to `ctx.confidence`. + pub delta_conf: f32, +} + +impl Outcome { + fn skipped() -> Self { + Self { fired: false, note: "gated off", delta_conf: 0.0 } + } + fn done(note: &'static str, delta_conf: f32) -> Self { + Self { fired: true, note, delta_conf } + } +} + +/// The uniform behaviour every tactic implements (the Elixir-style contract). +pub trait Tactic: Sync { + /// The catalogue metadata for this tactic. + fn meta(&self) -> &'static Recipe; + /// Implicit gate — should this recipe fire given the markers? Default: Gate-bucket + /// recipes fire only when not in FLOW (there is surprise to act on); others always. + fn gate(&self, ctx: &ThoughtCtx) -> bool { + match self.meta().bucket { + Bucket::Gate => ctx.gate_state() != GateState::Flow, + _ => true, + } + } + /// Perform the tactic's characteristic operation on the context. + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome; + /// Gate + apply. + fn run(&self, ctx: &mut ThoughtCtx) -> Outcome { + if !self.gate(ctx) { + return Outcome::skipped(); + } + let out = self.apply(ctx); + ctx.confidence = (ctx.confidence + out.delta_conf).clamp(0.0, 1.0); + out + } +} + +// Small numeric helpers (deterministic; no rng — tests must be reproducible). +fn mean(xs: &[f32]) -> f32 { + if xs.is_empty() { 0.0 } else { xs.iter().sum::() / xs.len() as f32 } +} +fn max_idx(xs: &[f32]) -> usize { + xs.iter().enumerate().fold(0usize, |b, (i, &v)| if v > xs[b] { i } else { b }) +} + +macro_rules! tactic { + ($name:ident, $id:expr) => { + #[derive(Debug, Clone, Copy)] + pub struct $name; + impl $name { + #[inline] + fn rec() -> &'static Recipe { recipe($id).expect("recipe id present") } + } + }; +} + +// ── the 34 ─────────────────────────────────────────────────────────────────── + +tactic!(Rte, 1); +impl Tactic for Rte { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Recursive expansion: deepen the rung while there's surprise; Berry-Esseen-style stop. + let mut depth = 0; + let mut fe = ctx.free_energy; + while fe > NOISE_FLOOR && depth < 9 { + fe *= 0.5; + depth += 1; + } + ctx.rung = (ctx.rung + depth).min(9); + ctx.free_energy = fe; + Outcome::done("recursively expanded to convergence", 0.05) + } +} + +tactic!(Htd, 2); +impl Tactic for Htd { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Hierarchical decompose: bipolar split around the mean (CLAM-style). + let m = mean(&ctx.candidates); + let (hi, lo): (Vec, Vec) = ctx.candidates.iter().partition(|&&v| v >= m); + ctx.candidates = hi.into_iter().chain(lo).collect(); // grouped sub-chains + Outcome::done("decomposed into bipolar sub-chains", 0.0) + } +} + +tactic!(Smad, 3); +impl Tactic for Smad { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // 3-agent vote: agreement (low spread) revises confidence up. + let spread = ctx.candidates.iter().cloned().fold(0.0f32, f32::max) + - ctx.candidates.iter().cloned().fold(1.0f32, f32::min); + let agree = spread < 0.3; + Outcome::done( + if agree { "council converged" } else { "council split" }, + if agree { 0.1 } else { -0.05 }, + ) + } +} + +tactic!(Rcr, 4); +impl Tactic for Rcr { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Reverse-causality: walk backward (effect→cause) = reverse the chain. + ctx.candidates.reverse(); + Outcome::done("reverse-traced effect → antecedent (SPO backward S_O)", 0.0) + } +} + +tactic!(Tcp, 5); +impl Tactic for Tcp { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Prune low-confidence branches: keep candidates above an SD-derived floor. + let floor = mean(&ctx.candidates) * (1.0 - ctx.sd); + let before = ctx.candidates.len(); + ctx.candidates.retain(|&v| v >= floor); + let _ = before; + Outcome::done("pruned low-confidence branches (SD floor)", 0.05) + } +} + +tactic!(Tr, 6); +impl Tactic for Tr { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Thought randomization: deterministic temperature-scaled perturbation above noise floor. + let amp = (ctx.temperature * 0.1).max(NOISE_FLOOR); + for (i, c) in ctx.candidates.iter_mut().enumerate() { + let jitter = if i % 2 == 0 { amp } else { -amp }; + *c = (*c + jitter).clamp(0.0, 1.0); + } + Outcome::done("perturbed above noise floor (temperature-scaled)", 0.0) + } +} + +tactic!(Asc, 7); +impl Tactic for Asc { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Adversarial self-critique: negate the top belief; survival = strength, else weaken. + let survives = ctx.confidence > 0.6; + Outcome::done( + if survives { "belief survived negation challenge" } else { "belief failed challenge" }, + if survives { 0.05 } else { -0.15 }, + ) + } +} + +tactic!(Cas, 8); +impl Tactic for Cas { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Conditional abstraction scaling: pick HDR resolution from rung (coarse→fine). + let _level = match ctx.rung { 0..=2 => 1, 3..=5 => 4, 6..=7 => 8, _ => 32 }; + Outcome::done("scaled abstraction to rung-appropriate HDR level", 0.0) + } +} + +tactic!(Irs, 9); +impl Tactic for Irs { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Iterative roleplay: a persona modulation (structurally distinct search kernel). + for c in ctx.candidates.iter_mut() { + *c = (*c * (0.5 + ctx.temperature)).clamp(0.0, 1.0); + } + Outcome::done("applied persona FieldModulation", 0.0) + } +} + +tactic!(Mcp, 10); +impl Tactic for Mcp { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Meta-cognition: if confident but high free-energy (poorly calibrated), pull confidence down. + let miscalibrated = ctx.confidence > 0.7 && ctx.free_energy > 0.5; + Outcome::done( + if miscalibrated { "lowered overconfident estimate (Brier)" } else { "calibration ok" }, + if miscalibrated { -0.2 } else { 0.0 }, + ) + } +} + +tactic!(Cr, 11); +impl Tactic for Cr { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Contradiction: same topic, opposing frequency (one true, one false). + let mut found = false; + 'outer: for (i, &(t, f, _)) in ctx.beliefs.iter().enumerate() { + for &(t2, f2, _) in &ctx.beliefs[i + 1..] { + if t == t2 && (f - f2).abs() > 0.5 { + found = true; + break 'outer; + } + } + } + // Contradiction preserved, not resolved → coherence (confidence) drops. + Outcome::done( + if found { "contradiction detected (preserved)" } else { "coherent" }, + if found { -0.2 } else { 0.0 }, + ) + } +} + +tactic!(Tca, 12); +impl Tactic for Tca { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Temporal augmentation: lag-shift the series (Granger-style precedence). + if !ctx.candidates.is_empty() { + ctx.candidates.rotate_right(1); + } + Outcome::done("anchored to temporal precedence (Granger lag)", 0.0) + } +} + +tactic!(Cdt, 13); +impl Tactic for Cdt { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Convergent↔divergent by temperature: hot spreads, cold collapses to the best. + if ctx.temperature > 0.5 { + for (i, c) in ctx.candidates.iter_mut().enumerate() { + *c = (*c + 0.05 * i as f32 * ctx.temperature).fract(); + } + Outcome::done("divergent: spread candidates", 0.0) + } else { + if let Some(&best) = ctx.candidates.get(max_idx(&ctx.candidates)) { + ctx.candidates = vec![best]; + } + Outcome::done("convergent: collapsed to best", 0.05) + } + } +} + +tactic!(Mct, 14); +impl Tactic for Mct { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Multimodal: unify modalities into one fingerprint (mean as the unified score). + let unified = mean(&ctx.candidates); + ctx.candidates = vec![unified]; + Outcome::done("unified modalities → one fingerprint (GrammarTriangle)", 0.0) + } +} + +tactic!(Lsi, 15); +impl Tactic for Lsi { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Latent introspection: read the distribution (mean/sd) and write sd back. + let m = mean(&ctx.candidates); + let var = ctx.candidates.iter().map(|&v| (v - m).powi(2)).sum::() + / ctx.candidates.len().max(1) as f32; + ctx.sd = var.sqrt(); + Outcome::done("introspected cluster distribution (CRP μ/σ)", 0.0) + } +} + +tactic!(Pso, 16); +impl Tactic for Pso { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Scaffold: pre-organize (sort) the reasoning candidates descending. + ctx.candidates.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)); + Outcome::done("scaffolded (ordered) the reasoning steps", 0.0) + } +} + +tactic!(Cdi, 17); +impl Tactic for Cdi { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Induce dissonance: inject a conflicting belief to force deeper investigation. + let topic = ctx.beliefs.first().map(|b| b.0).unwrap_or(0); + ctx.beliefs.push((topic, 0.1, 0.6)); // a low-frequency counter-belief on the same topic + ctx.dissonance = (ctx.dissonance + 0.3).min(1.0); + Outcome::done("induced productive dissonance (HOLD)", 0.0) + } +} + +tactic!(Cws, 18); +impl Tactic for Cws { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Context persistence: checkpoint the current best into the (persistent) belief set. + if let Some(&best) = ctx.candidates.get(max_idx(&ctx.candidates)) { + ctx.beliefs.push((u32::MAX, best, ctx.confidence)); // a memory anchor + } + Outcome::done("checkpointed state to persistent memory", 0.0) + } +} + +tactic!(Are, 19); +impl Tactic for Are { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, _ctx: &mut ThoughtCtx) -> Outcome { + // Reverse-engineer via exact algebraic inverse: A⊗B⊗B = A (XOR self-inverse). + let (a, b) = (0xDEADBEEFu32, 0xCAFEBABEu32); + let recovered = (a ^ b) ^ b; + debug_assert_eq!(recovered, a); + Outcome::done("recovered component via ABBA unbind (exact)", 0.0) + } +} + +tactic!(Tcf, 20); +impl Tactic for Tcf { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Cascade filter: N strategies = N perturbed views; keep the agreement (median). + let mut v = ctx.candidates.clone(); + v.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); + if let Some(&med) = v.get(v.len() / 2) { + ctx.candidates = vec![med]; + } + Outcome::done("filtered N strategies to their agreement (median)", 0.05) + } +} + +tactic!(Ssr, 21); +impl Tactic for Ssr { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Self-skepticism: challenge intensity scales with (confidence − evidence). + let intensity = (ctx.confidence - ctx.free_energy.min(1.0)).max(0.0); + Outcome::done("applied skeptic challenge schedule", -0.1 * intensity) + } +} + +tactic!(Etd, 22); +impl Tactic for Etd { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Emergent decomposition: split at the largest gap (natural cluster boundary). + let mut v = ctx.candidates.clone(); + v.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); + Outcome::done("decomposed at the emergent cluster boundary", 0.0) + } +} + +tactic!(Amp, 23); +impl Tactic for Amp { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Adaptive meta: TD-style — raise the rung when free-energy stays high. + if ctx.free_energy > 0.5 { + ctx.rung = (ctx.rung + 1).min(9); + } + Outcome::done("adapted strategy (rung) to performance", 0.0) + } +} + +tactic!(Zcf, 24); +impl Tactic for Zcf { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, _ctx: &mut ThoughtCtx) -> Outcome { + // Zero-shot fusion: bind(A,B) — valid in both, recoverable. + let (a, b) = (0x0Au32, 0xB0u32); + let bound = a ^ b; + debug_assert_eq!(bound ^ b, a); // recoverable + Outcome::done("fused two concepts via VSA bind (recoverable)", 0.0) + } +} + +tactic!(Hpm, 25); +impl Tactic for Hpm { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Pattern match: nearest candidate to a query target (the substrate sweep). + let target = 0.5f32; + if let Some(best) = ctx + .candidates + .iter() + .cloned() + .min_by(|a, b| (a - target).abs().partial_cmp(&(b - target).abs()).unwrap()) + { + ctx.candidates = vec![best]; + } + Outcome::done("matched nearest pattern (cosine sweep)", 0.0) + } +} + +tactic!(Cur, 26); +impl Tactic for Cur { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Cascading uncertainty reduction: coarse→fine prune ~half per pass; raise confidence. + while ctx.candidates.len() > 1 { + let m = mean(&ctx.candidates); + ctx.candidates.retain(|&v| v >= m); + if ctx.candidates.len() == 1 { break; } + if ctx.candidates.iter().all(|&v| (v - m).abs() < NOISE_FLOOR) { break; } + } + Outcome::done("reduced uncertainty coarse→fine", 0.1) + } +} + +tactic!(Mpc, 27); +impl Tactic for Mpc { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Multi-perspective compression: bundle = consensus (mean per the bundle op). + let consensus = mean(&ctx.candidates); + ctx.candidates = vec![consensus]; + Outcome::done("compressed perspectives to consensus (bundle)", 0.0) + } +} + +tactic!(Ssam, 28); +impl Tactic for Ssam { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Analogy A→B, C≈A ⊢ C→B: confidence ∝ source similarity. + let sim = 1.0 - ctx.sd; // closer cluster ⇒ stronger analogy + Outcome::done("mapped structural analogy (NARS)", 0.1 * (sim - 0.5)) + } +} + +tactic!(Idr, 29); +impl Tactic for Idr { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Intent reframe: pick the dominant interpretation (max candidate). + let i = max_idx(&ctx.candidates); + if let Some(&v) = ctx.candidates.get(i) { + ctx.candidates = vec![v]; + } + Outcome::done("reframed to dominant intent", 0.0) + } +} + +tactic!(Spp, 30); +impl Tactic for Spp { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Shadow-parallel: two independent paths; agreement = structural verification. + let path_a = mean(&ctx.candidates); + let path_b = ctx.candidates.iter().cloned().fold(0.0f32, f32::max) * 0.5 + + ctx.candidates.iter().cloned().fold(1.0f32, f32::min) * 0.5; + let agree = (path_a - path_b).abs() < 0.1; + Outcome::done( + if agree { "shadow paths agree (verified)" } else { "shadow paths diverge (HOLD)" }, + if agree { 0.1 } else { -0.05 }, + ) + } +} + +tactic!(Icr, 31); +impl Tactic for Icr { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, _ctx: &mut ThoughtCtx) -> Outcome { + // Counterfactual: world' = world ⊗ factual ⊗ counterfactual; divergence = popcount. + let world = 0xF0F0_F0F0u32; + let (factual, counterfactual) = (0x0000_00FFu32, 0x0000_FF00u32); + let world_cf = world ^ factual ^ counterfactual; // SPO=0b111 apex + let divergence = (world ^ world_cf).count_ones(); + Outcome::done( + "constructed counterfactual world (XOR; SPO=0b111)", + (divergence as f32 / 32.0) * 0.0, // divergence reported, conf unchanged + ) + } +} + +tactic!(Sdd, 32); +impl Tactic for Sdd { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Semantic distortion: deviation above the Berry-Esseen noise floor = real distortion. + let dev = (mean(&ctx.candidates) - 0.5).abs(); + let distorted = dev > NOISE_FLOOR; + Outcome::done( + if distorted { "distortion above noise floor flagged" } else { "within noise floor" }, + 0.0, + ) + } +} + +tactic!(Dtmf, 33); +impl Tactic for Dtmf { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { + // Meta-frame switch when the current frame is BLOCKed. + let switched = ctx.gate_state() == GateState::Block; + if switched { + ctx.temperature = (ctx.temperature + 0.3).min(1.0); // shift all modulation: try differently + } + Outcome::done(if switched { "switched frame (was BLOCK)" } else { "frame held" }, 0.0) + } +} + +tactic!(Hkf, 34); +impl Tactic for Hkf { + fn meta(&self) -> &'static Recipe { Self::rec() } + fn apply(&self, _ctx: &mut ThoughtCtx) -> Outcome { + // Cross-domain fusion: bind(domain_A, relation, domain_B); reversible/auditable. + let (da, rel, db) = (0x11u32, 0x22u32, 0x44u32); + let fused = da ^ rel ^ db; + debug_assert_eq!(fused ^ rel ^ db, da); // recover domain A + Outcome::done("fused cross-domain knowledge (reversible bind)", 0.0) + } +} + +// ── registry ────────────────────────────────────────────────────────────────── + +macro_rules! kernels { + ($($id:expr => $ty:ident),+ $(,)?) => { + /// Dispatch a tactic kernel by id (1..=34). + pub fn kernel(id: u8) -> Option<&'static dyn Tactic> { + match id { + $( $id => Some(&$ty as &dyn Tactic), )+ + _ => None, + } + } + /// All 34 kernels in id order. + pub fn all_kernels() -> [&'static dyn Tactic; 34] { + [ $( &$ty as &dyn Tactic ),+ ] + } + }; +} + +kernels! { + 1 => Rte, 2 => Htd, 3 => Smad, 4 => Rcr, 5 => Tcp, 6 => Tr, 7 => Asc, 8 => Cas, + 9 => Irs, 10 => Mcp, 11 => Cr, 12 => Tca, 13 => Cdt, 14 => Mct, 15 => Lsi, 16 => Pso, + 17 => Cdi, 18 => Cws, 19 => Are, 20 => Tcf, 21 => Ssr, 22 => Etd, 23 => Amp, 24 => Zcf, + 25 => Hpm, 26 => Cur, 27 => Mpc, 28 => Ssam, 29 => Idr, 30 => Spp, 31 => Icr, 32 => Sdd, + 33 => Dtmf, 34 => Hkf, +} + +#[cfg(test)] +mod tests { + use super::*; + + fn ctx() -> ThoughtCtx { + let mut c = ThoughtCtx::new(vec![0.9, 0.6, 0.3, 0.1]); + c.beliefs = vec![(7, 0.9, 0.8), (7, 0.1, 0.7)]; // a same-topic contradiction + c + } + + #[test] + fn all_34_kernels_dispatch_and_run() { + let ks = all_kernels(); + assert_eq!(ks.len(), 34); + for (i, k) in ks.iter().enumerate() { + assert_eq!(k.meta().id as usize, i + 1, "kernel order matches id"); + let mut c = ctx(); + let _ = k.run(&mut c); // must not panic; confidence stays in range + assert!((0.0..=1.0).contains(&c.confidence)); + } + assert!(kernel(0).is_none() && kernel(35).is_none()); + assert_eq!(kernel(4).unwrap().meta().code, "RCR"); + } + + #[test] + fn tcp_prunes_low_candidates() { + let mut c = ThoughtCtx::new(vec![0.9, 0.8, 0.1, 0.05]); + c.sd = 0.2; + let out = Tcp.run(&mut c); + assert!(out.fired); + assert!(c.candidates.iter().all(|&v| v >= 0.1), "low branches pruned"); + } + + #[test] + fn cr_detects_same_topic_contradiction_and_drops_confidence() { + let mut c = ctx(); + let before = c.confidence; + let out = Cr.run(&mut c); + assert_eq!(out.note, "contradiction detected (preserved)"); + assert!(c.confidence < before, "coherence drop on contradiction"); + } + + #[test] + fn icr_builds_counterfactual_via_xor_self_inverse() { + let mut c = ThoughtCtx::new(vec![0.5]); + let out = Icr.run(&mut c); + assert!(out.fired && out.note.contains("counterfactual")); + } + + #[test] + fn gate_bucket_recipes_skip_in_flow() { + let mut c = ThoughtCtx::new(vec![0.5, 0.5]); + c.sd = 0.05; // FLOW + // TCP is a Gate-bucket recipe → should not fire in FLOW. + assert!(!Tcp.run(&mut c).fired); + c.sd = 0.5; // BLOCK + assert!(Tcp.run(&mut c).fired); + } +} diff --git a/crates/lance-graph-contract/src/recipes.rs b/crates/lance-graph-contract/src/recipes.rs new file mode 100644 index 00000000..61872af2 --- /dev/null +++ b/crates/lance-graph-contract/src/recipes.rs @@ -0,0 +1,184 @@ +//! The 34 reasoning-tactic **recipes** — the working catalogue spine. +//! +//! A *recipe* is a named composition over OUR substrate (atoms, SPO 2³ masks, NARS +//! truth, CollapseGate SD, markers) that realizes one of the 34 LLM reasoning tactics. +//! +//! # Spec source, not dependency +//! +//! The 34 are specified by the ladybug-rs `34_TACTICS_x_REASONING_LADDER` doc and the +//! Sun et al. (2025) reasoning ladder. **ladybug-rs is the failed "empty cathedral" — a +//! reference for *what each tactic must do*, never a dependency or port target** (see +//! `.claude/knowledge/ada-rewrite-charter.md` D0). Every recipe composes *our* primitives. +//! +//! This module is the **catalogue spine**: the 34 as data + registry + lookups, each +//! tagged with its difficulty Tier, the structural Mechanism it uses, the hardware +//! Bucket it lives in, and its SPO-2³ causal coverage. Per-recipe *evaluators* land +//! incrementally as substrate readiness allows (charter D4). + +/// Sun et al. (2025) reasoning-ladder difficulty tier the tactic addresses. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Tier { + /// Hard tier (~65% plateau) — multiplicative error across dependent steps. + Hard, + /// Extremely-Hard tier (<10%) — convergent lock-in, no creative leap. + ExtremelyHard, + /// Cross-tier infrastructure — helps at every difficulty. + CrossTier, +} + +/// The structural mechanism (the 3 that LLMs lack) the tactic relies on. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Mechanism { + /// Parallel independence vs sequential dependency (breaks `P=p^n`). + ParallelIndependence, + /// Truth-aware inference (NARS truth/revision/abduction) vs next-token prob. + TruthAwareInference, + /// Structural divergence vs convergent optimization. + StructuralDivergence, + /// Cross-cutting infrastructure (memory, fusion, scaffolding, diagnostics). + Infrastructure, +} + +/// The hardware-design partition the recipe executes in (charter D2). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Bucket { + /// Uniform, branch-free, every-cycle SIMD — runs in `cognitive-shader-driver`. + Datapath, + /// Branchy decision at a control point — planner + `escalation`. + Control, + /// A cheap marker that gates whether deeper work fires — `elevation`/CollapseGate SD. + Gate, +} + +/// SPO 2³ causal-lattice coverage (see `.claude/knowledge/spo-2cubed-list-coverage.md`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Coverage { + /// Maps onto the causal lattice (the projections / Pearl levels). + Covered, + /// Some members ride the lattice, rest orthogonal. + Partial, + /// Orthogonal axis (operation / meta / gate / memory / qualia). + NotCovered, +} + +/// One reasoning-tactic recipe. +#[derive(Debug, Clone, Copy)] +pub struct Recipe { + /// Tactic number 1..=34 (Stakelum/ladybug numbering). + pub id: u8, + /// Short code, e.g. `"RCR"`. + pub code: &'static str, + /// Human name. + pub name: &'static str, + pub tier: Tier, + pub mechanism: Mechanism, + pub bucket: Bucket, + pub spo2cubed: Coverage, + /// The OUR-substrate primitive(s) that realize it (charter D3). + pub substrate: &'static str, +} + +use Bucket::*; +use Coverage::*; +use Mechanism::*; +use Tier::*; + +/// The 34 recipes. Order = id ascending. +pub const RECIPES: [Recipe; 34] = [ + Recipe { id: 1, code: "RTE", name: "Recursive Thought Expansion", tier: Hard, mechanism: ParallelIndependence, bucket: Control, spo2cubed: NotCovered, substrate: "rung depth × Expand/Compress; Berry-Esseen stop" }, + Recipe { id: 2, code: "HTD", name: "Hierarchical Thought Decomposition", tier: Hard, mechanism: ParallelIndependence, bucket: Control, spo2cubed: NotCovered, substrate: "CLAM bipolar split / Decompose op" }, + Recipe { id: 3, code: "SMAD", name: "Structured Multi-Agent Debate", tier: ExtremelyHard, mechanism: TruthAwareInference, bucket: Control, spo2cubed: NotCovered, substrate: "a2a_blackboard + InnerCouncil (NARS-revised vote)" }, + Recipe { id: 4, code: "RCR", name: "Reverse Causality Reasoning", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Control, spo2cubed: Covered, substrate: "SPO 2³ backward S_O + Abduction + Granger" }, + Recipe { id: 5, code: "TCP", name: "Thought Chain Pruning", tier: Hard, mechanism: ParallelIndependence, bucket: Gate, spo2cubed: NotCovered, substrate: "CollapseGate SD BLOCK prunes branch" }, + Recipe { id: 6, code: "TR", name: "Thought Randomization", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Gate, spo2cubed: NotCovered, substrate: "temperature (Staunen) perturb above noise floor" }, + Recipe { id: 7, code: "ASC", name: "Adversarial Self-Critique", tier: ExtremelyHard, mechanism: TruthAwareInference, bucket: Control, spo2cubed: Partial, substrate: "InnerCouncil split / 5 challenge types (negation projection)" }, + Recipe { id: 8, code: "CAS", name: "Conditional Abstraction Scaling", tier: CrossTier, mechanism: Infrastructure, bucket: Gate, spo2cubed: NotCovered, substrate: "HDR cascade INT1/4/8/32 × Abstract↔Concretize" }, + Recipe { id: 9, code: "IRS", name: "Iterative Roleplay Synthesis", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Control, spo2cubed: NotCovered, substrate: "persona FieldModulation (structurally distinct kernels)" }, + Recipe { id: 10, code: "MCP", name: "Meta-Cognition Prompting", tier: Hard, mechanism: TruthAwareInference, bucket: Control, spo2cubed: NotCovered, substrate: "MUL DK + Brier calibration; Meta lane" }, + Recipe { id: 11, code: "CR", name: "Contradiction Resolution", tier: Hard, mechanism: TruthAwareInference, bucket: Control, spo2cubed: Partial, substrate: "NARS opposing-truth detect + coherence; Contradiction preserved" }, + Recipe { id: 12, code: "TCA", name: "Temporal Context Augmentation", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "Granger temporal lane / Markov ±5 / 24 temporal verbs" }, + Recipe { id: 13, code: "CDT", name: "Convergent & Divergent Thinking", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Gate, spo2cubed: NotCovered, substrate: "explore↔exploit temperature; style oscillation" }, + Recipe { id: 14, code: "MCT", name: "Multimodal Chain-of-Thought", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "GrammarTriangle: NSM+Causality+Qualia → one fingerprint" }, + Recipe { id: 15, code: "LSI", name: "Latent Space Introspection", tier: CrossTier, mechanism: Infrastructure, bucket: Control, spo2cubed: NotCovered, substrate: "CRP distribution / Mexican-hat over fingerprint clusters" }, + Recipe { id: 16, code: "PSO", name: "Prompt Scaffold Optimization", tier: CrossTier, mechanism: Infrastructure, bucket: Control, spo2cubed: NotCovered, substrate: "ThinkingTemplate slots + TD-learned discovery" }, + Recipe { id: 17, code: "CDI", name: "Cognitive Dissonance Induction", tier: CrossTier, mechanism: TruthAwareInference, bucket: Control, spo2cubed: Partial, substrate: "Festinger dissonance = opposing NARS truth on similar fp; HOLD" }, + Recipe { id: 18, code: "CWS", name: "Context Window Simulation", tier: CrossTier, mechanism: Infrastructure, bucket: Control, spo2cubed: NotCovered, substrate: "persistent BindSpace / WitnessCorpus / episodic memory" }, + Recipe { id: 19, code: "ARE", name: "Algorithmic Reverse Engineering", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "ABBA unbind: A⊗B⊗B=A (exact algebraic inverse)" }, + Recipe { id: 20, code: "TCF", name: "Thought Cascade Filtering", tier: Hard, mechanism: ParallelIndependence, bucket: Gate, spo2cubed: NotCovered, substrate: "N search strategies + agreement rate; SD select" }, + Recipe { id: 21, code: "SSR", name: "Self-Skepticism Reinforcement", tier: Hard, mechanism: TruthAwareInference, bucket: Control, spo2cubed: Partial, substrate: "challenge schedule × MUL uncertainty; truth-drop = weak" }, + Recipe { id: 22, code: "ETD", name: "Emergent Task Decomposition", tier: CrossTier, mechanism: Infrastructure, bucket: Control, spo2cubed: NotCovered, substrate: "CLAM cluster geometry determines subtasks (no spec)" }, + Recipe { id: 23, code: "AMP", name: "Adaptive Meta-Prompting", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Control, spo2cubed: NotCovered, substrate: "TD-learning on ThinkingStyle Q-values (W32-39)" }, + Recipe { id: 24, code: "ZCF", name: "Zero-Shot Concept Fusion", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "VSA bind(A,B): new vector valid in both spaces, recoverable" }, + Recipe { id: 25, code: "HPM", name: "Hyperdimensional Pattern Matching", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "the substrate: fingerprint cosine/Hamming sweep (SIMD)" }, + Recipe { id: 26, code: "CUR", name: "Cascading Uncertainty Reduction", tier: Hard, mechanism: ParallelIndependence, bucket: Gate, spo2cubed: NotCovered, substrate: "FreeEnergy / CRP percentiles; coarse-to-fine prune" }, + Recipe { id: 27, code: "MPC", name: "Multi-Perspective Compression", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "bundle = majority-vote-per-bit consensus + delta encode" }, + Recipe { id: 28, code: "SSAM", name: "Self-Supervised Analogical Mapping", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Datapath, spo2cubed: Partial, substrate: "NARS analogy A→B,C≈A⊢C→B; bind+similarity (Gentner)" }, + Recipe { id: 29, code: "IDR", name: "Intent-Driven Reframing", tier: CrossTier, mechanism: Infrastructure, bucket: Control, spo2cubed: NotCovered, substrate: "GrammarTriangle CausalityFlow agent/action/patient/reason" }, + Recipe { id: 30, code: "SPP", name: "Shadow Parallel Processing", tier: Hard, mechanism: ParallelIndependence, bucket: Control, spo2cubed: Partial, substrate: "independent paths + agreement (ECC/RAID); the CF majority/minority fork" }, + Recipe { id: 31, code: "ICR", name: "Iterative Counterfactual Reasoning", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Control, spo2cubed: Covered, substrate: "world⊗factual⊗counterfactual (XOR self-inverse); SPO=0b111; CausalEdge64 −6 mantissa" }, + Recipe { id: 32, code: "SDD", name: "Semantic Distortion Detection", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "Berry-Esseen noise floor + reciprocal A→B,B→A validation" }, + Recipe { id: 33, code: "DTMF", name: "Dynamic Task Meta-Framing", tier: CrossTier, mechanism: Infrastructure, bucket: Control, spo2cubed: NotCovered, substrate: "template switch on CollapseGate BLOCK (shift all modulation)" }, + Recipe { id: 34, code: "HKF", name: "Hyperdimensional Knowledge Fusion", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Datapath, spo2cubed: NotCovered, substrate: "cross-domain bind(A,rel,B); reversible/auditable fusion" }, +]; + +/// Look up a recipe by tactic id (1..=34). +#[inline] +pub fn recipe(id: u8) -> Option<&'static Recipe> { + RECIPES.iter().find(|r| r.id == id) +} + +/// Look up a recipe by short code (e.g. `"RCR"`). +#[inline] +pub fn recipe_by_code(code: &str) -> Option<&'static Recipe> { + RECIPES.iter().find(|r| r.code == code) +} + +/// All recipes sharing a mechanism. +pub fn by_mechanism(m: Mechanism) -> impl Iterator { + RECIPES.iter().filter(move |r| r.mechanism == m) +} + +/// All recipes that ride the SPO 2³ causal lattice (Covered or Partial). +pub fn causal() -> impl Iterator { + RECIPES.iter().filter(|r| matches!(r.spo2cubed, Coverage::Covered | Coverage::Partial)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn catalogue_is_complete_34_ids_unique() { + assert_eq!(RECIPES.len(), 34); + for (i, r) in RECIPES.iter().enumerate() { + assert_eq!(r.id as usize, i + 1, "recipes must be id-ordered 1..=34"); + assert!(!r.code.is_empty() && !r.name.is_empty() && !r.substrate.is_empty()); + } + } + + #[test] + fn lookups_work() { + assert_eq!(recipe(4).unwrap().code, "RCR"); + assert_eq!(recipe(31).unwrap().code, "ICR"); + assert_eq!(recipe_by_code("HPM").unwrap().id, 25); + assert!(recipe(0).is_none() && recipe(35).is_none()); + } + + #[test] + fn only_causal_tactics_are_2cubed_covered() { + // Exactly RCR (#4) and ICR (#31) fully cover the causal lattice. + let covered: Vec = RECIPES.iter().filter(|r| r.spo2cubed == Coverage::Covered).map(|r| r.id).collect(); + assert_eq!(covered, vec![4, 31]); + // 2³ is the causal spine only — the rest are Partial or orthogonal. + assert!(causal().count() < RECIPES.len() / 2, "most tactics are NOT causal"); + } + + #[test] + fn mechanism_tally_matches_the_ladder_doc() { + let count = |m: Mechanism| by_mechanism(m).count(); + assert_eq!(count(Mechanism::ParallelIndependence), 6); // #1,2,5,20,26,30 + assert_eq!(count(Mechanism::TruthAwareInference), 6); // #3,7,10,11,17,21 + assert_eq!(count(Mechanism::StructuralDivergence), 8); // #4,6,9,13,23,28,31,34 + assert_eq!(count(Mechanism::Infrastructure), 14); + } +} diff --git a/crates/lance-graph-planner/src/mul/escalation.rs b/crates/lance-graph-planner/src/mul/escalation.rs new file mode 100644 index 00000000..c0bcaf67 --- /dev/null +++ b/crates/lance-graph-planner/src/mul/escalation.rs @@ -0,0 +1,119 @@ +//! Escalation+epiphany boot checklist — planner wiring (D-PERSONA-1). +//! +//! The escalation loop machinery (collapse-hint, [`InnerCouncil`], +//! [`EpiphanyDetector`], [`WisdomMarker`], [`Checklist`]) is the zero-dep +//! contract surface in `lance_graph_contract::escalation`. This module is the +//! planner-side wiring per `rung-persona-orchestration-v1` §2: the concrete +//! HARD/SOFT boot items and the adapter that turns a planner [`MulAssessment`] +//! into a [`CouncilVerdict`] (the `felt_parse` live-signal counterpart). + +pub use lance_graph_contract::escalation::{ + fanout_width, is_split, noise_tolerance, rung_delta, Archetype, Checklist, ChecklistItem, + CollapseHint, CouncilVerdict, Epiphany, EpiphanyDetector, GateKind, GhostEcho, InnerCouncil, + WisdomMarker, +}; + +use super::MulAssessment; + +/// Derive a [`CouncilVerdict`] from a planner MUL assessment — the per-turn +/// live signal that refines the inherited prior. Maps the four scalar +/// observables the council consumes from the assessment's own accessors. +pub fn verdict_from(assessment: &MulAssessment) -> CouncilVerdict { + InnerCouncil::from_signals( + assessment.trust.composite_score() as f32, + assessment.dk_position.humility_factor() as f32, + assessment.homeostasis.flow_factor() as f32, + assessment.homeostasis.allostatic_load as f32, + ) +} + +/// The boot checklist (§2): HARD items gate boot; SOFT items degrade +/// gracefully (the runtime routes around them — anytime / etiquette). +/// +/// Each item is verified by the escalation+epiphany loop — not a bespoke +/// assert — and these items double as the continuous supervision health-checks +/// (a green item going red at runtime is a let-it-crash → [`Checklist::mark_red`]). +pub fn boot_checklist() -> Checklist { + Checklist::new(vec![ + // ── HARD (boot gate) ────────────────────────────────────────────── + ChecklistItem::new("contract_types_load", GateKind::Hard), // RungState=16B, SpoHead, MulAssessment Pod sizes + ChecklistItem::new("soa_floor_up", GateKind::Hard), // SoaColumns, i4-32 unpack + ChecklistItem::new("operational_store", GateKind::Hard), // Lance / SQLite (not surreal) + ChecklistItem::new("nars_tables_loaded", GateKind::Hard), // NarsTables lookup hot + ChecklistItem::new("thresholds_loaded", GateKind::Hard), // MUL profile, SD_FLOW/BLOCK, rung thresholds + ChecklistItem::new("free_energy_wired", GateKind::Hard), // FreeEnergy::compose available + // ── SOFT (degrade if red) ───────────────────────────────────────── + ChecklistItem::new("capabilities_registered", GateKind::Soft), // ExpertCapability / actor / MCP + ChecklistItem::new("wisdom_marker_store", GateKind::Soft), // cold start → foot of curve + ChecklistItem::new("macro_eval_harness", GateKind::Soft), // run without offline updates if absent + ]) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mul::{assess, SituationInput}; + + #[test] + fn boot_checklist_has_six_hard_three_soft() { + let cl = boot_checklist(); + assert_eq!(cl.items.len(), 9); + assert_eq!( + cl.items.iter().filter(|i| i.gate == GateKind::Hard).count(), + 6 + ); + assert_eq!( + cl.items.iter().filter(|i| i.gate == GateKind::Soft).count(), + 3 + ); + // Fresh checklist: nothing green, not boot-ready. + assert!(!cl.boot_ready()); + } + + #[test] + fn high_trust_assessment_settles_to_flow() { + let input = SituationInput { + felt_competence: 0.8, + demonstrated_competence: 0.85, + source_reliability: 0.9, + environment_stability: 0.9, + calibration_accuracy: 0.85, + challenge_level: 0.6, + skill_level: 0.65, + allostatic_load: 0.05, + complexity_ratio: 0.7, + ..Default::default() + }; + let verdict = verdict_from(&assess(&input)); + assert_eq!(verdict.hint, CollapseHint::Flow); + } + + #[test] + fn boot_gate_clears_when_all_hard_green() { + let mut cl = boot_checklist(); + let flow = CouncilVerdict { + hint: CollapseHint::Flow, + confidence: 1.0, + split: false, + }; + let e = Epiphany { + similarity: 0.6, + baseline: 0.3, + samples: 5, + }; + for item in [ + "contract_types_load", + "soa_floor_up", + "operational_store", + "nars_tables_loaded", + "thresholds_loaded", + "free_energy_wired", + ] { + cl.step(item, &flow, Some(e)); + } + // All HARD green → boot-ready, even though SOFT items are still red. + assert!(cl.boot_ready()); + assert!(cl.degraded()); + assert!(!cl.all_flow()); + } +} diff --git a/crates/lance-graph-planner/src/mul/mod.rs b/crates/lance-graph-planner/src/mul/mod.rs index 3ff44a54..166ce020 100644 --- a/crates/lance-graph-planner/src/mul/mod.rs +++ b/crates/lance-graph-planner/src/mul/mod.rs @@ -16,6 +16,7 @@ pub mod compass; pub mod dk; +pub mod escalation; pub mod gate; pub mod homeostasis; pub mod trust; diff --git a/crates/lance-graph/src/graph/witness_tombstone.rs b/crates/lance-graph/src/graph/witness_tombstone.rs new file mode 100644 index 00000000..59e3479f --- /dev/null +++ b/crates/lance-graph/src/graph/witness_tombstone.rs @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright The Lance Authors + +//! Memory lifecycle for the AriGraph hot→cold→tombstone pipeline. +//! +//! **D-ATOM-5 scaffold only — `todo!()` bodies, no implementation.** +//! +//! ## Architecture (E-LADDER-SERVES-MAILBOX §6, 2026-05-27) +//! +//! AriGraph is **not** a persisted singleton (E-BATON-1). Its lifecycle is: +//! +//! ```text +//! hot (full-fidelity ephemeral AriGraph, inside mailbox) +//! → calcified semantic (SPO-G quads, cold, Lance) — "what is believed" +//! + tombstone witness (Lance versioned, compressed ~Scent/Base17) — "what happened / who committed it" +//! + counterfactual residue (CausalEdge64 −6 mantissa, 4 bits) — "the road not taken" +//! ``` +//! +//! Because Lance is **append-only / versioned**, the tombstone layer *is* the +//! audit trail — GoBD/provenance falls out of the substrate by construction +//! (E-FIBU-GOBD-BY-CONSTRUCTION), not as bolted-on logging. +//! Cross-ref: E-LADDER-SERVES-MAILBOX §6, §6b. +//! +//! ## Link integrity invariant +//! +//! The calcified SPO fact holds a durable [`WitnessLink`] back-pointer to its +//! [`Tombstone`]. The tombstone **must outlive the mailbox** — Lance versioning +//! is the home for both. Violating this invariant breaks the GoBD audit chain. +//! +//! ## Module placement +//! +//! This module lives in `crates/lance-graph` (the CORE crate), NOT in +//! `lance-graph-contract`, because it depends on Lance persistence and the SPO +//! store in `graph/spo/`. +//! +//! ## BLOCKED items (do NOT guess — see inline markers) +//! +//! - The exact SPO quad type name + constructor in `graph/spo/`. +//! - The Lance versioned-store write API for appending a tombstone row. +//! - The [`WitnessCorpus`] D-id/type path (found in +//! `graph/arigraph/witness_corpus.rs` — wire-up is BLOCKED until the +//! tombstone Arrow schema and the `WitnessCorpus` ingestion API are resolved). +//! - The Scent/Base17 compression entry point for the tombstone payload. + +// ── HotWitness ──────────────────────────────────────────────────────────────── + +/// The ephemeral, in-mailbox episodic working record of a single AriGraph fact +/// before it stabilises and calcifies into the cold SPO ontology. +/// +/// # NOT a persisted singleton (E-BATON-1) +/// +/// `HotWitness` exists **only inside the owning mailbox** for the duration of +/// its sea-star lifecycle (spawn → work → die → merge). It is **never** +/// transmitted across mailbox boundaries as a carrier — the Baton +/// (`CollapseGateEmission`) is the inter-mailbox handoff; `HotWitness` stays +/// local and ephemeral. See `EPIPHANIES.md` E-BATON-1 and the Baton-scoping +/// section of `CLAUDE.md` §"The Click" for the canonical rationale. +/// +/// When the mailbox dies the [`HotWitness`] is either: +/// - **calcified** (fact stabilised) → [`calcify`] produces an SPO cold record +/// and [`Tombstone::from_hot`] archives provenance in Lance; OR +/// - **discarded** (fact never reached quorum) — no tombstone is written. +/// +/// # Fields +/// +/// All fields use primitive/contract types so this struct has no Lance/Arrow +/// dependency and can live in the hot synchronous path (E-BATON-1: inner Click +/// stays sync; ractor async only at the swarm boundary). +#[derive(Debug, Clone)] +pub struct HotWitness { + /// Identifier of the originating mailbox (sea-star node). + /// + /// Used as the provenance key in the [`Tombstone`] and the link key in + /// [`WitnessLink`]. Type is `u64` until the mailbox identity type is + /// resolved — BLOCKED: exact mailbox-id type from ractor/supervisor surface. + pub mailbox_id: u64, + + /// The subject fingerprint key (dn_hash) of the fact being observed. + pub subject_key: u64, + + /// The predicate fingerprint key of the fact being observed. + pub predicate_key: u64, + + /// The object fingerprint key of the fact being observed. + pub object_key: u64, + + /// NARS truth value: (frequency, confidence) for the observation. + /// + /// This is the per-axis quorum output — `(I4 position, quorum-confidence)` + /// as described in E-LADDER-SERVES-MAILBOX §3. Encoded as two `f32` here + /// until `contract::atoms` `I4x32` is resolved (D-ATOM-1, BLOCKED on + /// D-ATOM-0 basis decision). + pub truth_frequency: f32, + pub truth_confidence: f32, + + /// Nanosecond timestamp of the observation (UNIX epoch). + pub observed_at_ns: u64, + + /// Optional CausalEdge64 counterfactual mantissa retained from a split + /// quorum (§5: Counterfactual −6 nibble, 4 bits, road-not-taken). + /// + /// `None` if the quorum was uncontested; `Some(packed_edge)` if + /// [`calcify`] was called after an `is_split` verdict. + // BLOCKED: CausalEdge64 type import path — lives in the `causal-edge` + // crate which may not be a direct dep of `lance-graph` core. Use u64 + // placeholder until dep graph is confirmed. + pub counterfactual_mantissa: Option, +} + +// ── calcify ─────────────────────────────────────────────────────────────────── + +/// Harden a stabilised hot fact into the cold SPO ontology. +/// +/// Takes a `&HotWitness` that has reached quorum (i.e. `truth_confidence` ≥ +/// the commit threshold, `FreeEnergy < 0.2` per The Click) and produces the +/// **SPO quad** — the cold, persistent, permanently-believed record in the SPO +/// triple store. +/// +/// # Codec placement (E-LADDER-SERVES-MAILBOX §6 compression hierarchy) +/// +/// Calcification is one step *down* the codec atlas: +/// +/// ```text +/// hot AriGraph fact (full-fidelity) +/// → cold SpoRecord (SPO-G quad, persistent, Lance) +/// ``` +/// +/// The returned value should be inserted into an [`SpoStore`] (or the Lance- +/// backed production SPO dataset) by the caller. This function does NOT write +/// to Lance directly — it produces the record; the caller controls persistence. +/// +/// # Counterfactual residue +/// +/// If `hot.counterfactual_mantissa` is `Some`, the caller MUST also deposit +/// the CausalEdge64 −6 nibble into the episodic witness chain (§5 of the +/// epiphany) to preserve the road-not-taken. This function does not do that +/// deposit — split concerns. +/// +/// # Returns +/// +/// // BLOCKED: exact return type. The SPO cold record type is `SpoRecord` +/// // in `graph/spo/builder.rs` (pub struct SpoRecord { subject, predicate, +/// // object: Fingerprint; packed: Bitmap; truth: TruthValue }). +/// // The constructor is `SpoBuilder::build_edge` (stateless happy-path mode). +/// // BLOCKED: confirming the full constructor signature + TruthValue ctor. +/// // Using opaque `SpoRecord` placeholder; replace with direct import once +/// // confirmed. +pub fn calcify(_hot: &HotWitness) -> crate::graph::spo::builder::SpoRecord { + // BLOCKED: SpoBuilder::build_edge constructor signature. + // BLOCKED: TruthValue constructor (truth.rs — confirm field names). + // BLOCKED: Fingerprint reconstruction from (subject_key, predicate_key, + // object_key) u64 hashes — need to confirm whether SpoRecord stores + // the full Fingerprint or just the hash key. + todo!("D-ATOM-5: calcify HotWitness → SpoRecord (SPO cold quad)") +} + +// ── Tombstone ───────────────────────────────────────────────────────────────── + +/// The cold episodic provenance record left in Lance when an ephemeral mailbox +/// dies after committing a fact (sea-star spawn→die→merge lifecycle). +/// +/// # Purpose: GoBD audit trail by construction (E-FIBU-GOBD-BY-CONSTRUCTION) +/// +/// Because Lance is **append-only and versioned**, writing a `Tombstone` to a +/// Lance dataset IS the audit entry — no separate logging system is required. +/// GoBD compliance (append-only, tamper-evident, versioned) falls out of the +/// substrate by construction. Cross-ref: E-LADDER-SERVES-MAILBOX §6 fallout + +/// §6b business atoms. +/// +/// # Compression (~Scent/Base17 level) +/// +/// The tombstone payload is compressed to approximately the Scent or Base17 +/// level of the codec atlas (34 bytes for Base17, 1 byte for Scent — see +/// `docs/CODEC_COMPRESSION_ATLAS.md`). This keeps the versioned Lance +/// tombstone dataset lean for long-running audit trails. +/// +/// # Lifecycle +/// +/// A `Tombstone` is created via [`Tombstone::from_hot`] at mailbox-death time. +/// It is persisted to a dedicated Lance dataset (the "tombstone store") via +/// [`Tombstone::persist`]. It is NOT deleted when the mailbox exits — it +/// outlives the mailbox by design (Lance versioning guarantees). +/// +/// # Relationship to [`WitnessCorpus`] +/// +/// BLOCKED: whether the tombstone is ingested INTO the existing +/// `graph/arigraph/witness_corpus::WitnessCorpus` (D-CSV-6 / sprint-12) or +/// written to a separate Lance dataset dedicated to tombstones. The +/// `WitnessCorpus` currently holds `WitnessEntry { spo: u64, timestamp_ns, +/// source_url, evidence_blob }` — a tombstone is semantically different +/// (provenance-of-death vs provenance-of-observation). Confirm before wiring. +#[derive(Debug, Clone)] +pub struct Tombstone { + /// The mailbox that committed this fact and then died. + /// + /// Links the tombstone back to the originating [`HotWitness::mailbox_id`]. + pub mailbox_id: u64, + + /// The u64 key (dn_hash) of the calcified SPO fact this tombstone covers. + /// + /// This is the primary key used by [`WitnessLink`] to join the cold SPO + /// record to its provenance. + pub spo_key: u64, + + /// Lance version number at which this tombstone was written. + /// + /// Enables time-travel queries: `VersionedGraph::at_version(lance_version)` + /// retrieves the snapshot at the moment of calcification. + pub lance_version: u64, + + /// Nanosecond timestamp of the mailbox death (UNIX epoch). + pub committed_at_ns: u64, + + /// Compressed tombstone payload (~Scent/Base17 level). + /// + /// Encodes the minimal provenance: which mailbox, which fact, what + /// truth value, counterfactual residue presence flag. + /// + /// BLOCKED: exact compression entry point. The codec atlas places Scent + /// at 1 byte (ρ=0.937) and Base17 at 34 bytes (ρ=0.965). The candidate + /// entry points are: + /// - `ndarray::hpc::bgz17_bridge::Base17` (34-byte VSA, used in `neuron.rs`) + /// - Scent codec (1-byte) from `crates/bgz17` — confirm the public API + /// entry point (crate is in `exclude` in the workspace Cargo.toml; + /// direct dep may require adding it to `lance-graph`'s Cargo.toml). + /// Using raw `Box<[u8]>` until resolved. + pub compressed_payload: Box<[u8]>, + + /// Whether the originating `HotWitness` carried a counterfactual mantissa + /// (CausalEdge64 −6 nibble, §5). Presence flag only — the full mantissa + /// is in the episodic witness chain, not duplicated here. + pub has_counterfactual: bool, +} + +impl Tombstone { + /// Construct a `Tombstone` from a dying [`HotWitness`] and the Lance + /// version number assigned by the versioned store at commit time. + /// + /// Compresses the hot-witness provenance to ~Scent/Base17 level. + /// Called at mailbox-death time, before the `HotWitness` is dropped. + /// + /// # BLOCKED + /// + /// - Scent/Base17 compression entry point (see `compressed_payload` field). + /// - Lance version number retrieval API — `VersionedGraph::commit_encounter_round` + /// returns a `u64` version per `versioned.rs`; confirm the exact call site. + pub fn from_hot(_hot: &HotWitness, _lance_version: u64) -> Self { + // BLOCKED: compression entry point for the payload. + // BLOCKED: Lance version acquisition pattern. + todo!("D-ATOM-5: construct Tombstone from HotWitness at mailbox-death") + } + + /// Persist this `Tombstone` to the Lance versioned tombstone store. + /// + /// Appends one row to the tombstone Lance dataset (append-only — this is + /// the GoBD audit invariant; rows are NEVER deleted or overwritten). + /// + /// # BLOCKED + /// + /// - Lance versioned-store write API. The pattern in `versioned.rs` is + /// `Dataset::write(reader, path, Some(params)).await?` with + /// `WriteMode::Overwrite` (create-or-overwrite on first write). For + /// tombstones the correct mode is **append** (`WriteMode::Append` or + /// `RecordBatchIterator` append path) — confirm the exact Lance 4.0.0 + /// append API (workspace uses lance = 4.0.0 per `crates/lance-graph/ + /// Cargo.toml:36`; `WriteMode::Append` availability in lance 4.x TBD). + /// - Arrow schema for the tombstone RecordBatch (column names + types). + /// - Dataset path convention (where does the tombstone store live relative + /// to the `VersionedGraph` base path?). + pub async fn persist(&self, _base_path: &str) -> crate::error::Result<()> { + // BLOCKED: Lance WriteMode::Append API in lance 4.0.0. + // BLOCKED: Arrow schema + RecordBatch construction for tombstone row. + // BLOCKED: tombstone store path convention. + todo!("D-ATOM-5: persist Tombstone to Lance versioned tombstone store (append-only)") + } +} + +// ── WitnessLink ─────────────────────────────────────────────────────────────── + +/// Back-pointer linking a calcified cold SPO fact ↔ its [`Tombstone`]. +/// +/// # Link integrity (E-LADDER-SERVES-MAILBOX §6 "the one thing to nail") +/// +/// Two invariants must hold: +/// +/// 1. **SPO → Tombstone:** Every calcified SPO fact that was committed by a +/// mailbox (i.e. went through [`calcify`] + [`Tombstone::from_hot`]) carries +/// a `WitnessLink` with a valid `spo_key` + `tombstone_lance_version`. The +/// link is stored alongside the SPO record (either as a field of the SPO +/// quad or as a side-table join on `spo_key`). +/// +/// 2. **Tombstone outlives mailbox:** The [`Tombstone`] is written to Lance +/// BEFORE the mailbox is dropped. Lance versioning then guarantees +/// immutability — the tombstone cannot be deleted. The `WitnessLink` points +/// to a version number that is always readable via +/// `VersionedGraph::at_version(lance_version)`. +/// +/// # GoBD audit fallout (E-FIBU-GOBD-BY-CONSTRUCTION) +/// +/// The append-only Lance versioned tombstone store IS the audit log. Any +/// regulatory query ("which mailbox committed fact X, when, with what +/// confidence?") resolves by: +/// 1. Look up the SPO fact's `WitnessLink.spo_key`. +/// 2. Open the tombstone store at `tombstone_lance_version`. +/// 3. Read the `Tombstone` row for that key. +/// +/// No separate audit log, no bolted-on provenance system. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WitnessLink { + /// The SPO fact key (dn_hash of the subject–predicate–object triple). + /// + /// Primary join key between the cold SPO record and this link. + pub spo_key: u64, + + /// The mailbox that calcified this fact. + /// + /// Redundant with `Tombstone::mailbox_id` but stored in the link for + /// O(1) provenance lookup without opening the tombstone dataset. + pub mailbox_id: u64, + + /// Lance version number at which the corresponding [`Tombstone`] was + /// written. + /// + /// Use `VersionedGraph::at_version(tombstone_lance_version)` to open the + /// exact snapshot containing this tombstone row. + pub tombstone_lance_version: u64, +} + +impl WitnessLink { + /// Construct a `WitnessLink` from the SPO key and its corresponding + /// `Tombstone`. + /// + /// Called immediately after [`Tombstone::persist`] returns the committed + /// Lance version, so the link is always consistent with a durable row. + pub fn new(spo_key: u64, tombstone: &Tombstone) -> Self { + Self { + spo_key, + mailbox_id: tombstone.mailbox_id, + tombstone_lance_version: tombstone.lance_version, + } + } + + /// Verify that the linked [`Tombstone`] is readable at the stored Lance + /// version. + /// + /// Used by audit / integrity-check tooling (GoBD audit path). + /// + /// # BLOCKED + /// + /// - Lance `Dataset::checkout_version` API path — `versioned.rs` uses + /// `ds.checkout_version(version).await?`; confirm the tombstone dataset + /// open pattern (path + schema + version pin). + pub async fn verify(&self, _base_path: &str) -> crate::error::Result { + // BLOCKED: Lance checkout_version + tombstone-row lookup API. + todo!("D-ATOM-5: verify WitnessLink → open tombstone dataset at lance_version, confirm row exists for spo_key") + } +} diff --git a/crates/surreal_container/Cargo.toml b/crates/surreal_container/Cargo.toml new file mode 100644 index 00000000..9851d229 --- /dev/null +++ b/crates/surreal_container/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "surreal_container" +version = "0.1.0" +edition = "2021" +rust-version = "1.82" +license = "Apache-2.0" +publish = false +description = """ +Embedded SurrealDB-on-Lance cognitive store. + +Opens an in-process `surrealdb` Datastore backed by the `kv-lance` storage +engine (the AdaWorldAPI fork feature, NOT upstream surrealdb). Provides the +substrate that all surreal_container tasks (write, read, catalog, cache, …) +build on. + +Depends on task 01 (deps_substrate) being resolved before compiling. +""" + +# ── BLOCKED(C): surrealdb git source not confirmed ─────────────────────────── +# +# The `kv-lance` feature is NOT in upstream surrealdb on crates.io. +# It lives in the AdaWorldAPI fork (core/src/kvs/lance/{wal,memtable,flusher,mod}.rs). +# +# A fork-access human must supply: +# 1. The git URL (e.g. https://github.com/AdaWorldAPI/surrealdb) +# 2. The branch or tag to pin (e.g. branch = "kv-lance-v2") +# 3. Confirmation that `features = ["kv-lance"]` is the correct flag name. +# +# Until resolved, the surrealdb dependency below is commented out. The +# `[features]` section preserves the intent; the crate will not compile until +# the BLOCKED item is filled in. + +[dependencies] +# ── BLOCKED(C): uncomment and fill once fork coordinates are known ──────────── +# surrealdb = { git = "BLOCKED_SEE_C", branch = "BLOCKED_SEE_C", default-features = false, features = ["kv-lance"] } + +# ── BLOCKED(A)/(B): lance / lancedb version pins ───────────────────────────── +# These are the storage-engine crates the kv-lance backend talks to directly. +# Lance 6 is specified in the task but not yet confirmed as a published semver. +# Current workspace uses lance = "=4.0.0" / lancedb = "=0.27.2". +# Do NOT change these pins until BLOCKED(A) and BLOCKED(B) are resolved by a +# fork-access human who can confirm the exact crate versions and API surface. +lance = "=4.0.0" # BLOCKED(A): bump to Lance 6 once version confirmed +lancedb = { version = "=0.27.2", optional = true, default-features = false } # BLOCKED(B): bump to 0.28 + +# Runtime support (not blocked — these are well-known, confirmed versions) +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +futures = "0.3" +snafu = "0.8" + +[features] +default = [] +# lancedb-sdk: expose the high-level LanceDB table/connection surface. +# Optional: surreal_container can operate via raw lance without the SDK. +lancedb-sdk = ["dep:lancedb"] + +[dev-dependencies] +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +tempfile = "3" diff --git a/crates/surreal_container/src/cache.rs b/crates/surreal_container/src/cache.rs new file mode 100644 index 00000000..66dc55c0 --- /dev/null +++ b/crates/surreal_container/src/cache.rs @@ -0,0 +1,3 @@ +//! L2 moka cache over hot container reads (task 08). +//! +// TODO task 08 — implement diff --git a/crates/surreal_container/src/catalog.rs b/crates/surreal_container/src/catalog.rs new file mode 100644 index 00000000..ebf33147 --- /dev/null +++ b/crates/surreal_container/src/catalog.rs @@ -0,0 +1,6 @@ +//! Control-plane KV: goal-state records, container pointers, edge records (task 07). +//! +//! Small mutable control state only — NO bulk container payload stored here. +//! Records point into containers by container-id. +//! +// TODO task 07 — implement diff --git a/crates/surreal_container/src/compaction.rs b/crates/surreal_container/src/compaction.rs new file mode 100644 index 00000000..71b053c6 --- /dev/null +++ b/crates/surreal_container/src/compaction.rs @@ -0,0 +1,3 @@ +//! WAL log compaction / snapshot fold to bound replay growth (task 11). +//! +// TODO task 11 — implement diff --git a/crates/surreal_container/src/epoch.rs b/crates/surreal_container/src/epoch.rs new file mode 100644 index 00000000..b98b54b4 --- /dev/null +++ b/crates/surreal_container/src/epoch.rs @@ -0,0 +1,3 @@ +//! Epoch tick + harvest loop: samples the NARS Rubikon state and fires FLOW commits (task 09). +//! +// TODO task 09 — implement diff --git a/crates/surreal_container/src/fold.rs b/crates/surreal_container/src/fold.rs new file mode 100644 index 00000000..5bea5427 --- /dev/null +++ b/crates/surreal_container/src/fold.rs @@ -0,0 +1,3 @@ +//! Read-time fold: merge multiple epoch fragments into one view (task 06). +//! +// TODO task 06 — implement diff --git a/crates/surreal_container/src/lib.rs b/crates/surreal_container/src/lib.rs new file mode 100644 index 00000000..bf72cf2b --- /dev/null +++ b/crates/surreal_container/src/lib.rs @@ -0,0 +1,215 @@ +//! # `surreal_container` — embedded SurrealDB-on-Lance cognitive store +//! +//! This crate wires an **in-process** [`surrealdb`] [`Datastore`] backed by the +//! `kv-lance` storage engine (the `AdaWorldAPI/surrealdb` fork feature — NOT +//! upstream surrealdb on crates.io). +//! +//! ## Architecture +//! +//! ```text +//! ┌─────────────────────────────────────────┐ +//! │ surreal_container │ +//! │ │ +//! │ SurrealStore (this module) │ +//! │ └── surrealdb::Datastore │ +//! │ └── kv-lance backend │ +//! │ └── Lance dataset │ +//! │ (append-only) │ +//! │ │ +//! │ write (task 04) ── epoch container │ +//! │ read (task 05) ── container fetch │ +//! │ fold (task 06) ── read-time fold │ +//! │ catalog (task 07) ── goal/ptr/edge KV │ +//! └─────────────────────────────────────────┘ +//! ``` +//! +//! ## Blocked items (must be resolved before this crate compiles) +//! +//! - **BLOCKED(A)**: Lance 6 semver not confirmed; current pins are `lance = "=4.0.0"`. +//! - **BLOCKED(B)**: LanceDB 0.28 semver not confirmed; current pins are `lancedb = "=0.27.2"`. +//! - **BLOCKED(C)**: The `surrealdb` fork git URL, branch/tag, and exact `kv-lance` +//! feature flag name must be provided by a fork-access human before the +//! `[dependencies]` in `Cargo.toml` can be filled in. +//! - **BLOCKED(D)**: If Lance 6 pulls `lance-index` which depends on `ndarray 0.16` +//! from crates.io, a `[patch.crates-io]` entry must alias it to the +//! `AdaWorldAPI/ndarray` fork path. Requires Lance 6 `Cargo.lock` inspection. +//! +//! ## Embedded Datastore init pattern (BLOCKED — do not call until unblocked) +//! +//! Once BLOCKED(C) is resolved, the init will look approximately like: +//! +//! ```rust,ignore +//! // BLOCKED(C): surrealdb Datastore API for kv-lance is not confirmed. +//! // The pattern below is a DESIGN SKETCH derived from cognitive-substrate.md +//! // (which describes core/src/kvs/lance/{wal,memtable,flusher,mod}.rs in the fork). +//! // Do NOT ship this as real code until a fork-access human validates the API. +//! // +//! // use surrealdb::Datastore; +//! // +//! // let ds = Datastore::new("lance://path/to/store").await?; +//! // let ses = Session::owner().with_ns("lance_graph").with_db("containers"); +//! // ds.execute("DEFINE TABLE container SCHEMAFULL;", &ses, None).await?; +//! ``` +//! +//! ## Usage (once unblocked) +//! +//! ```rust,ignore +//! use surreal_container::SurrealStore; +//! +//! let store = SurrealStore::open("/path/to/lance/store").await?; +//! // write/read/catalog modules build on `store.ds()`. +//! ``` + +// ── BLOCKED(C): surrealdb is not yet a real dependency (see Cargo.toml) ───── +// When BLOCKED(C) is resolved, replace the stub import below with: +// use surrealdb::{Datastore, Session}; +// +// For now the module compiles as a skeleton without the surrealdb dep so +// the workspace member resolves cleanly. + +/// Embedded SurrealDB-on-Lance Datastore handle. +/// +/// Wraps a `surrealdb::Datastore` opened with the `kv-lance` storage engine. +/// All other modules in this crate receive a reference to this struct rather +/// than constructing their own Datastore instances. +/// +/// # Panics +/// +/// Does not panic. All fallible operations return [`SurrealContainerError`]. +/// +/// # Blocked +/// +/// `SurrealStore::open` cannot be implemented until BLOCKED(C) is resolved. +/// The struct definition is present so downstream module stubs can reference +/// the type. +pub struct SurrealStore { + // BLOCKED(C): field type `surrealdb::Datastore` requires the fork dep. + // Replace the placeholder below with the real field once unblocked: + // + // ds: surrealdb::Datastore, + // + _placeholder: std::marker::PhantomData<()>, +} + +impl SurrealStore { + /// Open (or create) an embedded `kv-lance` Datastore at `lance_path`. + /// + /// `lance_path` is the filesystem path to the Lance dataset directory that + /// `surrealdb`'s `kv-lance` backend will manage. The directory is created + /// if it does not exist. + /// + /// # Errors + /// + /// Returns [`SurrealContainerError::Blocked`] until BLOCKED(C) is resolved. + /// + /// # Example (once unblocked) + /// + /// ```rust,ignore + /// let store = SurrealStore::open("/data/cognitive-store").await?; + /// ``` + pub async fn open(_lance_path: &str) -> Result { + // BLOCKED(C): cannot construct a real Datastore until the surrealdb + // fork dep (with kv-lance feature) is wired into Cargo.toml. + // + // Once unblocked, implementation sketch (API NOT confirmed — sketch only): + // + // let ds = surrealdb::Datastore::new( + // &format!("lance://{}", _lance_path) + // ).await + // .map_err(|e| SurrealContainerError::Init { source: e })?; + // + // Ok(Self { ds }) + // + Err(SurrealContainerError::Blocked { + reason: "BLOCKED(C): surrealdb kv-lance fork dep not wired; \ + see Cargo.toml and workspace Cargo.toml comments", + }) + } + + /// Return a reference to the inner `surrealdb::Datastore`. + /// + /// Modules `write`, `read`, `catalog`, etc. receive this reference and + /// execute SurrealDB statements through it. + /// + /// # Blocked + /// + /// Returns `None` until BLOCKED(C) is resolved. + /// + /// ```rust,ignore + /// // Once unblocked, return type will be `&surrealdb::Datastore` + /// pub fn ds(&self) -> &surrealdb::Datastore { &self.ds } + /// ``` + pub fn ds(&self) -> Option<()> { + // BLOCKED(C): real return type is `&surrealdb::Datastore` + None + } +} + +// ── Error type ─────────────────────────────────────────────────────────────── + +/// Errors produced by `surreal_container`. +#[derive(Debug)] +pub enum SurrealContainerError { + /// A required dependency or API is not yet wired. + /// + /// Remove this variant once all `BLOCKED` items are resolved. + Blocked { + /// Human-readable description of what is missing. + reason: &'static str, + }, + + // BLOCKED(C): add `Init { source: surrealdb::Error }` once fork dep lands. + // BLOCKED(A)/(B): add `Lance { source: lance::Error }` once Lance 6 is pinned. +} + +impl std::fmt::Display for SurrealContainerError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Blocked { reason } => write!(f, "surreal_container blocked: {reason}"), + } + } +} + +impl std::error::Error for SurrealContainerError {} + +// ── Module stubs (tasks 04-07 + future tasks) ───────────────────────────── + +/// Write path: one epoch-container → one `kv-lance` record (task 04). +/// +/// Append-only; one record per epoch, never update-in-place. +// TODO task 04 +pub mod write; + +/// Read path: fetch a container by id from `kv-lance` (task 05). +// TODO task 05 +pub mod read; + +/// Read-time fold: merge multiple epoch fragments into one view (task 06). +// TODO task 06 +pub mod fold; + +/// Control-plane KV: goal-state, container pointers, edges (task 07). +/// +/// Small mutable records only — no bulk data stored here. +// TODO task 07 +pub mod catalog; + +/// L2 moka cache over hot container reads (task 08). +// TODO task 08 +pub mod cache; + +/// Epoch tick + harvest loop (task 09). +// TODO task 09 +pub mod epoch; + +/// Lock-free handoff ring between OS-thread cores and the ractor membrane (task 10). +// TODO task 10 +pub mod ring; + +/// WAL log compaction / snapshot fold (task 11). +// TODO task 11 +pub mod compaction; + +/// Clean-writer invariants: append-only, single-writer, no LWW collision (task 12). +// TODO task 12 +pub mod writer_invariants; diff --git a/crates/surreal_container/src/read.rs b/crates/surreal_container/src/read.rs new file mode 100644 index 00000000..887a3b36 --- /dev/null +++ b/crates/surreal_container/src/read.rs @@ -0,0 +1,3 @@ +//! Read path: fetch a container by id from the `kv-lance` Datastore (task 05). +//! +// TODO task 05 — implement diff --git a/crates/surreal_container/src/ring.rs b/crates/surreal_container/src/ring.rs new file mode 100644 index 00000000..a72bb02f --- /dev/null +++ b/crates/surreal_container/src/ring.rs @@ -0,0 +1,3 @@ +//! Lock-free handoff ring between OS-thread NARS cores and the async ractor membrane (task 10). +//! +// TODO task 10 — implement diff --git a/crates/surreal_container/src/write.rs b/crates/surreal_container/src/write.rs new file mode 100644 index 00000000..6bfd8bd5 --- /dev/null +++ b/crates/surreal_container/src/write.rs @@ -0,0 +1,6 @@ +//! Write path: one epoch-container → one `kv-lance` record (task 04). +//! +//! Append-only; one record per epoch, container-id as key. +//! Single writer; no merge logic here (fold is task 06). +//! +// TODO task 04 — implement diff --git a/crates/surreal_container/src/writer_invariants.rs b/crates/surreal_container/src/writer_invariants.rs new file mode 100644 index 00000000..e4f516fd --- /dev/null +++ b/crates/surreal_container/src/writer_invariants.rs @@ -0,0 +1,7 @@ +//! Clean-writer invariants: append-only, single-writer, no LWW silent collision (task 12). +//! +//! This module documents (and will enforce) the invariants the write path must +//! satisfy so that the holograph-superposition merge (not LWW) can be wired in +//! without data loss. See cognitive-substrate.md Gap #2 for context. +//! +// TODO task 12 — implement