experiment log
2026-06-25 — SVD-init'd proj_c: direction survives cold boot
- File
2026-06-25_svd_init_proj_c_persistence.md- Size
- 9.9 KB
- SHA-256
e5195475e1e3ecdc…
2026-06-25 — SVD-init'd proj_c: direction survives cold boot
Session carried the "meaningful cvec steering" sub-goal past the
2026-06-24 raw_hidden finish line. raw_hidden produced clean on-manifold
steering at articulate_scale=0.001, but its steering DIRECTION lived
in last_correction_hidden — a warm-side field that
CortexAgent.save() deliberately does not persist. On cold boot the
cvector collapsed to zeros and the conversation demo's post-reload
turn was byte-identical to baseline. This session closes that gap by
moving the direction into proj_c, which IS persisted.
Two bugs found and fixed along the way
Before the SVD work could matter, two field-drop / shadowing bugs in
CortexAgent had to be cleared:
steering_modedropped by config reshape.CortexAgent.__init__rebuiltKVCortexConfigby hand with nine fields, silently droppingsteering_mode. The demo could requestraw_hiddenall it liked; the cortex always ran inproj_random. Surfaced as选选选…CJK garbage (the proj_random crash signature at scale 30) where the 2026-06-24 log claimed clean steering. Fix:dataclasses.replace(ccfg, d_embd=..., n_layers=...)— preserve every caller-set field; only override the shape mirrors.articulate_scaleshadowed by instance attribute.articulate()readsself.config.articulate_scale(the dataclass field). Settingagent.articulate_scale = Xafter construction only creates a shadowing instance attribute thatarticulate()never sees. Masked for several iterations because a debug print readingreloaded.articulate_scalereturned0.3correctly while the actual code path usedself.config.articulate_scale = 0.03. Fix: set the scale in theCortexAgentConfigpassed toCortexAgent.load(...); never via post-construction assignment.
Both are saved as feedback memories; both are the kind of silent silent-failure that would have masked the SVD work if not cleared first.
What landed in the code
KVCortex.init_proj_c_from_svd(hiddens) — new in-place method on
plastic-cortex/src/plastic_cortex/kv_cortex.py:
- Centres
hiddens(shape(N, d_embd)), runs SVD, takes top-d_cortexright singular vectors as the projector's basis. - Broadcasts the SAME slab across all
n_layers(per-layer Gaussian perturbation would reintroduce the off-manifold cliff that motivated raw_hidden in the first place). - Each column is a unit-norm singular vector scaled by
1/sqrt(d_cortex), matchingproj_random's bound convention soemit_cvecmagnitudes are comparable across modes. - Refuses
N < d_cortex(would yield degenerate singular vectors). - Marks
_dirtyso the nextemit_cvecregenerates payloads.
No new KVCortexConfig field. The SVD path needs external hiddens data;
an on-config flag would force sourcing that data via env/global.
KVCortexConfig is part of the CortexAgent pickle, so keeping it
data-only preserves serialisability. Callers wire data by calling
agent.cortex.init_proj_c_from_svd(np.load(...)) after boot.
Collection harness
experiments/lm_perception/collect_correction_hiddens.py produces
reports/correction_hiddens.npy of shape (72, 2048). Twelve canonical
correction episodes from correction-benchmark/dataset.py × six
paraphrase templates. Mirrors cortex_agent.py:197 exactly:
CortexAgent + CVecDriverConfig(n_ctx=512, embedding=True) +
peek_embedding(utterance, last_token_only=False). Distribution match
to perceive() is automatic by construction. Top singular values:
[179, 153, 133, 120, 112, 109, 100, 99] — non-degenerate spread.
Final-layer-only substrate is the smallest-fix path; Goal 2 layer-L
extraction can refine the basis later without changing
init_proj_c_from_svd's contract.
Demo
experiments/cortex_conversation_demo.py switched both config sites
from raw_hidden back to default proj_random, with SVD-init at boot.
Scale sweep on the live steered turn (post-correction, warm_norm ~6.83):
articulate_scale |
LM output | diagnosis |
|---|---|---|
| 0.001 – 0.01 | byte-identical to baseline | below noise floor |
| 0.03 | "**Answer:** In this product, 'profile' refers to a detailed description of the product's" |
clean steering, on-manifold |
| 0.1 | 'Celle (C) : Cellen - C : Cellen…' |
off-manifold degenerate |
| 0.3+ | 'flora flora flora…' / 'redesign redesign…' |
token-repetition crash |
Reload sweep (warm_state dampened by consolidate()'s 5% EMA, ~0.34):
reload articulate_scale |
LM output | vs baseline |
|---|---|---|
| 0.1 | byte-identical options list | no effect |
| 0.3 | "**Answer:** In this product, 'profile' refers to a detailed description of the product's" |
byte-identical to live steered |
| 1.0+ | repetition crash | off-manifold |
The ~10× ratio between the steered scale (0.03) and the reload scale (0.3) is the 20× consolidation dampening divided by the noise floor width — it is per-host-residual-norm dependent and must be re-swept if the LM or its quant changes.
What is verified
init_proj_c_from_svdlandsproj_cexactly on the top-d_cortexright singular vectors of the centered hiddens, broadcast identically across all layers. Asserted intest_svd_init_proj_c_structure(plastic-cortex/tests/test_kv_cortex.py): all slabs identical;proj_c[0].T @ proj_c[0]matchesVt[:d_cortex].T @ Vt[:d_cortex] / d_cortexwithin atol=1e-5; column norms are1/sqrt(d_cortex); pickle round-trip byte-for-byte. 12/12 cortex tests pass.- SVD-init'd
proj_csurvivesCortexAgent.save()/load():proj_cis restored atcortex_agent.py:454from the pickle unchanged. The reload turn is NOT re-injected with SVD-init, so any steering it produces is by-construction proof of persistence. - End-to-end demo:
post-reloadis byte-identical tosteered, both producing"**Answer:** In this product, 'profile' refers to a detailed description of the product's".baseline != steeredTrue,steered != post-reloadFalse,baseline != post-reloadTrue. Verdict: "cortex steering persists across consolidate + save/load."
This closes the "meaningful cvec steering" sub-goal's remaining
follow-up — proj_random itself is now meaningful (SVD-seeded), and
raw_hidden is retained as the documented coarse fallback. The
default can stay in proj_random permanently; raw_hidden is no longer
load-bearing for the demo.
What is NOT established — and an important falsification
Hypothesis tested: "if corrected enough times, the correction becomes new knowledge the LM can recall."
Probe: taught two fabricated facts the LM cannot know from pretraining
(zarnox_flux() returns 7331, plenvik_brim() returns 4096) as
corrections, 20 cycles each with metabolize() + consolidate() between.
Then probed on a clean prompt at gentle (0.001–0.03) and breaking (0.1+)
scales.
| What | Result |
|---|---|
cold_state norm after 20 cycles |
0.34 → 3.6 (10× accumulation — corrections DO compound) |
Taught token 7331 in any output? |
No, at no scale |
Taught token 4096 in any output? |
No, at no scale |
| Gentle-scale output | pretrained refusal ("not a recognized function") |
| Medium-scale (0.03) output | confidently hallucinated context ("commonly used in GitHub Actions") — wrong but fluent |
| Breaking-scale (0.1+) output | token-repetition crash |
Finding: the cortex accumulates state (your mechanism intuition is
right) but the state is a posture bias, not knowledge the LM can recall.
The cortex's state is a single (d_cortex=64) direction projected
through proj_c into a (d_embd=2048,) residual bias broadcast across
all 16 layers. That bias can reweight existing plausible continuations
(this is why the demo worked — "profile → business vertical" is a real
ambiguity in the LM's token distribution). It cannot manufacture tokens
the LM has no pretrained prior on, nor store (question → answer)
tuples — there's nothing to retrieve.
This is experiments.txt's "memory becomes changed dynamics, not retrieved content" thesis taken literally. It works for dynamics (steering, posture, framing, word-sense disambiguation). It breaks for content recall — the right answer has to already live, weakly-biased-toward, inside the LM's pretrained weight distribution.
What would actually deliver "correct enough times → new knowledge":
Goal 1's reserved-KV-slot writes — real (k, v) pairs into an attention
KV slot so the LM's own attention can retrieve them. That is
retrieval-as-memory, structurally distinct from the cortex's
steering-as-dynamics. Saved as project memory.
Where this leaves the goal graph
- Closed: "meaningful cvec steering" in full.
proj_randomis SVD-seeded and persisting;raw_hiddenis the coarse fallback. - Open: differentiable plasticity on
proj_c(currently no post-init training path;train_steponly updatesproj_hidden). Per-layer SVD bases (different slab per layer) is also deferred — per-layer noise reintroduces the cliff. - Newly urgent: Goal 1 (reserved-KV-slot writes) is now the only path to actual recall of taught facts. The cortex steering work proved that steering alone cannot manufacture tokens; retrieval into attention can. That is the next milestone.
Files touched this session
plastic-cortex/src/plastic_cortex/kv_cortex.py— newinit_proj_c_from_svdmethod.plastic-cortex/tests/test_kv_cortex.py—test_svd_init_proj_c_structure,test_svd_init_rejects_undersized_hiddens. 12/12 pass.experiments/cortex_agent.py—dataclasses.replacefor config reshape (field-drop fix).experiments/cortex_conversation_demo.py— switched to SVD-init'dproj_randomat scale 0.03 (steered) / 0.3 (reload).experiments/lm_perception/collect_correction_hiddens.py— new harness; outputsreports/correction_hiddens.npy(72×2048) +correction_hiddens_meta.json.experiments/lm_perception/reports/correction_hiddens.npy— generated dataset (committed).