Skip to content

Release 0.94.0: maturity, an opt-in for a brain that keeps learning - #255

Merged
muellerberndt merged 29 commits into
mainfrom
release-0.94
Nov 5, 2026
Merged

muellerberndt merged 29 commits into
mainfrom
release-0.94

Conversation

@muellerberndt

@muellerberndt muellerberndt commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

Release 0.94.0 (milestone v0.94.0, roadmap #109): one opt-in, maturity (#253), for a brain that goes on learning after it has acquired a skill (#169). Off, every brain is 0.93.1's to the bit.

What it adds

  • Maturity (Deep habits: repetition hardens a skill, disuse fades it, later learning does not erase it #253, opt-in). Brain.compose(..., maturity=True) gives the brain one slow variable, the use of its plasticity: a leaky integral of the actor's applied synapse steps, with no count of updates or age. The step factor m = 1 / (1 + (use / maturity_scale) ** 4) multiplies the actor's synapse and bias steps and engages surprise-gated writes on the place records with strength 1 - m, so a mature store keeps what an outcome confirms and moves on a surprise. maturity_scale (10) and maturity_recovery (200,000 moments) are retune settings; maturity is a construction setting. A mature brain saves checkpoint format cadence-brain/2; every other brain saves cadence-brain/1 as before. Documented in reward, memory, api and tune.

The readings (#169)

Read on fresh brains under a protocol declared before the run, in a continuing robot arena (144 brain-runs per arm), against the founder at its own actor step and at step 0.003, the bar #169 sets. Seven of the eight core gates held:

Gate Reading
Twenty fights: skill carried 0.105 lost, against 0.099 at step 0.003 (s.e. 0.011) and 0.170 at the founder's step
Sixty fights 0.003 less lost than at step 0.003
Acquisition (door test) +0.209, bar +0.106
Preservation panel chambers as the founder's; step factor 0.996 or above in every life
Reversed route unlearned falls 0.784, bar 0.361
Practised skill against a competing one loses 3.5 points (limit 5); the competing skill reaches the founder's
Short life as the founder's
Twenty fights: calm or single-winner runs failed: 2 mature runs against 1 founder run

#169 stays open with the full table; whether maturity becomes the default is #252's panel; savings after displacement are #254.

No regressions

  • Independent reviews of the whole branch against 0.93.1 (three rounds): the founder path is identical to 0.93.1; every finding is fixed with a test that fails without it, among them a load that accepted a surprise gate on stores other than the place records, an overflow warning on an error far within the noise, and grow and retune each pointing to the other for maturity. The last round also simplified the actor's scaled steps and the gated write, bit-identical for the founder (0 of 24 lives) and the mature brain (0 of 926 hashed values over NumPy, torch and mlx).
  • Bit identity to 0.93.1 on the release commit: four batteries (the kernels, learner, composed operations and checkpoints; lives with every 0.93 option, saved, reloaded, retuned, grown and grafted; refusals over damaged checkpoints and every setting; several-slot brains) differ only in the version string inside checkpoint metadata, and the 203 checkpoint members of four brains are identical to 0.93.1's with it normalized; founder parity 0 of 24 lives differing.
  • Suites on the release commit: the tests (2,389), the benchmarks directory (1,616), the mlx suite (2,419) and the recorded-value tests on the NumPy loop (243) pass; ruff and mypy are clean.
  • Soak lives on the CPU, torch MPS and mlx (20,000 moments each, living and taught, saved and reloaded every 1,000 moments with the twin continued): no refusal, no divergence between a brain and its reloaded twin, and all six end on the same fingerprint as on 0.93.0.
  • Artifacts: wheel and sdist pass twine check --strict; the wheel installed into a fresh venv runs the founder, every 0.93 option and maturity (living and stepping) through train, save, load, continue and grow, with a mature brain saving cadence-brain/2 and every other cadence-brain/1.

🤖 Generated with Claude Code

muellerberndt and others added 29 commits November 5, 2026 04:28
…es (#253, opt-in)

maturity=True gives each brain one slow variable, the use of its
plasticity: each actor update adds its applied magnitude in the brain's
own step units per synapse, and every moment of life (live, step, or
learn called on its own) keeps 1 - 1/maturity_recovery of it. The step
factor m = 1 / (1 + (use / maturity_scale) ** 4) multiplies the actor's
synapse and bias steps and engages the store's surprise-gated writes with
strength 1 - m: a write's change of a record, the shared matrix and the
stream's residual alike, is multiplied by 1 - (1 - m)(1 - g), with
g = max(0, 1 - 2 sqrt(R) / |e|) and R the record's running noise scale
(rate 0.05, from zero at birth). No count is kept; an idle brain's use
relaxes. Founders: maturity_scale 7.5, maturity_recovery 200,000.

The variable and the store's noise scales are saved and loaded with the
brain, kept by grow and graft and counted by parameters(); the scale and
the recovery are retune settings, maturity a construction one. On a
nursery life the library form reads as the evidence lane's prototype to
rounding (the actor's synapses within 2e-15, the use and the noise scales
equal). Off, the founder is unchanged: founder parity 0 of 24 against
a4fcea0, and the identity, options, refusals and slots batteries read 0
differences. Shared suite 3991 passed, 98 skipped, 1 xfailed; MLX suite
4021 passed, 68 skipped, 1 xfailed; ruff and mypy clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every panel life keeps the step factor at 0.98 or more at this scale
(SCREEN7: 7.5 held one key-door life at 0.9606, 9.0 at 0.9790, 10.0 all
at 0.9887 or more). Maturity tests 10 passed; founder parity 0 of 24
against a4fcea0; the four batteries 0 differences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A refused live, step or learn of a mature brain relaxed its use (the moment wrapper relaxed in
a finally), and a refused write left the store's noise scales moved (the gate updated them in
place, and Brain.learn's restore did not keep them). The gate now computes the moved noise
scales as a new array that the write installs with the records it accepts, Brain.learn keeps
and restores the reference with the store's other arrays, and the use relaxes only when the
outermost moment returns. Tests: a refused live, a refused step and a learn the actor refuses
after the store's write leave every array and the use as they were; a refused overflowing
store write keeps the noise scales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.93.1 loaded a maturity checkpoint, saved as cadence-brain/1, as a founder brain, dropping
the maturity and the store's noise scales without a word. Following the library's convention
for a new component, a brain with maturity now saves under the new format name
cadence-brain/2, which 0.93.1 and earlier refuse; every other brain saves cadence-brain/1 byte
for byte as before. Brain.load reads both and refuses a cadence-brain/1 file, or a file in the
layout of 0.84.3, that carries a maturity, and a cadence-brain/2 file without one. Test: the
founder's and the mature brain's format names, and each mismatch refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#253)

With two or more writing streams the gate moved the shared records by the mean of each row's
gained change while a stream that did not write moved by the mean gain times the ungated
change, so its residual around the shared records drifted (0.095 in the review's probe), and
memory.md's formula held for one writing stream only. The gate now takes one gain per action
for the write, the mean over the writing rows that observe the action, and applies it to the
ungated change of the shared records and of every writing stream's residual alike: every other
stream keeps its residual exactly as without the gate. With one writing stream the gain is that
stream's own and the arithmetic is the same to the bit. The docstring and memory.md give the
exact formula. Test: two writing streams, one confirmed and one surprised, move the shared
records and their residuals by the mean gain times the ungated change, and the third stream's
residual stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A finite outcome near 1.3e154 squared beyond the floating-point range: the noise scale became
inf, and NaN on the coordinates the cue never touched (zero weight times inf), and the brain
saved a checkpoint its own load refused. The store now checks the new noise scales with the
records and refuses such a write with the overflow's ValueError before anything is installed,
so no NaN is written and a saved brain always loads. Test: a live and a step paid 1.3e155
are refused, every array and the use stay, and the brain saves and loads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mature brain's actor update built its scaled config with dataclasses.replace, which reran
ActorCriticConfig's validation, so the eta_bias-above-eta RuntimeWarning fired on every update
(27 times in 30 moments in the review's probe). The update now copies the validated config and
sets the two scaled steps on the copy without validating again; the arithmetic is unchanged.
Test: a brain with eta_bias above eta and maturity warns once, at construction, through 30
moments of life at a factor below one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maturity_scale and maturity_recovery were accepted and ignored without maturity=True, even at
-5 or NaN. Construction now validates both always and refuses either at a value other than its
founder without maturity, as memory_context is refused without memory_choice; api.md's
construction refusals list both rules. Test: each setting without maturity, at an invalid or a
changed value, is refused; invalid values with maturity are refused; the founders named alone
build the founder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eads any use (#253)

A saved maturity with a missing field raised KeyError, one of the wrong type TypeError, and an
extra field, a non-boolean surprise_gate, negative noise scales and use=1e300 loaded (the last
then overflowed in factor(), a Python float power); a mature brain whose store had lost its
noise scales loaded ungated, and integer noise scales loaded and then failed the first write.
Load now validates the maturity field by field (exactly scale, recovery and use, each a finite
real in its range, use at most scale * Maturity.LIMIT, 1e76, where the factor is 1e-304),
refuses a non-boolean surprise_gate, negative noise scales and a maturity without the store's
noise scales, each with a ValueError naming the field, and reads the noise scales as floating
point. factor() returns 0 beyond the limit instead of overflowing; below it the arithmetic is
unchanged. api.md lists the rules. Tests: each malformed field refused by name, integer noise
scales load and live, the factor at 1e70 per scale is 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…253)

On the torch backend a mature brain's actor update read the efficacy back to the host to
measure the step it applied (one full read per update, where the founder reads none). The
update now keeps the kernel's scale tensor before the step (an update replaces it) and sums the
step's magnitude on the device, so one number comes to the host; the CPU and MLX backends
measure on the host arrays as before. Test: a torch brain with maturity reads no efficacy
through 60 moments of life and its use matches the CPU brain's within 1e-9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
api.md's parameters() row now counts the maturity's use and the place records' noise scales,
its SynapticMemory section gives gate_noise and maturity with the gate's constants, and the
agent section documents Maturity (fields, factor() with ORDER and LIMIT, relax(), to_dict())
and ActorCritic.maturity. Maturity is exported from cadence as the package exports the other
classes a brain exposes (ActorCritic, SynapticMemory, Arousal), and listed in the API index.
memory.md states that the gate is the place records' alone: the context records and the direct
payments write as without maturity. Test: cadence.Maturity is exported, and a brain with
memory_choice, memory_extinction and maturity gates its place records only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
)

The previous commit bounded a saved use by scale * 1e76, so a brain retuned to a smaller scale
could save a use its own load refused. The bound is now absolute, Maturity.LIMIT = 1e64, at
which the factor's power stays finite at the smallest scale (1e-12); the actor's update keeps
the use at most the limit (no life comes near it, so every reading is unchanged), load refuses
a use above it, and factor() returns 0 instead of overflowing for a use set beyond it by hand.
Test: a brain at the limit, at the smallest scale, stays there through five moments and saves
a checkpoint that loads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oment sentence (#253)

Brain.load accepted surprise_gate and gate_noise on the episodic context records
and the direct payments, which a brain never saves; _load_memory now takes
gate=True for the place records alone and refuses the gate elsewhere, with a
test on both stores. docs/reward.md: a live refused after its outcome was taken
keeps that outcome learned, so the sentence on refused moments is narrowed to
moments refused before they learn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ActorCritic.learn swapped in a copy of its frozen config with the steps scaled
(copy.copy and object.__setattr__), restored it in a finally, kept a second try
block for the founder and restored a use that nothing in the try writes. The
steps now read the factor where they are taken (_rates, in _learn and
_learn_device), so learn has one try block and measures the step applied after
it. The same bits: the founder's step is (1.0 * eta) * step, the mature one's
(m * eta) * step as before; founder parity 0 of 24 and the mature battery
0 of 926 values against 2c26af0. The docstring names what maturity scales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SynapticMemory._gate divided the noise spread by the error outside its errstate,
so a subnormal error against a nonzero noise scale (an error of 1e-310 against a
noise of 1) raised "RuntimeWarning: overflow encountered in divide", an error
under warnings-as-errors. The ratio's overflow is the right reading (a surprise
of zero), so the division now runs with overflow ignored. The local named
strength, which read as the store's own strength array, is engaged. Same bits:
the mature battery reads 0 of 926 values against 2c26af0, founder parity
0 of 24. The new test fails without the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SynapticMemory.observe wrote the shared change, the prediction and the fast
correction twice, once for the founder's write and once inside the surprise
gate's branch. They are computed once; the founder installs them as they are and
the gate multiplies them per action by its gain. A change to the write law is
now made in one place. Same bits: founder parity 0 of 24, the mature battery
0 of 926 values against 2c26af0 (several streams, masks, salience, partial
writes, m from 1 to near 0), and the memory and episodic test files pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brain._gate_store had one caller, and the isinstance check before it was always
true there (the store function makes a SynapticMemory). The two assignments now
stand where the place records are made, and the method is gone. Same bits:
founder parity 0 of 24, the mature battery 0 of 926 values against 2c26af0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…253)

The decorator that relaxes a mature brain's use once per moment of life was
named _moment, the name of the brain's record of the action awaiting its
outcome (self._moment, with _moment_code and _coded_moment beside it), and its
counter _moment_depth read as part of that record. They are _moment_of_life and
_life_depth. A rename only: founder parity 0 of 24, the mature battery 0 of 926.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#253)

reward.md's section now says what maturity is for (a brain that goes on
learning after it has acquired a skill, #169), what it leaves at its own steps
(the critic, lessons, rehearsal, a learned code, the recall memory's maps), that
maturity is a construction setting retune refuses and grow and graft keep, that
the scale and the recovery other than their founders need it, what it costs
(the noise scales, a pass over the synapses per actor update, the gate's
arithmetic per write) and that a mature brain saves cadence-brain/2, which
0.93.1 refuses. A step refused after its outcome was learned keeps the step in
the use without the moment's relaxation, as a live does (read on 99d1fbb). The
founder-scale readings name the preservation panel with its link. tune.md's
defaults table gains the three settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
memory.md gave the gain but described the noise scale as a running mean at
0.05; the code moves each record's noise by 0.05 times the key's squared weight
on it relative to the key's largest, per writing stream and observed action.
The text block now has the gain, g and the noise update with their symbols
(k, e = v - k @ (C + F), sqrt(k^2 @ R)). A young brain writes as without the
gate within rounding, and every write into the place records is gated:
outcomes and, with memory_choice, replayed credit and demonstrations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…thing (#253)

"cadence-brain/1, one format whatever the brain holds, except" and "a file of
either format carrying the other's maturity" now read: a brain with maturity
writes cadence-brain/2, and Brain.load refuses a /1 file with a maturity and a
/2 file without one. The members table lists gate_noise under
memories/hippocampus/. Maturity(scale, recovery, use) validates nothing itself
(a scale of 0 divides by zero in factor()); compose, retune and load do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
)

The entry ran one paragraph with a 130-column line and placed the settings'
founders inside the description of the use. It now gives what maturity is for
(#169), the use and its relaxation per accepted moment, the step factor and the
gate, what keeps its steps, then the checkpoint, retune and export facts. "An
idle brain" is "a brain that lives without learning", here and in reward.md:
the use relaxes per moment of life, not with time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…253)

SynapticMemory's docstring ran the gate's law in one sentence and said the
noise moves "toward the squared error at GATE_RATE"; it now names m, g, e and
R in order, says that Brain.compose(maturity=True) sets the gate on the place
records, and gives the noise update with its weight (the cue's squared weight
on the record relative to its largest). Maturity's docstring says "one that
lives without learning" for "an idle one". Brain.retune's docstring lists
maturity_scale and maturity_recovery among the names it takes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The field was Any, so mypy read nothing of what the gate calls on it and a
maintainer had to find its type in generic.py. It is Maturity | None, imported
for type checking only (memory.py imports nothing of plasticity at run time),
and _gate asserts both halves of the gate. ruff and mypy clean; founder parity
0 of 24, the mature battery 0 of 926.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ales (#253)

parameters() adds the maturity's use and the place records' noise scales, as
docs/api.md says; the docstring listed neither.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new user found maturity only through the CHANGELOG or the reward page's body.
The "Read in this order" entry for the reward page names it, and the "By
situation" table has a row for it: a brain whose actor's steps fall as it uses
its plasticity and whose store moves on surprises.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
outer(k^2 / max(k^2), e^2 - R) took a matrix as its second argument, where the
store section's outer takes two vectors. The update reads
R[i, a] += 0.05 * k_i^2 / max(k^2) * (e_a^2 - R[i, a]), as _gate computes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
grow(maturity=True) answered "maturity is not a part; retune changes a living
brain's settings", and retune refuses maturity as a construction setting, so
each refusal pointed at the other. maturity_scale or maturity_recovery on a
brain without maturity did the same. grow now refuses them as it refuses
arousal: "maturity is a construction choice grow does not add; compose the
brain with maturity=True". A mature brain's maturity_scale still reads "not a
part; retune changes ...", which retune does. grow.md and api.md list maturity
with arousal and the episodic memory. The two new cases of
test_grow_refuses_with_the_reason fail without the change. Founder parity
0 of 24, the mature battery 0 of 926; test_grow and test_documentation pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…253, #169)

Version 0.94.0 in pyproject.toml, cadence.__version__ and the install pins of the README and
the docs; CHANGELOG's Unreleased section becomes 0.94.0 with the readings of the confirmation
declared before its run: seven of #169's eight core gates held, the fight statistics failed by
one calm run, so #169 stays open and maturity ships as an opt-in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@muellerberndt muellerberndt added this to the v0.94.0 milestone Nov 5, 2026
@muellerberndt
muellerberndt merged commit 2a32a98 into main Nov 5, 2026
9 checks passed
@muellerberndt
muellerberndt deleted the release-0.94 branch November 5, 2026 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant