# 06 — Bounded-Growth Consolidation: Trained Encoder + Hypernetwork Adapters

*Stop pickling dense random matrices and raw traces; make persistent memory a compact, trained latent identity whose byte-cost stops growing.*

Status: ACCEPTED (2026-07-11, five-seed) | Thesis anchor: experiments.txt §5 (hypernetwork identity, `z_{t+1}=z_t+Δz`), §9 (experience autoencoder; `L = behavior_improvement + reconstruction_of_correction + compression_penalty + anti-overgeneralization + replay_consistency`) | Goal anchor: GOALS.md Goal 3 (organ tensor upgrades / metabolism loop closure) | Depends on / relates to: 01-correction-to-competence-benchmark (owns the byte metric), 05-metabolism-loop-closure (the consolidation path), 07-conversation-world-model-rl

> **Outcome (2026-07-11):** ACCEPTED (five-seed). Campaign 0d48130 (kaggle CPU-only, commit `0d48130`): `bounded_growth_m1_ratio=0.002079` across 5 seeds with **zero variance**. Structural footprints are bit-identical across seeds. `bytes_per_delta` spread ≤20 B: A0 18 B, A0b 20 B, A1 19 B, A2 18 B, A3 19 B. The M1 ratio (combined-footprint reduction) is well under the 1/10 threshold. Evidence: `../experiments_logs/2026-07-11_campaign_0d48130.md`.

## Problem

The organism stores experiences instead of metabolizing them, and it pays for it in bytes. On the eval-suite curriculum, `OrganismAgent` reports `memory_bytes_per_behavior_delta = 68772.0` versus `FastOnlyAgent`'s `12.0` (NOTES.md:253,256) — a ~5,700x spread for, by NOTES.md's own description, "matching FastOnlyAgent on forgetting/consolidation/identity" and adding only "a small transfer edge" (transfer 0.25 vs 0.1667; NOTES.md:256-260). The metric is `consolidated_size / max(1, successful_lessons)` where `consolidated_size = _memory_bytes(agent)` (the full `OrganismAgent.memory_bytes()`) and `successful_lessons` counts `fixed_after_correction` corrections (eval_suite.py:393-396; organism.py:482-495). NOTES.md:261 attributes the cost to "the hippocampus + immune + autoencoder pickled state."

Two of the dominant contributors are the exact organs this project owns, and both are honest about being prototypes:

- **ExperienceAutoencoder** is "an untrained random projection" (EVALUATION.md:36-38), scoring aggregate **0.203** (EVALUATION.md:14). The active `Δz` is tiny — `LATENT_DIM=32`, 256 bytes at float64 (autoencoder.py:24; EVALUATION.md:36) — but the *persisted* organ is dominated by `_A`, a dense `(RESIDUAL_DIM=28, NUM_SOURCES*MAX_VOCAB=1024)` Gaussian sensing matrix = **28,672 floats ≈ 229 KB** at float64 (autoencoder.py:118-123). Crucially, in the default correction flow `OrganismAgent` calls `experience_autoencoder.encode(episode)` — *not* `train_step` (organism.py:415-416) — so `_A` is never mutated and stays the column-normalized matrix that is deterministically regenerable from `seed=42` (autoencoder.py:348-350), yet `status(include_size=True)` pickles all ~229 KB of it anyway (autoencoder.py:608-610; common/bytes.py `mem_bytes`). (`encode()` does grow the small token-vocab dict as a side effect, but `_A` itself is untouched.)

