The whole loop at a glance
Everything else on this page is a close-up of one station on this ring.
A body senses the world. The sense becomes an observation. Paired with what was sensed before and what was done in between, it forms a triplet — the only record of cause and effect a body ever gets. A population of small world-models (frames) each try to place the observation in their own coordinate space; the ones that succeed contribute to the global pose, the system's reading of the moment. An external scorer judges every frame on everything it saw. A fixed drive turns the moment into a value signal, the action policy picks the move expected to raise it, the body acts — and the world answers with the next observation.
Before, action, after VALIDATED
A brain that reads every book about swimming still can't swim.
Everything PRA learns arrives in one shape: (previous_observation, action, observation) — what I sensed, what I did, what I sensed next. No labels, no dataset, no replay buffer. Drive the little rover below and watch its entire education stream past: each row is one triplet, each colored strip one observation (the rover's 12 distance-ish sensor channels).
Why triplets and not text
Prediction is the test of understanding: a model understands a world exactly as far as it can say what happens next, given what it does. That requires acting. Language describes the world secondhand; the triplet is the world answering you directly.
The reference frame VALIDATED
One small world-model betting the world has a particular number of knobs.
A frame is a learnable coordinate space with a chosen dimensionality dim. It does two jobs: place (turn an observation into a coordinate — its pose) and predict (given pose + action, say where the pose goes next). Below, four rival frames — betting 1, 2, 3, and 5 knobs — learn the same world live. The world secretly has 3 hidden knobs. Watch the reconstruction error: too few dimensions can't explain the world; enough dimensions can; extra dimensions… also can. That ambiguity is a problem we solve two sections down. (Give it a few hundred steps — real learning takes real time.)
The actual math (running live above)
encoder: h = tanh(W1·obs + b1); pose = W2·h + b2 decoder: hd = tanh(D1·pose + d1); recon = D2·hd + d2 transition: th = tanh(T1[a]·pose + tb1[a]); pred_pose = T2[a]·th + tb2[a] (one net per action) fit_quality = ‖recon − obs‖ / (‖obs‖ + 1e-6) prediction_error = ‖decode(pred_pose) − next_obs‖ / (‖next_obs‖ + 1e-6) ← observation space! effort = ‖pred_pose − pose‖ learning: plain gradient descent, every gradient element clipped to ±gradient_clip.
The load-bearing rule: prediction error is measured in observation space — the predicted pose is decoded back into a claimed observation and compared with reality. A collapsed frame can make its own pose trivially predictable; it cannot fake predicting the world. Never let a frame invent the ruler it is measured with.
Never grade your own homework VALIDATED
Frames choose what they learn from. They never choose what they're judged on.
Each event, a frame measures its fit. If the fit beats the fit_gate, the frame maps: it contributes its pose and gets to learn. If not, it drops the event — no learning. But here is the trick that keeps the whole ecology honest: the survival EMAs update on every event the frame is exposed to, mapped or not. Learning is sparse and self-selected; judgment is total. Flip the cheat switch below to see what happens if a frame were scored only on the events it deigned to map.
The update, exactly
fit = ‖recon − obs‖ / (‖obs‖ + 1e-6) recon_err_ema ← ema_decay·recon_err_ema + (1 − ema_decay)·fit ← on EVERY exposed event if fit < fit_gate: frame maps → contributes pose, learns ← "sparsity by pull"
This split — sparse learning, total judgment — is the entire reason dimensionality selection works. A cheating frame narrows its gate, learns a tiny comfortable niche, and reports a beautiful score while explaining almost nothing. The coverage-fair EMA makes that strategy fatal instead of dominant.
The price of a dimension VALIDATED
Parsimony is not a truth the system discovers. It's a price it pays.
Honest error keeps creeping down as you add dimensions — past the world's true size and onward, forever, via overfit. "Lowest error wins" would over-dimension without limit. So the scorer charges rent: w_complexity · dim. The winner is the frame at the start of the diminishing-returns plateau — the point where one more dimension stops paying for itself. The error bars below are real, from frames of every size trained live on a world with 4 hidden knobs. Drag the price and watch the winner move: at zero the raw-error minimum wanders into the overfit plateau; too dear collapses everything toward one knob. And notice the winner usually settles at 3, not 4 — the fourth knob doesn't pay its rent at this budget. The real system does the same at scale (it lands at median dim 9–10 on worlds of "true" size 20–50): the price doesn't discover truth, it buys value.
The survival score
survival_score = w_explain·recon_err_ema + w_predict·pred_err_ema
+ w_effort·effort_ema + w_complexity·dim (lower = better)
At scale the error curve has no knee at all — it just keeps falling to the capacity ceiling. So the penalty isn't detecting the world's true size; selection simply keeps buying dimensions while the marginal error-gain exceeds the price, and stops there. The ecology lands at the price-optimal operating point — stably, at every scale measured.
A head full of rival guessers VALIDATED
Evolution inside one head, on a timescale of minutes.
Nobody tells PRA how many dimensions its frames should have. Instead: if no frame can map an observation, a new frame is born on demand. Every consolidation cycle, new candidates spawn near the best frame's dimensionality (copy, never mutate — a bad idea just fails to earn its place). The young get a protection window; the mature face a survival bar that tightens as the population grows — crowding raises the pressure, so the head stays lean without a hand on the dial. Below, a real population of real frames learning a true-dim-3 world, from empty.
The slow loop, in order
each consolidation cycle:
1. age everyone; candidates mature at age ≥ min_age_cycles
2. apply pending anatomy changes
3. evict: threshold = base / (1 + coeff · max(0, pop − baseline)) ← falls with crowding
remove unprotected frames scoring above it, worst first (never below min_frames)
hard cap: still too many → cull worst to max_frames
4. spawn candidates: dim = best_dim ± 1 (mostly), sometimes explore
5. snapshot
Note the division: crowding tightens the tolerated-error bar. A bar that loosened instead would let eviction vanish and the population slam into the hard cap. And this is the system's whole memory story — it keeps running structure, not stored episodes. No scrapbook required.
Wanting things DESIGNEDOPEN EDGES
The one thing the system can never rewrite is its own wanting.
Action needs a direction, and direction needs a fixed notion of "better": the innate drive, a pure function from recent experience to a scalar value signal. The corridor below has three territories: a mastered zone (nothing left to learn), a learnable zone (structure the agent can still crack), and a noisy TV (pure static — unlearnable, forever novel). The agent picks actions by one-step lookahead on its drive. Switch drives and watch where it spends its life. Novelty alone walks straight into the TV and stays there; learning-progress ignores what it has mastered and what it can never master.
The drives, exactly
curiosity = w_progress·LP + w_novelty·novelty LP = max(0, mean(older errors) − mean(recent errors)) ← rewards error going DOWN novelty = unfamiliarity vs a bounded memory of recent observations competence = mastery + familiarity mastery = max(0, 1 − mean(recent prediction error)) familiarity = 1 − novelty
The anti-noise-trap property: on static, error is high but flat, so LP ≈ 0. On mastered ground, error is low and flat — LP ≈ 0 again. Only a live learning frontier scores. Honesty note from the measurements: at scale, novelty-directed curiosity performed worse than random (−0.062); the inverted preference — competence — is what beat it (+0.067). The perfect drive is still an open problem, and PRA says so out loud.
Turning down the static OPT-IN
Real sensors lie. Some channels carry the world; some carry snow.
Feed half the sensors pure static and structure-finding collapses: the noise floor drowns the score's gradient, static corrupts the shared encoder, and no frame ever clears the survival bar. The fix is a learning-free whiteness estimator: static is white — a fresh draw every step — while real channels carry the world's temporally-correlated dynamics. Per channel, track the lag-1 autocorrelation ρ̂; weight channels by it; apply the same weights to judging and learning (the judge and the learner must never disagree). Channels never go fully silent — the floor keeps every channel minimally audible, in case it starts carrying the world tomorrow.
The estimator (five arrays, zero learning)
per step: m ← β·m + (1−β)·obs; d = obs − m
v ← β·v + (1−β)·d²
cov ← β·cov + (1−β)·d·(prev_obs − m)
recompute: ρ̂ = clip(cov/(v+1e-6), 0, 1)
w = clip(ρ̂ / max(ρ̂), floor, 1) (unready channels stay at 1)
apply: judging → ‖(recon − obs)⊙w‖ / (‖obs⊙w‖ + 1e-6)
learning → encoder sees w⊙obs; placement backprops (recon − obs)⊙w
The transport argument: weighting unit static at 0.2 makes every computation operation-for-operation equivalent to running at static amplitude 0.2 — an operating point already measured as a clean PASS. The remedy doesn't hope; it transports the system into a regime known to work. Off means off: floor = 0 → byte-identical pinned behavior, zero extra float work. (Demo uses a faster β than the shipping 0.995 so you don't wait 200 steps.)
Pausing, not remembering VALIDATED
A brain you can switch off is only trustworthy if switching it back on changes nothing.
The entire evolving state — every frame's tensors, drive bookkeeping, counters, the random number generator itself — serializes to a versioned blob. The guarantee is byte-identical resume: a run resumed from any snapshot is bit-for-bit the same as the run that never stopped. Not "approximately the same brain." The same brain. Prove it to yourself:
This runs a live ecology for 300 steps, snapshots at step 150, runs to the end, then restores the snapshot and re-runs the second half. The two traces are hashed and compared.