Status: ACTIVE — promoted from deferred 2026-05-09 after E4 validation surfaced the LLM-band-aid drift (60-70% of recent engineering effort spent on LLM-mitigation scaffolding, ~845 LOC of band-aid code, growing). The MVP for negative-instruction tool-failure hints failed validation (n=6 per arm, no benefit observed; default flipped OFF), making the architectural pivot urgent. Begins: Phase -1 + Phase 0 harness in 1.0 (parallel to docs work). Full Phase 0 validation, Phase 1, and Phase 2 in 1.1+. Substrate-primary AUT mode (the parallel-architecture work) interleaves with the language phases. Companion plans: persona_convergence_crucible.md (Roy methodology — same long-horizon shape), behavioral_convergence_practice.md, memory_consolidation_practice.md, v1_refinement.md (Phase 0 harness scope add) Operating context: Roy long-horizon simulations (sim-years of subjective experience) with persistent substrate across sessions, plus deliberately text-heavy curricular sims (mom-reading, teacher-student, dialogue). The substrate-primary AUT runs in parallel mode — the existing LLM-AUT path remains available so users can continue running D&D campaigns and other long-horizon LLM-driven sims while this work matures.
Added 2026-05-27 per CLAUDE.md Principle 3. Analyzed per-phase because the plan spans a multi-year research program with several distinct mechanism proposals.
Question per phase: does this need to be its own mechanism, or can it ride on existing infrastructure?
Phase -1 (substrate action-generation prototype — SHIPPED): could-ride-on-existing. NAc.recommend_action() is a new method on the existing NAc class consuming existing reward-bias + causal-link state. propose_via_substrate() is a new function in the existing agent_loop, producing an LLMProposal (existing frozen dataclass — cluster_id: str | None field added at end per CC3 path-(b)). No new bus, bridge, builder, registry. Specific reason: the bio-readiness audit (2026-05-09) named "no decision_engine.decide() that skips the LLM" as the gap; the fix exposes existing NAc state in a new shape, not a new mechanism.
Phase 0 (pre-linguistic cradle harness — SHIPPED): could-ride-on-existing. New cradle_prelinguistic arc rides on existing arcs.py BUILTIN_ARCS dispatch. Motor-only AUT prompt renderer is a new prompt module (prompts/motor_only_aut.py) — but rides on existing PromptBuilder pattern. SubstrateTelemetry JSONL writer rides on existing sim_log pattern. The new EC sensor-encoding entry point (SensorEncoder) is a new encoder class — but parallels existing LinguisticEncoder shape and routes through existing EC.pattern_complete_or_separate.
Phase 1 (vocabulary-constrained language — 1.1+): yes-needs-own (small). Token-to-EC-node binding registry has no current home — neither EC nor ATL key by token_id. Specific reason: existing identity schemes (tool-name in NAc reward biases; cluster-UUID in _cluster_reward_bias) don't survive cross-modality (per feedback_two_identity_schemes.md). The new registry can either ride on cross_modal_substrate_binding.md's edges (if Roy-4 PASS) or build a local co-occurrence learner.
Phase 2 (symbol binding layer — 1.1+): yes-needs-own per jepa_cross_modal_alignment.md — see that plan's front-gate analysis. Roy-4 FAIL forced the architecture into JEPA territory; existing infrastructure cannot do cross-modal alignment across 384-dim sensor vs 768-dim linguistic spaces.
Phase 3 (from-scratch sequence model — research direction): yes-needs-own. No existing sequence model in Maxim; this phase explicitly is the new mechanism. Specific reason: "language is I/O from substrate" thesis cannot be earned without showing language emerges from substrate — and that requires removing the pretrained LLM at the action-selection layer.
B5 Hivemind shareability infrastructure (referenced cross-plan in maxim_hivemind.md): analyzed there.
Verdict aggregate: Phases -1 and 0 ride on existing infrastructure (additive methods + parallel encoder + telemetry writer). Phases 1, 2, 3 introduce genuinely new mechanisms — and each is structurally required because the existing identity-and-encoding architecture cannot support cross-modal binding.
The work in this plan ships as a parallel AUT mode alongside the existing LLM-driven AUT. Users continue to get the LLM-AUT path by default; the substrate-primary mode is opt-in (e.g., --aut-mode substrate-primary) until convergence proves out.
Where LLMs continue to live (and where prompt engineering continues):
- Orchestrator (sim driver / DM) — LLM-driven; prompt-shaped scenario generation
- Environment NPCs — LLM-driven characters in scenes
- LLM-AUT mode (default) — the existing path, kept fully maintained
- Imagination designer — LLM-driven entity design at scene boundaries
What substrate-primary mode replaces:
- The AUT's action-selection LLM call only. NAc/reflexes/DN/imagination compose the action choice; no instruction-following layer between percept and motor.
Why parallel and not replacement: the existing LLM-AUT path delivers value today (D&D, Reachy demos, the published 1.0 thesis). Tearing it out would be reckless; building substrate-primary alongside lets us A/B at every stage and makes the kill criterion in the next section enforceable.
A substrate-primary AUT that cannot survive a D&D-style campaign orchestrated by an LLM-DM is a failed bio-substrate. AND a simulation environment that cannot be navigated by a learning substrate is a failed simulation environment. The convergence test is mutually load-bearing:
| Outcome | Diagnosis |
|---|---|
| Substrate AUT runs the campaign cleanly | Substrate is real. Project thesis validated. |
| Substrate AUT fails; LLM-AUT succeeds in same scenario | Substrate insufficient for non-trivial cognition. Reframe required. |
| LLM-AUT also fails the same scenario | Simulation environment is the failure — the test isn't measuring what we think it is. |
| Both succeed but substrate is much weaker | Acceptable interim. Substrate scope clear; LLM remains in user-facing path. |
D&D is the right test because it has: long-horizon temporal structure, novel entities every session, decision-making with delayed reward, role-coherence demands, multi-agent dynamics. If the substrate works only in the cradle and breaks in D&D, we shipped a sensorimotor toy. The cradle is necessary; D&D is sufficient.
A staged research program to remove the "pretrained LLM cheats" objection from Maxim's core thesis ("bio-systems carry the cognition; language is I/O"). The endpoint of the program — if it gets there — is a Maxim agent whose linguistic competence is acquired entirely through embodied simulation rather than imported from a pretrained model. The earlier stages are cheaper experiments that test the thesis at lower cost and gate the expensive ones.
This is not "build microGPT and wire it to NAc." That framing skips two cheaper experiments that would tell us whether the headline experiment is even worth running. It also misreads the role of the language model in current Maxim — a from-scratch sequence model trained on sim-scale reward signals will fail not because of data scale (Roy-scale curriculum addresses that) but because the objective is wrong: you can't bootstrap language from reward alone, and you can't bootstrap grounding from autoregressive prediction alone.
Each phase ships a finding about the substrate, not a deliverable that depends on later phases working. If Phase 0 reveals that bio-systems can't form proto-concepts without linguistic supervision, the whole program stops and we've still learned something load-bearing about the substrate. If Phase 2 ships a working symbol-binding layer but Phase 3's from-scratch sequence model never converges, Phase 2 is still a real result.
This framing is mandatory because the program spans years and we cannot tie its value to the most ambitious phase clearing.
The user's first instinct — "wire microGPT from scratch and let NAc supervise it" — has three problems that need to be named so the plan can avoid them:
- Reward-only training of a randomly-initialized transformer doesn't work. This is RLHF without the SFT base. Known to collapse. The pretrained LLM in current Maxim isn't optional decoration; it carries the prior over plausible language that lets reward-shaping tune behavior rather than discover language.
- Autoregressive prediction ≠ grounding. A from-scratch GPT trained on sim transcripts will memorize sequences. It won't bind tokens to bio-substrate concepts unless you add a grounding objective alongside the prediction loss. That objective is the actually-novel piece, not the transformer.
- The thesis ("substrate carries cognition") doesn't require removing the LLM. It requires showing that the substrate does the work. You can show that with the LLM constrained or absent in specific channels — Phase 0 and Phase 1 below — without rebuilding the language model.
So: do the cheap thesis-tests first, then earn the right to the expensive build.
Phase -1 — Substrate action-generation prototype (~1 week, gate before all other phases) — GATE CLEARED (2026-05-09)
Question (the most important one in this plan): Can the substrate generate even a single non-reflex action without LLM proposal?
Result: YES. Shipped via PR #228 (commits 75d1112 + b02a070). NAc.recommend_action() produces an action proposal from causal-link confidence + reward bias + drive-relevance heuristic; propose_via_substrate() in agent_loop.py wraps that into an LLMProposal for the executor. 11 unit tests in tests/unit/test_nac_recommend_action.py + 11 integration tests in tests/integration/test_substrate_primary_aut.py. Phase 0 unblocked.
Why this comes before Phase 0: the bio-substrate readiness audit (2026-05-09) scored 2/10 for action selection. NAc predicts and biases but does not propose. DN does autonomous gaze but no tool invocation. Reflexes cover only 2 thermal cases. There is no decision_engine.decide() that does not go through an LLM proposal today. Phase 0 strips language from the AUT prompt but leaves the LLM as motor-primitive selector — it does NOT prove the substrate can act on its own. This phase does.
Setup:
- Single-tick prototype, not a full sim. AUT given one entity (food) and one goal-relevant drive (hunger > 0.5).
- LLM call PATH disabled entirely on the AUT side (orchestrator can stay LLM).
- Substrate-only action generation:
NAc.recommend_action(state) → (tool_name, params, confidence)— new method, prototype implementation. May initially be "the most-positive-bias tool from active EC nodes,"argmaxover reward_bias. - Action dispatched if confidence > threshold; otherwise no-op.
Success criterion: the AUT calls pick_up_food (or any goal-relevant tool) on at least one tick across 20 trials without an LLM proposal. Just one. Proves the substrate can produce an action.
Failure criterion: zero non-reflex actions across 20 trials. Means NAc cannot serve as action-proposal source even in the simplest case. The substrate-primary architecture is not feasible as currently structured; the program redirects to building a minimum action-generation layer (new bio-system or significant NAc extension) before Phase 0 is worth running.
Implementation surface:
- New method
NAc.recommend_action(state, available_tools) → ActionProposal | None(~50 LOC, prototype quality) - New
--aut-mode substrate-primaryCLI flag (~20 LOC plumbing) - Single-tick test harness in
tests/integration/test_substrate_action_generation.py(~80 LOC) - No new bio-systems; just expose existing NAc data in the right shape.
Calibration: this phase is a Boolean. It either produces an action or doesn't. No nuance, no curves, no instrumentation overhead. If it works, Phase 0 proceeds. If it doesn't, the plan stops and refactors.
Phase 0 — Pre-linguistic cradle baseline (~2 weeks, ships finding regardless) — HARNESS SHIPPED (2026-05-09); validation pending
Status: harness shipped via PR #228 (commits b02a070 + 78d9683); validation blocked on Roy harness + EC sensor-encoding gap.
Harness deliverables (shipped):
cradle_prelinguisticarc in simulation/arcs.py — same developmental scaffolding ascradle, all English instructions stripped.- Motor-only AUT prompt renderer in prompts/motor_only_aut.py — numeric drives + sensors + bare tool names; English-leak sentinels in tests.
- Per-tick
SubstrateTelemetryJSONL writer in simulation/substrate_telemetry.py — captures EC node count, NAc reward bias, drive states, active proposal. Fail-soft (never crashes the loop). --researchwith--aut-mode substrate-primaryenables telemetry; routes throughstart_simulation_mode(not the multi-agent paper harness).- 13 harness tests in tests/integration/test_phase0_harness.py.
- Smoke run cleared: 38 actions, 61 causal links, hunger drift 0.0 → 0.65 over 5 turns. See docs/experiments/13_phase0_harness_smoke.md.
Validation gaps (pending):
-
EC sensor-encoding entry point— CLOSED 2026-05-09 (second commit, branchfeat/phase0-sensor-encoding):SensorEncoderin similarity/encoder.py hashes drive snapshots into a 384-dim embedding via low/high basis interpolation and routes through ECpattern_complete_or_separatewith modality"interoception". Wired into agent_loop.py::propose_via_substrate — fires once per substrate-primary tick before reading drives. Smoke re-run produced ECnode_count0 → 1 with modality"interoception": 1. Surprising finding (smooth drift collapses to one cluster — EC centroid tracking + shared bases for unchanged sensors) documented in 13_phase0_harness_smoke.md; Phase 0+ refinement targets enumerated there. The bare measurement gap (anything > 0) is closed; cluster-purity tuning waits on Roy. -
G4: Substrate-primary reward-feedback wire— CLOSED 2026-05-11. Track 2 deferred the cluster-keyed reward-update wire; Roy-0 caught it; this section originally documented it as an architectural gap, then shipped the wire in the same session. Summary of what landed:LLMProposal.cluster_id: str | None = None(CC3-compatible optional field at end of frozen dataclass) — src/maxim/agents/llm_types.py.propose_via_substratestashes the active EC cluster on the returned proposal — src/maxim/runtime/agent_loop.py.record_outcome(..., cluster_id=...)callsNAc.update_cluster_reward(agent_id, cluster_id, sig, ±1.0)when set; no-op for thecluster_id=NoneLLM-primary path — src/maxim/runtime/tool_dispatch.py.- All 6
_record_outcomecall sites in agent_loop.py plus theexecute_parallel_actionspath now threadcluster_idfrom the in-scope proposal (ctrl.pending_proposal.cluster_idor localproposal.cluster_id). NAc.dump()/load_stateserialize_cluster_reward_biasunder a newcluster_reward_biaskey (keys join(agent, cluster, tool_sig)with\x1fsotool:use:dodgeround-trips cleanly). Backward-compatible: pre-G4 snapshots load with an empty cluster dict.substrate_diff.NacDiffcarriescluster_reward_bias_{available,l2,top_deltas};substrate_diff_to_jsonsurfaces the field inresult.jsonundernac.cluster_reward_biasso Roy iterations can read the metric without re-running the diff.- 6 regression tests in tests/integration/test_substrate_primary_aut.py::TestG4ClusterRewardWire cover: proposal envelope stashes cluster_id; record_outcome populates
_cluster_reward_biason success/failure; cluster_id=None is a no-op; persistence roundtrip preserves the dict; pre-G4 snapshot loads cleanly; substrate_diff surfaces non-zero L2 when arms differ. All 94 tests in the cluster surface pass; full fast suite green (6484 passed; one pre-existing flaketest_context_index.py::test_similar_text_foundunrelated).
Empirical confirmation (2026-05-11 Roy-0 re-measurement):
cluster_reward_bias_l2 = 2.4587on both A-vs-blank pairs (vsn/apre-G4 — the field didn't exist inaut_nac.json).cluster_reward_bias.available = trueon every pair (vsfalsepre-G4).- Top deltas: 6×
tool:sense_food_sourceat the+1.0per-key cap (six distinct EC cluster ids accumulated during arm A's 50-turn priming) + 2×tool:infant_humanoid_pick_upat ±0.15 (arm A hit a failure case blank arms didn't). b_vs_ccluster_reward_bias_l2 = 0.21 (stochastic-cluster noise floor for blank-vs-blank under this fixture); A-vs-blank ratio ≈ 11.6×.- Full numbers + pre/post comparison: docs/experiments/15_g4_cluster_reward_wire.md. Roy-0 iteration log carries the same table.
What's NOT in this closure (intentional):
- Substrate-primary score-threshold tuning.
min_confidence=0.3may need to be path-specific (lower for substrate-primary's cold-start regime) — but that's its own measurement question, not the deferred wire. The 2026-05-11 re-run produced 6 cluster updates all on the same tool (sense_food_source), suggesting the cold-start path collapses to one drive-affinity match and loops on it. Roy-1 with a diverse holdout fixture is the next test. reward_bias_l2(the per-ATL-node recognition bias fromcredit_nodevia reaction-drivendistribute_reward) stays 0 — that's a different code path G4 doesn't touch. Pre-G4 readers conflated it with cluster bias because cluster_reward_bias wasn't serialised; the re-measurement makes the distinction empirically visible.
Original architectural-gap analysis (preserved for context — the wire that closed is the one this describes): Roy-0 ran 15 min end-to-end against a healthy leader with
aut_mode=substrate-primaryand produced zero action proposals (proposal=none× hundreds of loop ticks). Three parallel investigations (static gate trace + commit forensics + persisted-state inspection) converge on the same root cause: the cluster-keyed reward update wire was explicitly deferred when cluster-keyed action selection shipped, so NAc has nothing learned to recommend from.What's wired (substrate-primary commit series):
78d9683— Phase 0 harness:propose_via_substrate()called from agent_loop.py:2654-2670 whenaut_mode == "substrate-primary".e293eb7—SensorEncoder.encode_sensors()fires once per tick beforerecommend_action; drives hashed into 384-dim embedding, routed throughEC.pattern_complete_or_separatewith modality"interoception".fad326f—frozen_centroid_modalitiesconfig in similarity/ec.py prevents interoception centroid from drifting.6d0e4a7—NAc._cluster_reward_biasdict +update_cluster_reward()API +recommend_action(current_cluster_id=...)parameter; agent_loop.py::propose_via_substrate capturescluster_idfromencode_sensors()and threads it intorecommend_action.6643755— orchestrator narration silenced whenaut_mode == "substrate-primary"(simulation/bridge.py skipsinject_cliandpercept_anxiety_hook).
What's deferred (the G4 wire): the
6d0e4a7commit message states explicitly: "Reward update wiring (cluster_id-awarerecord_outcome) is deliberately out of Track 2's scope — the API exists and is unit-covered, but the agent_loop's_record_outcomechain doesn't yet plumb cluster_id."cluster_idis captured at proposal time but never travels to the post-execute outcome record, soNAc._cluster_reward_biasstays empty AND the per-toolreward_biasstays empty across every session.Empirical confirmation from Roy-0 (
~/.maxim/sim_reports/20260510_213337/priming final +20260510_213527/arm A test):aut_nac.json::reward_bias— 0 keys in both priming-final and arm A.goal_reward_biascarries only the two test probes (cradle_prelinguistic: 0.196+ an arm-specific key).aut_nac.json::links— 133 causal-link targets accumulated, but theoutcome_signaturefields encode outcome descriptions ("tool:sense_food_source:positive","plan:drive:hunger(0.50) → food:success") rather than affordance-name → reward valuations the proposer could query.aut_hippocampus.json::episodes— 662 episodes captured but mostlyvalence = -0.25(passive observation), notext/percept_refs, noconcept_indexentries.ec.json— not present in any session dir (EC writes go through the liveECinstance during the run but aren't persisted under theaut_*prefix the way NAc/Hippocampus are; orthogonal to G4 but worth tracking — visible to the proposer in-process, invisible to substrate_diff across sessions).~/.maxim/roy/roy-0-smoke/result.json::pairwise_diffs—reward_bias_l2: 0.0andreward_bias_top_deltas: []on every pair, confirming reward_bias never populated in any of the three arms.
Where the silent gate fires: decisions/nac.py::recommend_action at lines 1295 (
if not scores: return None) and 1300 (if best_score < min_confidence: return None, defaultmin_confidence=0.3). With emptyreward_bias+ empty_cluster_reward_bias+ no causal-link confidence to score against + cold-start drives, every available tool scores 0.0.recommend_actionreturns None. agent_loop.py:742 sees None and returns None. The substrate-primary branch at agent_loop.py:2664 leavesctrl.pending_proposalunset. Next tick: same gate fires the same way. No log line surfaces — the only positive log ("substrate-primary proposal: tool=...") fires inside theif substrate_proposal is not None:branch.What closing it costs (concrete change surface): plumb
cluster_idfrompropose_via_substrate's capture site through to every_record_outcomecall in agent_loop.py, then have those sites invokeNAc.update_cluster_reward(agent_id, cluster_id, tool_signature, reward)alongside the existing reward-update calls. Per therecord_outcome_call_sitesfeedback memo, there are ~7_record_outcomesites in agent_loop.py (grepnac=_loop_nac). The cluster_id needs to ride on the pending-proposal envelope (or be re-derived from sensor state at outcome time — re-derivation is the cheaper option since sensors are read every tick anyway, but it requires the outcome callback to know which tick's cluster matters and that may not be stable across tool-execution latency). Estimate: 1-2 days for the threading work + invariant tests, then a fresh Roy-0 run to confirmreward_bias_l2 > 0on the A-vs-blank pairs.What does NOT close G4 alone: populating
reward_biasis necessary but not sufficient for the proposer to fire. Themin_confidence=0.3gate means a few small updates won't cross threshold; the cold-start regime needs either (a) lowered threshold for the substrate-primary path specifically, (b) drive-affinity scoring that's non-zero from the first tick when a relevant drive crosses the 0.5 activation cutoff, or (c) cluster-bias accumulation tuned to dominate at small N. Track 2's design contemplates (c) —update_cluster_rewardis meant to give cluster bias the loudest voice in selection — but only if there are any cluster reward observations in the bank, which loops back to the same threading gap. Roy-1 should not run until a sub-Roy-0 (10-turn synthetic) showsreward_bias_l2 > 0after wire close.Why this belongs in Phase 0 not Roy: the wire and its closure are properties of the substrate-primary architecture, not the persona-convergence harness. Roy-0 only surfaced the gap because it was the first long-horizon run with substrate-primary that captured persisted diffs across arms. Until G4 closes, every Roy iteration's "active substrate" measurements (reward_bias L2, valence KS, episode valence distribution) read 0 regardless of priming — the harness works, but the substrate has nothing active to compare.
Cross-references: Roy-0 iteration log entry in persona_convergence_crucible.md § Iteration log, commit
6d0e4a7message, tests/integration/test_substrate_primary_aut.py (5 unit-coverage tests forupdate_cluster_rewardexist already; the wire is what's missing). -
Roy long-horizon harness — Phase 0 validation needs sim-years of subjective experience across persistent substrate sessions. Designed but unbuilt; tracked as the gating dependency for Phase 0/1 validation, persona convergence, and D&D survival testing.
-
Pre-linguistic orchestrator narration — the LLM-DM still emits English narration percepts the substrate-primary AUT ignores. Phase 0's "no English" goal is met on the AUT side but not the orchestrator side; eventually the orchestrator should produce sensor writes only (or be silenced entirely).
Question: Can EC/ATL/NAc form coherent, persistent concepts with zero linguistic supervision?
Setup: Strip English narration entirely from cradle acts 1-2 in data/components/items/cradle*.yaml and the matching narrative phases in simulation/arcs.py::BUILTIN_ARCS["cradle"]. The infant body still has drives (hunger, thermal, contact) and proprioceptive sensors via _data/components/bodies/infant_humanoid.yaml, and reflexes still fire from _data/reflexes/infant.yaml. What's removed: every scripted line of mother-narration, every English-named affordance description in the prompt, every word in phase.summary. The LLM is still present (the agent has to act), but it receives only sensorimotor channel data and produces only motor primitives — no English in either direction.
Measurement:
- EC node count over time: do clusters form?
- Cluster purity against held-out test stimuli (does "warm contact with caregiver" reliably activate the same EC region across sessions?)
- NAc reward_bias distribution: are biases forming around drive-resolution events?
- Cross-session persistence: load the substrate into a fresh sim — do the proto-concepts re-activate on matching sensory patterns?
Implementation surface:
- New cradle arc variant:
cradle_prelinguisticin simulation/arcs.py. - Motor-only AUT prompt template — strips the Acting Coach's linguistic framing, presents drive/sensor state and available motor primitives only.
- Telemetry: per-tick EC activation snapshot, NAc reward_bias snapshot, sensor reading snapshot. Existing JSONL writers extend.
- No new bio-systems. No new buses. This is a sim-config and prompt-shaping experiment.
Gate / kill criterion:
- Pass: measurable EC cluster formation tied to repeating sensorimotor patterns; NAc reward_bias differentiates drive-resolution events from null events; cross-session re-activation > chance.
- Fail: no detectable substrate structure after 50+ sessions of Roy-scale exposure. Stops the program. If bio-systems can't form concepts without linguistic supervision, the thesis is wrong and Phases 1-4 have nothing to ground in.
- Mixed: clusters form but don't persist, or persist but don't generalize. Diagnostic, not fatal — feeds memory_consolidation_practice.md.
What this DOESN'T test: language. Phase 0 is intentionally pre-linguistic. Whether words can later bind to these clusters is Phase 2's question.
Question: When the LLM can only produce words that bind to substrate concepts, does behavior stay coherent or collapse?
This is the cheapest possible test of "the substrate carries the cognition" without rebuilding anything. Keep the pretrained LLM as input parser. At output time, mask logits so the model can only produce tokens that have an active binding to an EC node (Phase 0's clusters, plus any substrate concepts formed during the agent's prior life). Words for things the substrate has never encoded — including most of the LLM's pretrained world knowledge — are unavailable.
Setup:
- Symbol-binding registry: a persisted map
token_id → ec_node_idpopulated from co-occurrence during normal language-on simulations. Until the registry is populated, the agent operates mute (motor-only). - Output-time logit mask in the LLM call path. Gates words by binding strength.
- Test scenarios: a held-out task suite where success depends on the agent describing things it has and hasn't experienced. The gap between "describe an object you've handled" (substrate-bound, vocabulary available) and "describe an object from training data only" (substrate-unbound, vocabulary masked) is the load-bearing measurement.
Measurement:
- Behavioral degradation curve: how does task success scale with binding-registry size?
- "Knowledge leakage" detection: does the agent attempt to use pretrained-world-knowledge words at frequencies above its substrate exposure? Constraint failures are findings.
- Comparison against unconstrained baseline at matched substrate maturity.
Implementation surface:
- models/language/maxim_peer_backend.py gains an optional
logit_mask_providerparameter. The peer-backend invariants (one HTTP call, typed failure mapping) stay intact — masking is local pre/post-processing, not an extra request. - decisions/nac.py or similarity/ec.py exposes a
bound_tokens()accessor. - Co-occurrence learner runs as a passive observer subscribed to the existing percept/action buses; no new orchestrator. Coordination note (2026-05-13): cross_modal_substrate_binding.md ships the substrate-level binding mechanism (EC nodes binding to EC nodes via Hebbian co-activation) in 1.1. Phase 1's
token_id → ec_node_idsymbol-binding registry is a token-granularity special case of those edges. If the binding plan ships first or in parallel, Phase 1's co-occurrence learner becomes a thin lookup overEC._binding_edgesrather than duplicate machinery. Phase 1 does not depend on the binding plan — fallback to its own co-occurrence learner if the binding plan slips — but coordinating the two reduces duplicate work.
Gate / kill criterion:
- Pass: behavior degrades gracefully with vocabulary size; substrate-bound vocabulary is sufficient for in-distribution task success; out-of-distribution tasks fail in the predicted way.
- Fail: behavior collapses even when the substrate has rich bindings, OR behavior survives masking trivially because the LLM routes around it. Both findings adjust Phase 2's design but don't kill the program — they tell us where the LLM is doing more than I/O.
What this DOESN'T test: producing language from scratch. The LLM is still doing all the sequence generation; we're just gating its vocabulary. Phase 2 builds the actual binding mechanism Phase 1 here borrows.
Question: Can a small, online-trained associative model bind words to bio-substrate concepts well enough to substitute for the LLM's vocabulary on bound concepts?
Architecture (deliberately not a transformer):
- Inputs: word-token sequences (subword level, but with a new tokenizer trained from the simulation corpus — not the pretrained LLM's tokenizer, which leaks subword priors).
- Outputs: distributions over EC node IDs.
- Training signal: co-occurrence in simulations where percept ground truth is known (mom-reading sims emit
(token_sequence, scene_entity_id, ec_node_id)tuples; teacher-student sims emit explicit naming events). - Model: simple — embedding lookup + small MLP, or a tiny RNN. Not a transformer. The point is to see what the smallest model that can bind looks like, then scale up only if it fails.
Why not a transformer here: because the question isn't "can a sequence model learn language" — it's "can the substrate provide enough grounding signal that any reasonable binding architecture works." If the simplest model works, the result is stronger. If it fails and a transformer succeeds, that's also informative — tells us the binding requires within-sequence context, not just word-to-concept mapping.
Persistence: weights live in ~/.maxim/util/grounded_language/ per-agent, persist across sessions, version-tagged via the standard _format_version contract per CLAUDE.md v1.0 freeze. Online updates batched per-session-end alongside the Hippocampus consolidation pass. Catastrophic forgetting is the obvious risk — mitigation is replay-based: hippocampal episodes carrying linguistic content get sampled into the binding-layer's training batch during sleep replay, mirroring biological consolidation.
Curriculum integration:
- Mom-reading sims become the primary high-density linguistic input channel. New sim type: scripted narrator-driven sims where the LLM-mom emits text deterministically tied to known scene state. Token-to-EC ground truth is automatic.
- Teacher-student sims provide explicit naming events: "this is a sword. say 'sword.'" Used for direct supervision in early phases, weaning to indirect co-occurrence as the binding registry matures.
- Standard generative sims feed the binding layer passively — no curriculum metadata, just whatever co-occurrence the AUT produces.
Measurement:
- Binding accuracy on held-out token-to-concept pairs.
- Generalization: does "flame" map to the EC cluster for "fire" (re-using the affordance concept transfer machinery from affordance_concept_transfer.md)?
- Phase 1 re-run with binding registry populated by Phase 2 (instead of co-occurrence): does Phase 1 behavior improve?
Gate / kill criterion:
- Pass: binding accuracy on held-out pairs > 70% after Roy-scale curriculum; generalization to substrate-equivalent concepts works; replay-based consolidation prevents catastrophic forgetting across sessions.
- Fail: binding doesn't stabilize, OR stabilizes but doesn't generalize, OR catastrophic forgetting overwhelms replay. Each failure mode informs whether Phase 3 is worth attempting and which of its objectives need to do more work. The program continues to Phase 3 only if Phase 2 ships a usable binding registry.
Question: Can a sequence model trained from scratch on the Roy curriculum, with substrate-grounding as a co-equal training objective alongside next-token prediction, replace the pretrained LLM?
Why this is now plausible (vs my initial pushback in conversation): Roy long-horizon sims plus text-heavy curriculum (mom-reading, teacher-student) plus persistent substrate accumulation across sessions can plausibly approach infant-scale linguistic exposure. ~5K words/turn × 100 turns/sim × 100 sims = 50M words in the same order as a 4-year-old's exposure. The data scale objection from the conversation thread was wrong on that axis. The objective and grounding objections still stand and are addressed below.
Architecture choices to settle in this phase:
- Transformer vs RNN vs state-space. Default to small transformer (~10-100M params) because tooling is best, but the choice is not load-bearing for the thesis — it's load-bearing for whether anything trains at all in our constraints. Honest pick: smallest model that converges on the curriculum.
- Tokenizer trained from the simulation corpus (continuing Phase 2's choice). No pretrained tokenizers — they leak.
- Two-objective loss: standard autoregressive next-token loss + a substrate-grounding loss (predict the active EC node ID given the word context, or given the produced token, predict which EC concept it references). The grounding loss is the load-bearing novel piece. Phase 2's binding registry seeds the grounding labels.
Reward shaping (NAc):
- NAc reward signals enter as a third objective with low weight, not as the primary loss. This is the actually-correct framing of "wire NAc to the language model": it tunes a model that already has language structure from the next-token objective, rather than trying to discover language from reward alone.
- Reward enters at the utterance level (was this utterance followed by drive-resolution? did it lead to a goal-related outcome?), not the token level. Token-level reward shaping is known to produce reward-hacking in language models.
Curriculum scheduling:
- Phase 3a: from-scratch model trained on the Roy curriculum corpus passively, no NAc, no grounding loss. Pure next-token. This is a sanity baseline — does a small transformer trained on 50M words of Maxim curriculum produce coherent text at all?
- Phase 3b: add grounding loss. Measure whether tokens now bind to EC concepts beyond co-occurrence statistics.
- Phase 3c: add NAc reward signal. Measure whether utterance-level behavior shifts toward NAc-rewarded outcomes.
Persistence: weights persist across sessions per-agent (same path convention as Phase 2). Per-session checkpoints to allow rollback if a curriculum batch destabilizes the model. Replay-based consolidation continues to be load-bearing.
Gate / kill criterion:
- Pass-3a: model produces coherent in-distribution text after Roy-scale curriculum. Coherence is a low bar here — "passes a basic syntactic-fluency check" is enough.
- Pass-3b: grounding loss measurably aligns model token outputs with substrate concepts; held-out grounding accuracy > Phase 2's binding-registry baseline.
- Pass-3c: NAc reward shaping changes utterance distribution in the predicted direction without collapsing fluency.
- Fail at any sub-gate: ships a finding about which objective is doing what. Stops Phase 4 but doesn't unshipphases 0-2.
Question: Does the Maxim agent retain its substrate behaviors when the pretrained LLM is fully replaced by the Phase 3 model? Are claims about the substrate's role corroborated or refuted?
Methodology: mirror the persona-convergence three-arm comparison (persona_convergence_crucible.md Methodology section).
| Arm | Language model | Substrate |
|---|---|---|
| A | Pretrained 14B (current) | Roy-trained |
| B | Phase 3 from-scratch | Roy-trained |
| C | Pretrained 14B | Blank (control) |
The interesting comparisons:
- A vs B: same substrate, different language model. Does behavior persist? If yes, strong evidence the substrate carries the cognition. If no, the LLM was doing more than I/O all along — load-bearing finding either way.
- B vs C: from-scratch language + Roy substrate, vs pretrained language + blank substrate. If B beats C on substrate-dependent tasks, the program's thesis is corroborated.
Publishable result: B vs C with explicit task-success deltas. If A ≈ B on substrate tasks, that's the cleanest possible demonstration that current Maxim's claimed substrate-driven behavior survives the LLM swap.
Pretrained tokenizers (BPE from GPT-2/3/4, SentencePiece, etc.) carry priors. They've already decided that "anti" and "disestablishment" are subword units because of training-corpus statistics. Reusing them in Phase 2 or 3 leaks knowledge sideways and undermines the from-scratch claim. Train tokenizers from the Maxim curriculum corpus only. Start character-level in early Phase 2 prototypes if subword behavior is unclear; move to BPE only once curriculum scale justifies it.
Online learning + persistent weights = a known disaster mode. Mitigation across all phases:
- Replay-based consolidation: hippocampal episodes are already replayed during sleep (memory_consolidation_practice.md). Extend the replay path to feed the binding-layer (Phase 2) and language model (Phase 3) training batches. This is biologically motivated and operationally necessary.
- Per-session checkpoints with rollback: if a session destabilizes the model (loss spike, eval collapse), revert. The cost is one sim's worth of learning; the benefit is bounded blast radius.
- Importance-weighted updates: linguistic events tied to high-valence outcomes weight more in the consolidation batch. This piggybacks on existing valence machinery.
The Roy operator picks the curriculum. Start text-density-low (mom-reading bedtime stories, simple naming) and ramp toward text-density-high (teacher dialogues, peer conversation). The scheduler is operator-facing config in the Roy harness, not a new bio-system. Findings about which curricula are load-bearing accrue to this plan's iteration log over time.
Phase 2's small associative model: trains on a CPU during sleep replay. No new infrastructure. Phase 3's from-scratch transformer: trains on the user's RTX 5080 leader during off-sim periods or as a dedicated training session between Roy iterations. Not real-time. Roy's pacing supports this — sim-time and wall-clock-time are decoupled.
The runtime ships several mitigations for pretrained-LLM behaviors that don't exist in a from-scratch substrate. These are appropriate for current Maxim use but must be disabled for Phase 0 (pre-linguistic) and Phase 1 (vocabulary- constrained), or the experiments measure the crutch instead of the substrate.
| Crutch | What it does | Disable via |
|---|---|---|
| Tool-failure hint section | Adds a === Tools You've Hallucinated === block to the prompt listing names the agent previously called that don't exist (mitigates qwen-class training-prior fallback to respond etc.). Discovered in cradle E1 experiment 2026-05-09 — qwen2.5-14B hallucinated respond in 67% of runs even when it wasn't in the prompt. |
MAXIM_TOOL_FAILURE_HINTS=0. Field on LLMRequest.failed_tools; population gated in agent_loop.py at the submit_context call site; section emitter is build_failed_tools_section in prompt_builder.py. |
Add new crutches to this table as you discover them. The principle is the same in every case: if a mitigation exists because the pretrained LLM does something the substrate hasn't earned, the substrate-only experiments must turn it off so we measure substrate competence and not the mitigation.
The substrate's bio-natural analog of the tool-failure hint exists already —
NAc records tool:X → failure:not_registered with negative valence and
biases against re-attempting that tool. With the hint disabled, that NAc
learning becomes the only signal, which is what we want to measure in
Phase 0/1: does the substrate's negative-valence avoidance suppress
hallucinated-tool calls without an explicit prompt warning? If yes, the
crutch was unnecessary all along; if no, we've found a real substrate gap
to address (perhaps via stronger NAc weight on tool-failure outcomes).
- No new bio-systems. Every wire-up uses existing bio-systems (EC, ATL, NAc, Hippocampus) and existing buses (PainBus, ReactionBus, percept stream).
- No new buses or coordinators. The binding registry, the from-scratch model — both are passive observers / online consumers of existing event streams. Adding a new bus to coordinate them would violate the build_bio_stack invariants from bio_stack_unification.md.
- Maxim Oasis + Hivemind coupling is intentional but additive. maxim_hivemind.md (which superseded the old Mother Maxim plan on 2026-05-09) builds on this plan's substrate-primary work. The Hivemind is the peer-to-peer substrate-sharing layer; the Oasis is the persistent substrate-primary instance that distills LLM-AUT contributions and broadcasts patterns. Phase -1 and Phase 0 must run with Hivemind disabled (raw substrate, no bootstrap) so the headline experiment stays clean — the Hivemind is the end-user-convenience path, not the research path.
Phase 0: simulation/arcs.py, _data/components/bodies/infant_humanoid.yaml, new prompt template under src/maxim/prompts/, telemetry extensions to simulation/orchestrator.py.
Phase 1: models/language/maxim_peer_backend.py (logit mask hook), decisions/nac.py or similarity/ec.py (bound-tokens accessor), new module src/maxim/language/binding_registry.py.
Phase 2: new package src/maxim/language/ — binding_layer.py, tokenizer.py, consolidation.py. New persistence path ~/.maxim/util/grounded_language/. Hooks into memory/hippocampus.py replay path.
Phase 3: extends src/maxim/language/ — from_scratch_lm.py, training loop, curriculum runner. Likely a new optional extra pymaxim[grounded-language] to gate the torch dependency.
Phase 4: comparison harness alongside existing Roy machinery; no new core code.
(Append findings here as phases run. Living doc convention per persona_convergence_crucible.md and behavioral_convergence_practice.md.)
Trigger: E4 controlled validation of MAXIM_TOOL_FAILURE_HINTS (negative-instruction tool-failure hint section) — n=6 per arm — showed no benefit and possible backfire on qwen2.5-14B (treatment mean 4.67 respond-hallucinations vs control 3.33; total 37 vs 27; 2× _llm_unavailable cascades). Default flipped OFF. The MVP failure was the proximate cause; the deeper cause was a systemic audit:
Audit findings (three parallel reviews, cross-confirmed):
- LLM band-aid surface ~845 LOC across the codebase.
json_parser.pyis 32.9% repair logic for malformed LLM output;prompt_builder.pyis 22.5% mitigation scaffolding; the largest single band-aid isorchestrator.py::_stall_detectorat 219 LOC existing solely because the LLM gets stuck in observation loops. 7 distinct LLM failure-mode mitigations have dedicated code. - Recent engineering split: 60-70% LLM mitigation, 30-40% bio-substrate. The drift is real and accelerating.
- Bio-substrate readiness for action selection: 2/10. NAc predicts and biases but does not propose actions. DN does autonomous gaze but no tool invocation. Reflexes cover 2 thermal cases. There is no
decision_engine.decide()that does not go through an LLM proposal today. This is a much larger gap than "strip the LLM" implies — hence Phase -1's gating role.
Strategic decisions made:
- Plan moved out of
deferred/to active. Status changed from "post-1.0" to "Phase -1 + Phase 0 harness in 1.0; full Phase 0 + 1 + 2 in 1.1+". - Parallel-mode architecture: existing LLM-AUT path stays as the user-facing default. Substrate-primary AUT mode runs in parallel under
--aut-mode substrate-primary(or similar). LLMs continue to drive orchestrator + NPCs + imagination + LLM-AUT mode; prompt engineering work continues for those layers. Substrate-primary mode is the only path with no LLM in the AUT itself. - D&D as the bidirectional kill criterion (see top of plan). A substrate that can't survive a D&D campaign orchestrated by an LLM-DM is a failed bio-substrate. AND a simulation environment that no learning substrate can navigate is a failed simulation environment. Mutually load-bearing.
- Phase -1 added as the most important single experiment in the program — proves (or disproves) that the substrate can generate even ONE non-reflex action without LLM proposal. ~1 week of work; Boolean outcome; gates everything else.
- Roy harness precondition acknowledged unmet — designed but unbuilt. Phase 0 validation depends on Roy; Phase 0 HARNESS does not. Harness ships in 1.0 as experimental (
--researchflag); validation waits for Roy in 1.1+.
1.0 scope add: Phase -1 + Phase 0 harness (~600-700 LOC across NAc.recommend_action, --aut-mode flag, motor-only AUT prompt template, telemetry snapshots, single-tick test harness). Behind experimental flag; doesn't touch user-facing 1.0 surface; doesn't gate 1.0 docs work.
Companion change: MAXIM_TOOL_FAILURE_HINTS default flipped to OFF (was ON post-MVP). Validation showed no benefit; opt-in only for further experimentation. Documented in the "Pretrained-LLM crutches" table above.
Two-session sprint after the 2026-05-09 strategic pivot.
Phase -1 — substrate action-generation Boolean: PASS.
NAc.recommend_action(agent_id, available_tools, current_drives) -> dict | None(decisions/nac.py) scores each tool by causal-link confidence + reward bias + drive-relevance heuristic (substring + affinity table for cold-start). Returns None below threshold — silent IDLE, never random.propose_via_substrate()(runtime/agent_loop.py) wraps the recommendation as anLLMProposalwithstrategy_used="substrate-primary"and dispatches via the standard executor path.--aut-mode {llm-primary,substrate-primary}plumbed throughcli→start_simulation_mode→run_agentic_loop. In substrate-primary mode the LLM submit branch is gated off; no inference call is ever issued.- 22 tests across unit + integration. LLMRouter.dispatch tripwire confirms substrate path never touches the LLM.
Phase 0 — harness shipped:
cradle_prelinguisticarc +select_arc_for_goalexact-name resolution.- Motor-only AUT prompt renderer.
SubstrateTelemetryJSONL writer wired into the substrate-primary tick.--researchsemantic split: with--aut-mode substrate-primaryit means "telemetry on" (not the multi-agent paper harness).
Smoke run cleared the success criterion:
maxim --sim cradle_prelinguistic --embodiment bodies/infant_humanoid \
--aut-mode substrate-primary --research --interactive false \
--sim-max-turns 5
38 actions, 61 causal links, hunger drift 0.0 → 0.65, 195 telemetry rows. Substrate proposed sense_food_source on the cold-start drive-affinity heuristic (food substring matched hunger affinity).
Surprising findings (full write-up: 13_phase0_harness_smoke.md):
- Substrate-primary mode owns its own clock. First smoke run produced 0 actions because nothing was polling the embodiment — the LLM-primary path drives the embodiment via
EmbodimentPerceptSource.next_percept()→evaluate_failures(), but substrate-primary skips that. Fixed by callingevaluate_failures()insidepropose_via_substrate. Documented as a structural property: anything the LLM-primary path implicitly relied on (drive drift, percept polling) needs to be re-wired for substrate-primary. - Substrate-primary bypasses
LinguisticEncoder. ECnode_countstayed at 0 throughout the smoke run because substrate-primary doesn't feed text percepts through the encoding path. Phase 0's cluster-formation measurement is blocked on a sensor-percept encoding entry point — next work item. - Drive-affinity heuristic + scene tools. The infant body's
pick_uplost to the cradle scene'ssense_food_sourcebecause the affinity table matches "food" substrings. Same mechanism as the test suite caught withpick_up_food, just a different tool name. The heuristic is a Phase -1 placeholder; the full plan calls for replacement with EC embedding similarity once Phase 0 sensor-encoding lands. - Pre-existing AttributeError. End-of-run warning
'GenerativeCampaignResult' object has no attribute 'turns_completed'surfaces on every termination path. Pre-existing bug in the generative campaign runner; flagged for future cleanup.
1.0 implication: B5's first ~700 LOC ships as planned. Hivemind shareability (~660 LOC) remains pending. The next concrete substrate-primary work item is the EC sensor-encoding entry point (small) — that's the cheapest experiment that converts the harness into an actual Phase 0 measurement.
The user's instinct (replace pretrained LLM with from-scratch microGPT trained via NAc) was right in motivation, wrong in shape. The motivation is real: current Maxim's "substrate carries cognition" thesis has an asterisk because the LLM smuggles in conceptual priors. The shape is wrong because reward-only training of randomly-initialized transformers fails, autoregressive prediction doesn't ground, and the cheapest tests of the thesis don't require rebuilding the language model at all.
This plan addresses the motivation by sequencing four experiments of increasing cost. Phase 0 (pre-linguistic cradle) and Phase 1 (vocabulary constraint) test the thesis cheaply and ship findings regardless of later phases. Phase 2 (symbol binding) and Phase 3 (from-scratch sequence model) build the headline experiment, but only after gates clear. The Roy long-horizon context plus persistent substrate plus text-heavy curriculum makes Phase 3's data scale plausible — that part of my conversational pushback was wrong.
The hardest unsolved problem in the plan is catastrophic forgetting across sessions. Replay-based consolidation is the bet; it might not be enough. If Phase 2 fails primarily on forgetting rather than binding accuracy, the program fork is "do we invest in better consolidation or accept that this model needs offline batch training between Roys." Either fork is a real research finding worth publishing.
The program might not finish. That's fine — the framing rule says each phase justifies itself.
The Exp 44 LLM-primary divergence surfaced that the LLM-primary AUT lacks the
drive-gating/salience filter substrate-primary has (NAc drive-gating makes a
cold body select warming). Rather than patch a per-experiment introspection
filter (LLM-mitigation scaffolding this plan exists to stop), substrate→LLM
action-salience belongs here as a substrate-native primitive: let drive/NAc
state gate/weight what the language model attends to and selects. Machinery
partly exists — GatingContext.drive_states (runtime/gating.py:96) — and
substrate-primary's propose_via_substrate + INTROSPECTION_TOOL_NAMES
exclusion is the reference mechanism. Tracked from
docs/plans/controlled_llm_primary_embodied_harness.md (G2) + memory
project_exp44_llm_primary_divergence.