Skip to content

Latest commit

 

History

History
438 lines (302 loc) · 55.9 KB

File metadata and controls

438 lines (302 loc) · 55.9 KB

Grounded Language Acquisition + Substrate-Primary AUT (parallel-mode architecture)

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.

Front-gate scope pressure (retroactive)

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.

Architectural framing — parallel modes, not replacement

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.

D&D as the bidirectional kill criterion

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.

What this is

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.

Framing rule: each phase justifies itself

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.

What's wrong with the naive framing

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:

  1. 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.
  2. 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.
  3. 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," argmax over 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-primary CLI 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_prelinguistic arc in simulation/arcs.py — same developmental scaffolding as cradle, 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 SubstrateTelemetry JSONL writer in simulation/substrate_telemetry.py — captures EC node count, NAc reward bias, drive states, active proposal. Fail-soft (never crashes the loop).
  • --research with --aut-mode substrate-primary enables telemetry; routes through start_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 pointCLOSED 2026-05-09 (second commit, branch feat/phase0-sensor-encoding): SensorEncoder in similarity/encoder.py hashes drive snapshots into a 384-dim embedding via low/high basis interpolation and routes through EC pattern_complete_or_separate with modality "interoception". Wired into agent_loop.py::propose_via_substrate — fires once per substrate-primary tick before reading drives. Smoke re-run produced EC node_count 0 → 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 wireCLOSED 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_substrate stashes the active EC cluster on the returned proposal — src/maxim/runtime/agent_loop.py.
    • record_outcome(..., cluster_id=...) calls NAc.update_cluster_reward(agent_id, cluster_id, sig, ±1.0) when set; no-op for the cluster_id=None LLM-primary path — src/maxim/runtime/tool_dispatch.py.
    • All 6 _record_outcome call sites in agent_loop.py plus the execute_parallel_actions path now thread cluster_id from the in-scope proposal (ctrl.pending_proposal.cluster_id or local proposal.cluster_id).
    • NAc.dump() / load_state serialize _cluster_reward_bias under a new cluster_reward_bias key (keys join (agent, cluster, tool_sig) with \x1f so tool:use:dodge round-trips cleanly). Backward-compatible: pre-G4 snapshots load with an empty cluster dict.
    • substrate_diff.NacDiff carries cluster_reward_bias_{available,l2,top_deltas}; substrate_diff_to_json surfaces the field in result.json under nac.cluster_reward_bias so 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_bias on 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 flake test_context_index.py::test_similar_text_found unrelated).

    Empirical confirmation (2026-05-11 Roy-0 re-measurement):

    • cluster_reward_bias_l2 = 2.4587 on both A-vs-blank pairs (vs n/a pre-G4 — the field didn't exist in aut_nac.json).
    • cluster_reward_bias.available = true on every pair (vs false pre-G4).
    • Top deltas: 6× tool:sense_food_source at the +1.0 per-key cap (six distinct EC cluster ids accumulated during arm A's 50-turn priming) + 2× tool:infant_humanoid_pick_up at ±0.15 (arm A hit a failure case blank arms didn't).
    • b_vs_c cluster_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.3 may 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 from credit_node via reaction-driven distribute_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-primary and 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 when aut_mode == "substrate-primary".
    • e293eb7SensorEncoder.encode_sensors() fires once per tick before recommend_action; drives hashed into 384-dim embedding, routed through EC.pattern_complete_or_separate with modality "interoception".
    • fad326ffrozen_centroid_modalities config in similarity/ec.py prevents interoception centroid from drifting.
    • 6d0e4a7NAc._cluster_reward_bias dict + update_cluster_reward() API + recommend_action(current_cluster_id=...) parameter; agent_loop.py::propose_via_substrate captures cluster_id from encode_sensors() and threads it into recommend_action.
    • 6643755 — orchestrator narration silenced when aut_mode == "substrate-primary" (simulation/bridge.py skips inject_cli and percept_anxiety_hook).

    What's deferred (the G4 wire): the 6d0e4a7 commit message states explicitly: "Reward update wiring (cluster_id-aware record_outcome) is deliberately out of Track 2's scope — the API exists and is unit-covered, but the agent_loop's _record_outcome chain doesn't yet plumb cluster_id." cluster_id is captured at proposal time but never travels to the post-execute outcome record, so NAc._cluster_reward_bias stays empty AND the per-tool reward_bias stays 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_bias0 keys in both priming-final and arm A. goal_reward_bias carries only the two test probes (cradle_prelinguistic: 0.196 + an arm-specific key).
    • aut_nac.json::links — 133 causal-link targets accumulated, but the outcome_signature fields 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 mostly valence = -0.25 (passive observation), no text/percept_refs, no concept_index entries.
    • ec.json — not present in any session dir (EC writes go through the live EC instance during the run but aren't persisted under the aut_* 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_diffsreward_bias_l2: 0.0 and reward_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, default min_confidence=0.3). With empty reward_bias + empty _cluster_reward_bias + no causal-link confidence to score against + cold-start drives, every available tool scores 0.0. recommend_action returns None. agent_loop.py:742 sees None and returns None. The substrate-primary branch at agent_loop.py:2664 leaves ctrl.pending_proposal unset. Next tick: same gate fires the same way. No log line surfaces — the only positive log ("substrate-primary proposal: tool=...") fires inside the if substrate_proposal is not None: branch.

    What closing it costs (concrete change surface): plumb cluster_id from propose_via_substrate's capture site through to every _record_outcome call in agent_loop.py, then have those sites invoke NAc.update_cluster_reward(agent_id, cluster_id, tool_signature, reward) alongside the existing reward-update calls. Per the record_outcome_call_sites feedback memo, there are ~7 _record_outcome sites in agent_loop.py (grep nac=_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 confirm reward_bias_l2 > 0 on the A-vs-blank pairs.

    What does NOT close G4 alone: populating reward_bias is necessary but not sufficient for the proposer to fire. The min_confidence=0.3 gate 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_reward is 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) shows reward_bias_l2 > 0 after 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 6d0e4a7 message, tests/integration/test_substrate_primary_aut.py (5 unit-coverage tests for update_cluster_reward exist 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_prelinguistic in 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.

Phase 1 — Vocabulary-constrained output (~2 weeks)

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_id populated 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_provider parameter. 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_id symbol-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 over EC._binding_edges rather 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.

Phase 2 — Symbol-binding layer (~2 months)

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.

Phase 3 — From-scratch sequence model (~6-12 months, the headline)

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.

Phase 4 — Pretrained-vs-grounded A/B (~3 months, only if Phase 3 clears)

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.

Cross-cutting concerns

Tokenization

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.

Catastrophic forgetting across sessions

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.

Curriculum scheduling

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.

Compute

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.

Pretrained-LLM crutches to disable for Phase 0/1

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).

What's deliberately NOT in this plan

  • 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.

File touch summary (estimated)

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.

Iteration log

(Append findings here as phases run. Living doc convention per persona_convergence_crucible.md and behavioral_convergence_practice.md.)

2026-05-09 — Plan promoted from deferred; parallel-mode architecture decided

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):

  1. LLM band-aid surface ~845 LOC across the codebase. json_parser.py is 32.9% repair logic for malformed LLM output; prompt_builder.py is 22.5% mitigation scaffolding; the largest single band-aid is orchestrator.py::_stall_detector at 219 LOC existing solely because the LLM gets stuck in observation loops. 7 distinct LLM failure-mode mitigations have dedicated code.
  2. Recent engineering split: 60-70% LLM mitigation, 30-40% bio-substrate. The drift is real and accelerating.
  3. 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 (--research flag); 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.

2026-05-09 — Phase -1 GATE CLEARED + Phase 0 harness SHIPPED (PR #228)

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 an LLMProposal with strategy_used="substrate-primary" and dispatches via the standard executor path.
  • --aut-mode {llm-primary,substrate-primary} plumbed through clistart_simulation_moderun_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_prelinguistic arc + select_arc_for_goal exact-name resolution.
  • Motor-only AUT prompt renderer.
  • SubstrateTelemetry JSONL writer wired into the substrate-primary tick.
  • --research semantic split: with --aut-mode substrate-primary it 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):

  1. 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 calling evaluate_failures() inside propose_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.
  2. Substrate-primary bypasses LinguisticEncoder. EC node_count stayed 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.
  3. Drive-affinity heuristic + scene tools. The infant body's pick_up lost to the cradle scene's sense_food_source because the affinity table matches "food" substrings. Same mechanism as the test suite caught with pick_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.
  4. 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.


Honest recap

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.

Intake (2026-07-15): substrate-driven salience for the LLM path (from Exp 44)

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.