Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .claude/board/AGENT_LOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
## [Main thread / Opus, autoattended] D-EW64-3/4 review LAND + CodeRabbit contains nit applied (#448)

Opus review agent: **LAND** — no P0/P1 (exhaustively verified: coldest == eviction victim for every word, no holes, promote_into word == promote().0 + sink gets exactly the eviction; firewall + API clean; 545 green). 2 optional editorial P2s NOT applied (don't block). CodeRabbit: 1 nit (💤 low value) — `contains` → `self.iter().any(|x| x == e)` (more idiomatic, reuses iter; equivalent) — APPLIED. episodic_edges tests still green; default clippy clean. #448 CI re-runs on this push.

---

## [Main thread / Opus, autoattended] D-EW64-3 + D-EW64-4 — EpisodicEdges64 cold-tier read surface + DemotionSink seam

**Branch:** claude/jolly-cori-clnf9. **Cargo:** contract lib **545 green** (+10 episodic_edges: 6 coldest/contains + 4 promote_into); default clippy `-D warnings` clean; `episodic_edges.rs` clean at pedantic+nursery. Plan-agent-sequenced (the 2 unblocked slices of 3; slice 3 + plasticity-write + comprehension↔arcuate are GATED/needs-design — flagged for user).

**Shipped:** `EpisodicEdges64::coldest()` (the eviction victim, symmetric to `strongest`) + `contains()` (family-discriminating membership); `DemotionSink` trait + `promote_into()` (the hot→cold exit seam — promote routing the evicted edge to the cold connectome; surreal/LanceDB-LIVE impls deferred + GATED on OQ-11.6, same dependency-inversion idiom as `MailboxSoaOwner`). Prepended the `Heel`-vs-`PlasticityState` correction (Plan agent caught my E-EW64-STRENGTH imprecision).

**Loop:** drafted → next: Opus review agent on the diff → PR (claude/jolly-cori-clnf9 → main) → subscribe. Accumulating for morning merge.

---

## [Main thread / Opus, autoattended] PR #447 MERGED — D-EW64-2 + white-matter findings landed

**#447 merged → main** (EpisodicEdges64::{promote, strongest} MRU hot-tier + E-PLANNING-IS-WHITE-MATTER + E-EW64-STRENGTH-IS-CE64-PLASTICITY + MD001 fix). Loop iteration complete: drafted → Opus review (LAND, +2 coverage tests) → CodeRabbit (1 MD001, fixed) → CI green → merged. Session auto-unsubscribed. Branch synced onto main.

**Loop continues:** spawned a Plan agent to sequence the next UNBLOCKED, offline-testable, firewall-clean slices toward the EW64↔CE64 white-matter prefetch seam. Surreal-side stays GATED (OQ-11.6); no live-pipeline rewrites unattended. Next: execute slice 1 from the plan → review → PR.

---

## [Main thread / Opus, autoattended] D-EW64-2 review (LAND) + 2 coverage tests added

**Branch:** claude/jolly-cori-clnf9 | **PR #447.** Opus review agent verdict: **LAND** — no P0/P1. It re-implemented `promote` and brute-forced all 0-4-edge words × every promote target: zero invariant violations (strongest==e, no dups, eviction only on full+new, coldest==slot 3, idempotence, order preserved); packing/shift correct; firewall clean; API consistent. Applied its 2 recommended P2 coverage tests: `promote_cross_family_local_collision_is_not_deduped` (dedup discriminates on family) + `promote_chains_mru_aging_and_appends_fresh_on_non_full` (multi-promote MRU aging + fresh-on-non-full append). Left the 3rd P2 (pre-existing `to_slot` masking on contract-violating `EdgeRef` input) as out-of-scope (module-wide decision; only triggers on invalid input).
Expand Down
8 changes: 8 additions & 0 deletions .claude/board/EPIPHANIES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 2026-06-01 — E-EW64-STRENGTH CORRECTION — "W15 0..3 plasticity" is `high_heel::Heel` (128-byte container field), NOT the 64-bit `CausalEdge64`; the 64-bit edge's plasticity is the 3-bit-per-plane `PlasticityState`

**Status:** CORRECTION (factual, Plan-agent-grounded against source 2026-06-01). Refines `E-EW64-STRENGTH-IS-CE64-PLASTICITY`'s mechanism claim; does NOT change D-EW64-2 (MRU slot-order strength stores NO plasticity — it stands regardless).

The original said "strength = co-addressed `CausalEdge64` plasticity (W15 0=frozen..3=hot)." Source check: the W15 byte-3 scalar 0..3 is `high_heel::Heel` (`high_heel.rs:168`, a **128-byte container field**), NOT the 64-bit `CausalEdge64`. The 64-bit edge's plasticity is `causal-edge::PlasticityState` (`plasticity.rs`) = **3 bits, one per S/P/O plane** (heat/freeze/hot_count), not a 0..3 scalar. "Co-addressed CE64 plasticity" must pick ONE model — a **USER decision**. Consequence: the **plasticity-WRITE co-fire op is GATED** on 3 counts — (1) `causal-edge` doesn't build offline (anstream uncached); (2) the Heel-scalar-vs-PlasticityState-per-plane mismatch needs user design; (3) it hits `I-LEGACY-API-FEATURE-GATED` (the v1 PLAST_SHIFT=49 vs v2=50 boundary codex caught 5× in sprint-11). D-EW64-2's MRU (slot-order = recency, no stored weight) is unaffected. The Hebbian "wire together" weight-bump waits on the user's plasticity-model decision. Cross-ref: D-EW64-2/3/4, `causal-edge::edge.rs:471/483` (v2-gated getter/setter), `I-LEGACY-API-FEATURE-GATED`.

---

## 2026-05-31 — E-EW64-STRENGTH-IS-CE64-PLASTICITY — EW64's "stronger immediate edges" need no new field (strength = co-addressed CE64 plasticity + MRU slot-order); the surreal LIVE wingman is the designed orchestrator but GATED + optional

**Status:** FINDING (design resolution, register-lazy). User-stated 2026-05-31 ("episodicwitness64 needs the stronger immediate edges; wingman orchestration in surrealdb in same substrate is an option"). Resolves the strength gap in `EpisodicEdges64`; weighs the surreal-same-substrate option. Feeds the queued spec `episodic-witness64-ce64-prefetch.md`.
Expand Down
2 changes: 2 additions & 0 deletions .claude/board/LATEST_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -496,3 +496,5 @@ PR sequence: #360 → #361 → post-#360 substrate-sweep (this PR).
### Current Contract Inventory — new entry

- **`crates/lance-graph-arm-discovery`** (NEW, **excluded** standalone zero-dep crate; build via `cargo test --manifest-path crates/lance-graph-arm-discovery/Cargo.toml`). The **Aerial+** Rust transcode (Karabulut 2025, 2504.19354v1) — the upstream runtime-data proposer leg of `streaming-arm-nars-discovery-v1`. Public surface: `encode::{FeatureSpec, Dataset}`, `rule::{Item, CandidateRule, Proposer}`, `translator::{arm_to_nars, NarsTruth, CandidateTriple, FeedProjector, DebugProjector, NARS_PERSONALITY_K}`, `ndjson::to_ndjson`, and (feature `aerial`, default-on) `aerial::{Rng, AerialAutoencoder, AerialParams, AerialProposer, extract_rules, ExtractParams}`. 35/35 tests, clippy `-D warnings` clean. Emits the `{"s","p","o","f","c"}` ndjson the SPO store loader reads; `(f,c)` == `TruthValue::new(f,c)` == `ruff_spo_triplet::Triple{f,c}`. Determinism boundary: nondeterministic AE is seeded + feature-gated + emits *proposals* only. Synergy map: `.claude/knowledge/aerial-arm-ruff-spo-codegen-synergies.md`. Status board: D-ARM-13 (Shipped on branch) + D-ARM-SYN-1/2/3 (Queued). **Not** in `lance-graph-contract` yet — `rule`/`translator` are the local seam until D-ARM-1/2 land the shared carriers.

> **2026-06-01 — PR-in-flight (autoattended)** (D-EW64-3/4): `lance_graph_contract::episodic_edges` gains `EpisodicEdges64::{coldest, contains, promote_into}` + the `DemotionSink` trait. `coldest()` = the eviction victim (symmetric to `strongest()`); `contains()` = family-discriminating membership; `promote_into(e, sink)` = `promote` routing the evicted (coldest) edge to a `DemotionSink` — the hot→cold connectome exit. `DemotionSink` impls (surreal/LanceDB-LIVE "wingman", `E-SUBSTRATE-IS-THE-SCHEDULER`) are deferred + GATED on OQ-11.6. Zero-dep; contract lib 545 green; default clippy clean; `episodic_edges.rs` pedantic+nursery clean.
7 changes: 7 additions & 0 deletions .claude/board/STATUS_BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -729,3 +729,10 @@ When a new deliverable is added to a plan:
When a deliverable is abandoned:
1. Edit Status → **Abandoned**. Don't remove the row.
2. Cite the replacement in Notes.

## D-EW64-3 / D-EW64-4 (2026-06-01, autoattended)

| D-id | deliverable | status | evidence |
|---|---|---|---|
| D-EW64-3 | `EpisodicEdges64::{coldest, contains}` — MRU cold-tier read surface | In PR | contract lib 545 green; clippy clean |
| D-EW64-4 | `DemotionSink` trait + `promote_into` — hot→cold exit seam (impls gated OQ-11.6) | In PR | contract lib 545 green; clippy clean |
175 changes: 175 additions & 0 deletions crates/lance-graph-contract/src/episodic_edges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,54 @@ impl EpisodicEdges64 {
(Self(raw), evicted)
}

/// [`promote`](Self::promote) `e`, routing any eviction to `sink` (the cold
/// connectome). Returns the new word. When a fresh edge displaces a full
/// word the sink receives exactly the coldest edge (= [`coldest`](Self::coldest));
/// no eviction (non-full word, or re-firing a present edge) → sink untouched.
/// This is the hot tier's defined exit into the cold tier — the contract-side
/// half of `E-SUBSTRATE-IS-THE-SCHEDULER` (the surreal/LanceDB-LIVE wingman
/// implements [`DemotionSink`], gated on OQ-11.6).
#[must_use]
pub fn promote_into(self, e: EdgeRef, sink: &mut impl DemotionSink) -> Self {
let (next, evicted) = self.promote(e);
if let Some(victim) = evicted {
sink.demote(victim);
}
next
}

/// The strongest / most-immediate edge (slot 0 under the MRU invariant), or
/// `None` if the word is empty.
#[must_use]
pub const fn strongest(self) -> Option<EdgeRef> {
self.edge(0)
}

/// The coldest / least-immediate present edge — the **last** occupied slot
/// under the MRU invariant, i.e. exactly the edge [`promote`](Self::promote)
/// will evict when a fresh edge displaces a full word. `None` if empty.
/// Symmetric to [`strongest`](Self::strongest); the cold tier peeks the
/// victim here before it falls out.
#[must_use]
pub const fn coldest(self) -> Option<EdgeRef> {
let mut last = None;
let mut i = 0;
while i < Self::CAPACITY {
if let Some(e) = self.edge(i) {
last = Some(e);
}
i += 1;
}
last
}

/// Is `e` present in any slot? Membership discriminates `family` (so
/// `cross(3, 3)` ≠ `intra(3)`), matching [`promote`](Self::promote)'s dedup.
#[must_use]
pub fn contains(self, e: EdgeRef) -> bool {
self.iter().any(|x| x == e)
}

/// Iterate the present edges in slot order.
pub fn iter(self) -> impl Iterator<Item = EdgeRef> {
(0..Self::CAPACITY).filter_map(move |i| self.edge(i))
Expand Down Expand Up @@ -252,6 +293,19 @@ impl EpisodicEdges64 {
}
}

/// Receives an edge demoted out of the hot 4-slot tier — the **cold connectome**
/// (`E-EW64-STRENGTH-IS-CE64-PLASTICITY`, `E-SUBSTRATE-IS-THE-SCHEDULER`).
///
/// This is the stable **zero-dep seam** between the hot tier (the `EpisodicEdges64`
/// MRU word, in the SoA) and the cold tier (the full connectome). Impls — the
/// surreal-LIVE / LanceDB-LIVE "wingman" that persists and re-prefetches demoted
/// edges — are deferred and GATED on OQ-11.6; the contract owns only the seam,
/// the same dependency-inversion idiom as [`crate::soa_view::MailboxSoaOwner`].
pub trait DemotionSink {
/// An edge aged out of the hot tier; route it to the cold connectome.
fn demote(&mut self, evicted: EdgeRef);
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -497,4 +551,125 @@ mod tests {
]
);
}

