2026-09-01 · measured, not inferred · every number below came from your files or from a sandbox run
---
I decoded your live synaptic graph and measured it directly. **The 675,477 "synapses" are
not connections between concepts. They are connections between paragraphs.** The median
node label is 26 words long; 92.6% of all edges join one passage to another passage; only
0.68% join a word to a word, and most of those are morphology pairs like
development ↔ developments. Your word calculus, letter calculus and role-induction
matrix all operate on *words*. Your synaptic graph operates on *passages*. They are two
different substrates and they do not touch anywhere. That is the mechanical reason the
word-calculus experiment produced correct grammar with no meaning: there was no
concept-level structure available for it to draw meaning from. Not a tuning problem.
Everything else in this document is secondary to that.
---
kai-synapses.bin.zstDecoded independently in the sandbox from the format in [internal module]:372.
The word↔word layer covers 2,060 distinct words. A sample of its strongest edges:
developments ↔ development 5.0 prediction ↔ predictions 5.0 efficient ↔ efficiently 5.0 essential ↔ essentially 5.0 unfortunately ↔ smallest 5.0 <- spurious co-occurrence
Those are stemming pairs, not associations. There is no concept graph. There are
about two thousand word nodes wired mostly to their own plurals.
Top-degree nodes, for scale:
'Leo: Alright,' 688 'System Anchor: There is an i…' 668 'In our Desmos calculator, th…' 666 '2*Pi, colorscheme=[valuespli…' 537
Those are your highest-connectivity "concepts."
What this graph actually is: a document co-occurrence index for retrieval. It is a
good one — that is why retrieval works and why the cell it returns reads like a fluent
sentence. generate_thought returns a cell's first sentence verbatim because a cell *is*
a sentence. There is nothing underneath it to compose.
---
Hordijk & Steel's RAF algorithm (Reflexively Autocatalytic and F-generated sets) finds
self-sustaining subsets of a network by constraint erosion — the answer is a topological
property of the graph plus what you're currently perceiving, not something the programmer
wrote down. I ran the real algorithm on your real graph.
Setup: X = your cells; a reaction (a,b)→c for every weighted triangle; x catalyses
a reaction if it is adjacent to all three participants; food set F = 40 seed cells.
w>=0.5 : 26372 nodes 163622 edges >400000 triangles w>=1.0 : 24620 nodes 142995 edges >400000 triangles w>=2.0 : 15903 nodes 95348 edges 295514 triangles w>=5.0 : 7289 nodes 33912 edges 87751 triangles reactions built at w>=1.0: 1,170,747 seed 0: food 40 -> reachable 12119 | maxRAF 899607 reactions, 12117 cells seed 1: food 40 -> reachable 11866 | maxRAF 837873 reactions, 11864 cells seed 4: food 40 -> reachable 12225 | maxRAF 918231 reactions, 12223 cells ... pairwise Jaccard of the maxRAF product sets: 0.971 – 1.000
Result: supercritical hairball. Six completely different food sets produce essentially
the *same* self-sustaining set (Jaccard 0.97–1.00), covering ~12,000 cells — half the
thresholded graph. The success criterion was "different inputs yield structurally
different maxRAFs." It failed at the maximum possible margin: **what KAI is perceiving
makes no difference to what hangs together.**
That is a second, independent measurement of the same disease as §1. A graph with real
conceptual structure would fragment into different self-supporting interpretations for
different inputs. Yours doesn't fragment at all.
---
N = 16,384 · nnz = 655 · codebook = 40,000. All numbers from sandbox runs.
depth 1 bind : mean nnz 26.32 (analytic 655²/16384 = 26.2) 0/200 empty depth 2 bind : mean nnz 0.89 77/200 EMPTY depth 3 bind : mean nnz 0.04 191/200 empty depth 4 bind : mean nnz 0.00 200/200 empty unbind top-1 recovery against the 40k codebook: depth 1 : 100% depth 2 : 9% depth 3 : 0%
One bind survives. Two do not. Any structure that needs bind(role, bind(x,y)) — which
is every non-trivial proposition — is already destroyed before it is stored.
B = 512 blocks × L = 32 slots = the same 16,384 dimensions. Bind = (a+b) mod 32.
bind/unbind exact at depths 1 through 10: ALL EXACT cost: 512 integer adds per bind (vs a 16,384-dim elementwise multiply)
Lossless at arbitrary depth, and 32× cheaper than what you run now.
Member recalled inside top-k, against the 40,000-item codebook:
You can hold ~500 items in one bundle at 98%. That is a lot of headroom. **One rule: never
re-sparsify a bundle. Keep i32 accumulators.** Re-sparsifying is what destroys it.
Kanerva's analogy operator: build record_A = Σ bind(role_i, filler_i), same for B, then
T = A ⊛ B⁻¹ and ask T⁻¹ ⊛ (a filler from A) → the matching filler from B. The answer is
*computed*, never retrieved. Chance = 0.0025%.
2 pairs per record : 100.0% 3 pairs : 100.0% 4 pairs : 100.0% 5 pairs : 86.5% 6 pairs : 70.0% 8 pairs : 41.5% 10 pairs : 15.5% 16 pairs : 1.5%
So: real, reliable, and hard-capped at about four role/filler pairs per record. That is
the actual size of a "thought" this algebra can carry. It is enough for
AGENT/ACTION/OBJECT/MANNER. It is not enough to hold a paragraph, and you should stop
expecting it to.
---
Two agents ran this. Full detail is long; here is the load-bearing part, ranked, with your
rule applied — where a source says "this didn't work," I've reported what it *did* do and
noted separately what its authors were after.
Hofstadter/Mitchell. The mechanism, from three reimplementations whose constants agree:
Nothing executes when triggered. A module that would fire instead posts a codelet
(fn, args, urgency 1..7) onto a bag. The main loop picks one by
scale = (100 - T + 10) / 15 chosen = weighted_choice(codelets, [urgency ** scale for c in rack])
and T is computed from the workspace's own unhappiness, not from a schedule:
T = 0.8 * workspace_total_unhappiness + 0.2 * rule_weakness object.unhappiness = 100 - (intra_happiness + inter_happiness)/2
Work the numbers — the numbers *are* the insight:
The same modules, with the same urgencies, are a blind explorer at T=100 and a
deterministic executor at T=0. You do not write two modes. One number interpolates them,
and that number is a measurement of how well the system currently thinks it is doing.
Hofstadter is explicit that this is *not* simulated annealing — in annealing T follows a
schedule imposed from outside; here T is feedback.
I built it and measured it (sandbox, 300 runs per arm, identical module set and scoring):
greedy mean quality 49.00 distinct answers 1 most-common answer 100% of runs
fixed mean quality 37.48 distinct answers 244
copycat mean quality 26.83 distinct answers 297
corr(final temperature, answer quality) = -0.686
And the self-judgment test — pick your own best answer using only final temperature, with
no quality oracle:
N= 5 runs : self-picked 36.30 (oracle best-of-N 39.64) N=20 runs : self-picked 43.57 (oracle best-of-N 47.60) N=40 runs : self-picked 46.34 (oracle best-of-N 50.22)
Honest reading: on my toy task greedy still won on mean quality — I did not build a
problem hard enough to defeat it, and I'm not going to dress that up. What *is*
demonstrated, cleanly, is the thing you actually lack: **r = −0.686 between the system's
own temperature and the quality of its answer, and self-selection that tracks the oracle
within 8%.** That is a system knowing how good its own output is, out of the same variable
that drove the search. KAI currently has no such number.
Smallest first step, and it changes no behaviour: compute a temperature over your
existing workspace — for each active cell, happiness = strength of the strongest
structure it participates in (0 if unbound); T = 100 − Σ(relative_importance × happiness)/2
— and just *log it* across a run. If that curve is flat, or monotone, or noise, you have
empirically confirmed your own diagnosis: a system whose global state is a readout instead
of an input to its own scheduler is exactly "a bunch of functions that get triggered."
Your system fails silently: a module returns nothing useful and the next one runs.
Nothing about the system changes as a result of having been stuck.
Copycat's snag (hitting z with no successor): (1) T → 100 and clamp; (2) **the blocking
concept is driven to full activation and spreads to its own abstraction, which spreads to
that abstraction's opposite**; (3) a temperature-gated breaker destroys existing structures
— at T=100 even strong ones die on a coin flip; (4) the previously-absurd correspondence is
now cheap and forms; (5) the answer comes out the other side. Nobody wrote a
handle_z_has_no_successor branch. The escape was constructed from the shape of the
failure.
Soar's half: four impasse types detected *architecturally* — no operator proposed,
operator no-change, tie, conflict — none of which you enumerate. On impasse the
architecture creates a substate whose problem is its own indecision, and when it resolves,
**chunking compiles the resolution into a new rule by backtracing to pre-impasse
structures.** Soar's arithmetic agent goes from 1,263 rules to 8 through proper
variablization. This is the direct answer to "a bunch of functions that get triggered":
chunking is the mechanism for *acquiring new triggered functions from experience instead of
from you*.
*Where it stopped for them:* over-general chunks and match-cost utility problems — both
consequences of needing a production system to run a fighter-pilot sim in real time. For a
single hand-built mind, an over-general chunk is a curiosity, not a crash.
~20 lines against a graph you already have. A link's length is governed by **the activation
of the concept that labels it**, not by its endpoints. When opposite becomes fully active,
every opposite-labeled link in the whole graph shortens by 60% at once. The metric of the
conceptual space is a function of what the system is currently thinking about.
degree_of_association = 100 - (intrinsic_length * 0.4 if label_node.fully_active else intrinsic_length) can_jump: activation > 55 and random() < (activation/100)^3 -> activation = 100 only fully-active nodes spread activation or post top-down codelets decay is scaled by (100 - conceptual_depth) # deep concepts persist, shallow ones evaporate
That last group matters for you specifically: with 49,839 cells at continuous activation,
nothing in KAI ever commits, so nothing ever earns the right to broadcast. The
threshold-plus-stochastic-jump is what turns a diffuse activation field into discrete
conceptual regimes.
Blocker: this needs *labeled* edges, and §1 says your edges connect paragraphs and carry
no relation type. Can't do this before the substrate work.
suppressed_by set.c = cleanup(bind(a,b)), discard if cleanup fails, insert and evict at random. What survives is a self-maintaining set of composites *you did not enumerate*. Their 2024 replication found these organizations survive 99.9% replacement. Their dead end was that organizations cannot be *composed* — irrelevant if you want one self-maintaining set rather than a hierarchy. This one exploits your specific substrate rather than ignoring it — but only after §3.2, because it needs binding that survives depth.---
Mechanically, yes, with six real wires. Counted across all 232 .rs files:
env::var reads (~128 KAI_* vars, all read once at boot into AtomicBools that are never stored again outside tests).The codebase already knows. core/engine.rs:178-383 classifies every module, and
core/mind_frame.rs:34 defines the vocabulary: Active / Observed / Decorative / Pruned.
24 modules are marked Observed with the literal reason *"exists in the brain stack but has
no direct MindFrame authority in this pass"*; 15 Pruned; 5 Decorative.
The seven real couplings, for the record:
norepinephrine.attention_threshold() | global_workspace.salience_floor | fully live — the best one in the system |dopamine.engagement_multiplier() | rehearsal LTP gain (synapse.rs:530) | fully live (oracle path) |dopamine × predictor.avg_error | neuroplasticity.learning_rate | live |drive.valence / avg_chi | EMOTIONAL_TEMP softmax temperature | live (TUI path) |raphe.tonic_5ht ⟷ habenula.activity | each other's setpoints | live — the only genuine feedback pair |predictor.avg_error | synapse.surprise_level | live but flag-gated off by default |amygdala.arousal() | thalamus.gate_gain | written every tick, read by nobody |1. core/calibration.rs:64 feedback() has zero non-test callers. The read side *is*
live (engine.rs:1226, :1287), so every region's confidence_offset is permanently
0.0. Your whitepaper's "Epistemic Immune System Component 1" negative-feedback loop is
fully open.
2. drive/mod.rs:219 adaptive_interval_ms() computes a 2000–12000 ms heartbeat from
engagement and confusion, and is used at exactly four sites — **all four are format!
strings for the status line.** The real heartbeat is main.rs:11389,
Duration::from_millis(5000), a literal. The adaptive heartbeat is a printed number.
3. self_state_hub.workspace_salience_floor() — its eight input fields are only ever
written from bridge/ipc_server.rs, a different process path. On the main path the floor
is 0.27 forever, from constructor defaults.
Exactly one variable in the entire system has a viability range that some operation
defends: Universe::calibration_floor (universe.rs:436). Range [0.40, 0.65]. Violation
signal = contradictions found by the Three-Angle Protocol; at >5 contradictions the floor
rises 0.05 and the acceptance gate tightens; quiet ingest walks it back down 0.01 at a time.
That is a genuine Ashby-style ultrastable variable — and **its writer and its reader are the
same function.** Nothing outside ingest_and_verify participates.
Everything else — phi_g, chi, valence, cognitive_load, memory_pressure, all the
*_THRESHOLD constants — is a readout. MAX_TOTAL_SYNAPSES is the clearest case: on hitting
10M the system returns RefusedTotal and keeps going. No pruning is triggered, no rate
is lowered. It just stops learning and doesn't mention it.
---
1. Fix the substrate granularity. Nothing else on this list can work on a graph whose
nodes are paragraphs. You need cells that are words and concepts, and edges that carry a
relation type. Everything in §4 assumes a labeled concept graph; §1 says you don't have
one. *This is the missing thing, and it is upstream of all the rest.*
2. Instrument temperature (read-only). An afternoon. Log it, don't feed it back. If the
curve is flat, that's your confirmation. If it moves, §4.1 tells you exactly how to close
the loop.
3. Close the three free loops — calibration.feedback(), adaptive_interval_ms(), the
hub feed. All three are one-liners into consumers that are already live and currently
reading constants. Lowest risk on this entire document.
4. Swap in a sparse-block-code binding channel (§3.2). Lossless at depth, 32× cheaper.
Then §3.4's algebra becomes usable for 4-slot thoughts.
5. Make failure an event (§4.2). Right now KAI cannot notice that it is stuck, and a
system that can't notice can't construct its way out.
The temperature loop did not beat greedy on my toy — I said so above. RAF found nothing
useful in your graph, but that is a finding about the graph, not a refutation of RAF; I
can't tell you what it does on a proper concept graph because you don't have one yet. And
"generate fluent language from VSA algebra alone" remains genuinely unpublished — I'm not
treating that as a stop sign per your note, but I'm also not going to tell you someone has
done it.
---
Sandbox scripts: e1_binding.py (§3), e5_temperature.py (§4.1), e6_raf.py (§2).
Graph decode reads [internal module] per the format at
[internal module]:372-448 — 4-byte label count, then length-prefixed UTF-8
labels, then a 4-byte synapse count, then 20-byte records `(p:u32, q:u32, w:f32, tick:u32,
fire_count:u32)`, the whole thing zstd-compressed. Nothing in your tree was modified.