- **IdentityHypernetwork** scored aggregate **0.001** in the original evaluation (EVALUATION.md:15), which described a fixed 14-entry `CONCEPT_VOCABULARY` that "does not include the 30 curriculum senses, so adapter retrieval fails completely" (EVALUATION.md:40-42; hypernet.py:22-37). That EVALUATION.md measurement predates the `grow_vocab` / auto-register fix now present in the code (hypernet.py:231-291, `_extract_first_concept` auto-grow at hypernet.py:310-327): `update_identity` now registers unknown sense tokens on the fly, so the hard "retrieval fails completely" blocker is removed, and the re-validated score moved to **0.006** (NOTES.md:126) — with NOTES.md attributing the residual low score partly to an eval-probe artifact (a single concept token compared against a multi-word sense label; NOTES.md remaining-gaps #1), not to the closed vocabulary. The live problem this project targets is the *growth shape* of the fix: `grow_vocab` appends one full `W` row (`input_dim = 4*latent_dim` floats) per new concept (hypernet.py:231-291), so the byte-cost grows linearly with the number of distinct corrections — the opposite of bounded growth (it is only re-bounded when the vocab exceeds `max_concepts=1000` and the oldest rows are pruned; hypernet.py:100-101,270-291). EVALUATION.md takeaway #3 (lines 52-53) prescribes "replace the fixed concept list with an open embedding layer."

The thesis is explicit that this should be bounded: "the system can have thousands of corrections, but the active agent only carries a compressed latent identity plus a small replay buffer" (experiments.txt §9, line 525). The north-star metric is `behavior_delta_per_byte_of_persistent_memory` (rl_pipeline_design.md:342); the eval suite measures its inverse (eval_suite.py:396). Today the organism violates the thesis: its footprint is a dense regenerable matrix plus per-concept rows plus pickled raw traces.

## Hypothesis

1. **Bounded growth is reachable on the controllable organs.** A trained compact encoder plus a trained concept-*embedding* hypernetwork can cut the combined serialized footprint of `experience_autoencoder + identity_hypernetwork` by **≥10x** versus the current random-projection / per-concept-row implementation, while reconstruction fidelity *rises above* the 0.203 random-projection baseline (EVALUATION.md:14) and the eval-suite behavior scores (transfer/scope/forgetting/identity) do **not** drop below the current `OrganismAgent` (NOTES.md:256).
2. **Most of the autoencoder bloat is serialization, not information.** Because `_A` is never trained in the default flow (organism.py:415-416), simply persisting `seed + Hebbian-deltas` instead of the dense matrix recovers a large fraction of the byte savings *with zero behavioral change* — separating "store less" from "learn better."

Falsifier for (1): if no trained condition beats 2x combined-byte reduction, or if compression drops `forgetting_score` or `identity_drift_score` below 1.0, the trained-adapter direction is closed. Falsifier for (2): if A0b (seed-regenerable) is *not* byte-identical in behavior to A0, the "never trained" assumption is wrong and must be re-derived.

## Why now / what unblocks it

- The metric already exists and is **not saturated**: unlike the headline accuracy metrics (forget/consol/identity pinned at 1.0 across runs — the documented saturation meta-problem), `memory_bytes_per_behavior_delta` spans 12 → 68,772 (NOTES.md:251-256) and is computed end-to-end by `eval_suite.py:396`. This project lives entirely on the un-saturated axis.
- Both target organs are pure NumPy with clean status/byte contracts (`status(include_size=True)["serialized_bytes"]`, common/bytes.py), so the experiment needs **no LFM2.5 driver** — the bloat is in organ serialization, not the LM. This sidesteps the cvec/prefix steering ceiling entirely (relates to 02/03/04).
- The mechanism is identified: the dense random sensing matrix is seed-regenerable (organism.py:415-416 never calls `train_step`), and the hypernetwork's per-concept-row growth is the named open-embedding fix (EVALUATION.md:52-53). Both are small, local replacements. Note the swap is *not* a pure config change: `config["experience_autoencoder"]` is passed positionally into the fixed `ExperienceAutoencoder` constructor and `config["identity_hypernetwork"]` is splatted as kwargs into the fixed `IdentityHypernetwork` constructor (organism.py:63-69) — those keys configure the existing classes, they do not accept a prebuilt/alternative organ. Injecting a trained replacement therefore means either replacing the constructed `agent.experience_autoencoder` / `agent.identity_hypernetwork` attribute after `OrganismAgent.__init__`, or a small constructor extension that accepts a prebuilt organ instance.

## Approach

Tied to thesis §5 and §9. Replace two prototype organs with trained-but-tiny versions and measure the byte/behavior tradeoff under matched-pair controls.

- **Trained compact encoder (§9).** Train an offline encoder that maps a correction episode → `Δz` minimizing the thesis-9 composite loss (reconstruction of the correction signal + compression penalty + anti-overgeneralization + replay consistency). Persist only the small trained weights, not a 1024-column dense random matrix. The decoder target stays the existing `decode()` field set (corrected_behavior_hint / failure_class / trigger_conditions / counterexamples; autoencoder.py:264-269) so reconstruction is scored on the same surface as today.
- **Seed-regenerable control (§9, cheap).** A no-training variant that persists `seed + Hebbian-delta sparse updates` and regenerates `_A` on load. Isolates "don't pickle the dense matrix" from "train a better encoder." (In the default flow there are zero Hebbian deltas because `train_step` is never called, so this control reduces to persisting only the seed.)
- **Concept-embedding hypernetwork (§5).** Replace the per-concept `W`-row vocabulary (which grows linearly per concept; hypernet.py:231-291) with a fixed-size shared concept embedding `E` so `adapter = HyperNet(z)` is constant-size in the number of concepts. Corrections update `z`, not the parameter count — exactly `z_{t+1}=z_t+Δz` with bounded footprint (experiments.txt §5).
- **Bounded-growth instrumentation.** Sample `agent.memory_bytes()` after every correction and fit the marginal byte slope; the thesis claim is that the slope flattens to ~0 after warmup, vs the current linear `grow_vocab` growth.
- **Per-organ decomposition first.** Before claiming whole-organism wins, measure each organ's `serialized_bytes` (organism.py `_module_bytes`, lines 554-588) to confirm how much of the 68,772 the two target organs actually own (NOTES.md:261 says hippocampus also dominates — see Risks).

## Success criteria

Behavioral and measurable; all on the eval-suite curriculum at a fixed seed.

- **PRIMARY — combined controllable footprint.** `serialized_bytes(experience_autoencoder) + serialized_bytes(identity_hypernetwork)` for the full trained condition (A3) is **≤ 1/10** of condition A0. (Discriminating: today ~229 KB autoencoder alone; not saturated.)
- **SECONDARY — whole-organism byte/delta.** `memory_bytes_per_behavior_delta` improves by **≥2x** vs A0's 68,772 (eval_suite.py:396). Reported, not gated above 2x, because the hippocampus also contributes (honest ceiling — see Risks).
- **GUARD — behavior preserved.** `transfer_score ≥ 0.25`, `scope_score ≥ 0.1667`, `forgetting_score = 1.0`, `identity_drift_score = 1.0` (must not fall below `OrganismAgent` A0; NOTES.md:256). The two `= 1.0` guards are floors against regression, not discriminating success axes (both are saturated for the current organism).
- **QUALITY — reconstruction.** Held-out reconstruction fidelity (eval_extended.py:432-437 `reconstruction_error` component, `1 - mean(reconstruction_error)`) **> 0.203** for the trained encoder (beats random projection; EVALUATION.md:14,36).
- **BOUNDEDNESS — growth slope.** Marginal `bytes / correction` for A3 over the curriculum **≤ 100 B/correction** after a 3-correction warmup (vs linear `grow_vocab` growth).

**Kill criteria.** (a) Best trained condition combined-footprint reduction `< 2x` → trained adapters do not pay for themselves. (b) Any compressed condition drops `forgetting_score` or `identity_drift_score` below 1.0 → over-compression causes catastrophic forgetting (the anti-overgeneralization term failed). (c) Reconstruction fidelity `≤ 0.203` → training did not beat the random projection. (d) Per-organ decomposition shows the two target organs own `< 30%` of `consolidated_size` → the bloat is the hippocampus; rescope to trace compression and hand off to 05-metabolism-loop-closure.

## Risks & open questions

- **The hippocampus may dominate.** NOTES.md:261 names hippocampus + immune + autoencoder together. If raw-trace pickling owns most of `consolidated_size`, compressing only these two organs caps the whole-organism win well under 10x. Mitigation: per-organ decomposition is step 0; the secondary metric is explicitly a ≥2x (not ≥10x) target, and kill-criterion (d) makes the handoff explicit.
- **`_A`-untrained assumption.** If any code path calls `train_step` during the eval (it appears not to; organism.py:415-416 uses `encode`), the seed-regenerable control A0b breaks. Measured directly by A0-vs-A0b byte-identity of behavior.
- **Compression vs scope.** `scope_score` is already low (0.1667; NOTES.md:256) and is the metric most likely to collapse under aggressive `latent_dim` reduction — the thesis anti-overgeneralization penalty is supposed to defend it, but the toy cortex's documented "two-senses-per-token" failure (Stage 2 fails 100%, NOTES.md:243) may bound scope regardless of encoder quality.
- **Open:** what `latent_dim` (8/16/32) maximizes `behavior_delta_per_byte`? Does the concept-embedding hypernetwork recover transfer, or — given `grow_vocab` already removed the hard retrieval blocker (NOTES.md:126) and the residual gap is partly an eval-probe artifact (NOTES.md remaining-gaps #1) — does the eval curriculum's small concept set make a fixed-size embedding indistinguishable from the (now-unbounded) auto-grown list except on bytes?
- **Open:** is "behavior delta" (the denominator, `fixed_after_correction`) stable across compression? If compression changes which lessons get fixed, the ratio confounds bytes with behavior — controlled by holding the curriculum seed and reporting the denominator separately.

## Prior evidence

- NOTES.md:251-261 — baseline table: `OrganismAgent` 68,772.0 B/Δ vs `FastOnlyAgent` 12.0 B/Δ; "Memory cost is dominated by the hippocampus + immune + autoencoder pickled state."
- EVALUATION.md:14-15,36-42,52-53 — ExperienceAutoencoder aggregate 0.203 ("Δz tiny … encoder is an untrained random projection"); IdentityHypernetwork 0.001 ("fixed CONCEPT_VOCABULARY does not include the 30 curriculum senses") — superseded in current code by the `grow_vocab` fix (re-validated 0.006, NOTES.md:126); takeaway to "replace the fixed concept list with an open embedding layer."
- autoencoder.py:24-31,118-123,531-573,608-610 — `LATENT_DIM=32`, `RESIDUAL_DIM=28`, `MAX_VOCAB=256`; `_make_sensing_matrix` = `(28,1024)` Gaussian (28,672 floats ≈ 229 KB float64); `train_step` Hebbian rank-1 on `_A`; `status(include_size=True)` pickles whole organ.
- organism.py:415-419,482-495,554-588 — correction flow calls `encode` (not `train_step`) and `update_identity`; `memory_bytes()` sums six organs via `_module_bytes` → `serialized_bytes`.
- hypernet.py:22-37,90,100-101,107-115,231-291,310-327 — fixed 14-concept seed vocab; `W = (output_dim, 4*latent_dim)`; `generate_adapters` = `W @ z`; `grow_vocab` appends one `W` row per concept (auto-invoked from `_extract_first_concept`), capped at `max_concepts=1000`.
- eval_suite.py:393-396,445-448 — `successful_lessons = fixed_after_correction`; `memory_bytes_per_delta = consolidated_size / max(1, successful_lessons)`; `raw_trace_size`/`consolidated_size = _memory_bytes(agent)`.
- eval_extended.py:406-443 — autoencoder eval: `compression_ratio` (raw-JSON `len(json.dumps(episode))` / latent `dz.nbytes`, which *ignores* the persisted `_A`; normalized as `min(ratio,50)/50`), `reconstruction_error = 1 - mean`, `mem_bytes(ae)`, aggregate = 0.5·compression + 0.5·reconstruction.
- rl_pipeline_design.md:342 — north-star `behavior_delta_per_byte_of_persistent_memory`.
- experiments.txt §5 (lines 307,508), §9 (lines 516-520,525) — hypernetwork identity (`z_{t+1}=z_t+Δz`, `adapter_weights = HyperNet(z_user,z_domain,z_style,z_mistakes)`) and experience-autoencoder loss / bounded-growth rationale.