#[test]
fn coldest_of_empty_is_none() {
assert_eq!(EpisodicEdges64::empty().coldest(), None);
}

#[test]
fn coldest_of_single_equals_strongest() {
let w = EpisodicEdges64::empty()
.push(EdgeRef::intra(7).unwrap())
.unwrap();
assert_eq!(w.coldest(), w.strongest());
assert_eq!(w.coldest(), EdgeRef::intra(7));
}

#[test]
fn coldest_of_full_word_is_last_slot() {
let mut w = EpisodicEdges64::empty();
for k in 1..=4u16 {
w = w.push(EdgeRef::intra(k).unwrap()).unwrap();
}
assert_eq!(w.coldest(), EdgeRef::intra(4)); // slot 3
}

#[test]
fn coldest_equals_promote_eviction_victim() {
// The edge coldest() names is exactly the one promote evicts when a
// fresh edge displaces a full word — ties the read + write APIs.
let mut w = EpisodicEdges64::empty();
for k in 1..=4u16 {
w = w.push(EdgeRef::intra(k).unwrap()).unwrap();
}
let victim = w.coldest();
let (_w2, evicted) = w.promote(EdgeRef::intra(9).unwrap());
assert_eq!(evicted, victim);
}

#[test]
fn contains_present_and_absent() {
let w = EpisodicEdges64::empty()
.push(EdgeRef::intra(1).unwrap())
.unwrap()
.push(EdgeRef::cross(2, 5).unwrap())
.unwrap();
assert!(w.contains(EdgeRef::intra(1).unwrap()));
assert!(w.contains(EdgeRef::cross(2, 5).unwrap()));
assert!(!w.contains(EdgeRef::intra(2).unwrap()));
}

#[test]
fn contains_discriminates_family() {
let w = EpisodicEdges64::empty()
.push(EdgeRef::intra(3).unwrap())
.unwrap();
assert!(w.contains(EdgeRef::intra(3).unwrap()));
assert!(
!w.contains(EdgeRef::cross(3, 3).unwrap()),
"family is discriminated"
);
}

/// Test sink that records demoted edges in arrival order.
struct VecSink(Vec<EdgeRef>);
impl DemotionSink for VecSink {
fn demote(&mut self, evicted: EdgeRef) {
self.0.push(evicted);
}
}

#[test]
fn promote_into_non_full_leaves_sink_empty() {
let mut sink = VecSink(Vec::new());
let w = EpisodicEdges64::empty()
.push(EdgeRef::intra(1).unwrap())
.unwrap()
.promote_into(EdgeRef::intra(2).unwrap(), &mut sink);
assert!(sink.0.is_empty(), "no eviction on a non-full word");
assert_eq!(w.strongest(), EdgeRef::intra(2));
}

#[test]
fn promote_into_full_routes_coldest_to_sink() {
let mut w = EpisodicEdges64::empty();
for k in 1..=4u16 {
w = w.push(EdgeRef::intra(k).unwrap()).unwrap();
}
let victim = w.coldest().unwrap();
let mut sink = VecSink(Vec::new());
let w2 = w.promote_into(EdgeRef::intra(9).unwrap(), &mut sink);
assert_eq!(sink.0, vec![victim], "exactly the coldest is demoted");
assert_eq!(
w2,
w.promote(EdgeRef::intra(9).unwrap()).0,
"word equals promote().0; the sink only adds routing"
);
}

#[test]
fn promote_into_chain_accumulates_evictees_in_age_order() {
let mut sink = VecSink(Vec::new());
let mut w = EpisodicEdges64::empty();
// 1..=4 fill [4,3,2,1]; 5 evicts 1; 6 evicts 2.
for k in 1..=6u16 {
w = w.promote_into(EdgeRef::intra(k).unwrap(), &mut sink);
}
assert_eq!(
sink.0,
vec![EdgeRef::intra(1).unwrap(), EdgeRef::intra(2).unwrap()]
);
}

#[test]
fn promote_into_refire_present_leaves_sink_untouched() {
let mut w = EpisodicEdges64::empty();
for k in 1..=4u16 {
w = w.push(EdgeRef::intra(k).unwrap()).unwrap();
}
let mut sink = VecSink(Vec::new());
let _ = w.promote_into(EdgeRef::intra(2).unwrap(), &mut sink);
assert!(sink.0.is_empty(), "re-firing a present edge never demotes");
}
}
Loading