Neuroplasticity in KAOS
The library starts learning from itself

A seven-chapter story about how a passive filing cabinet turned into a self-organizing graph — and how an honest benchmark forced us to throw away the first architecture and build it right. Every number in this post is measured, reproducible, and committed to the repo next to the docs.

The library wasn't wrong. It was just dead. Skills accumulated, memory accumulated, shared log entries piled up, but none of it learned anything about which entries mattered, which ones worked, which ones always showed up together. A filing cabinet, not a brain. This post is the story of how we fixed that.
KAOS plasticity demo: BM25 picks the wrong skill, agents accumulate feedback, weighted search flips the ranking, failures get categorised.
6 scenes, ~45 seconds. Real CLI. No edits, no cuts. Every command in this GIF is reproducible via uv run kaos skills search --rank weighted and kaos dream failures.

01The shape we started from

Before v0.8.0, KAOS already had three persistence surfaces agents could write into: the skill library, cross-agent memory, and the shared log. Each one was well-designed on its own — FTS5 search, BM25 ranking, append-only durability. Each one worked.

But if you stepped back and asked what does the whole library know that none of its individual pieces know, the answer was: nothing. The library was a set of tables you wrote into. It had zero opinions about which of its own contents were good, which were noise, which were always used together. Nothing ranked skills by whether they actually worked. Nothing noticed that two skills were always applied in the same session. Nothing caught that the same error kept coming back across agents.

A brain isn't like that. A brain is a graph that rewires itself every time a neuron fires. What fires together wires together. What goes unused atrophies. What recurs gets reinforced. Crucially, that rewiring happens continuously, as a side effect of normal operation — no one runs a scheduled batch job to do it.

We wanted that layer.


02First question: does weighted ranking even help?

The naive move was to just use the counters we already had. Every skill already tracked use_count and success_count — we just never read them at query time. So: what if skills.search() ranked results by success rate multiplied by recency decay, not pure BM25?

Before writing any code: would that actually help? Not theoretically. Empirically. On a benchmark.

I built one: 10 retrieval queries, 20 skills arranged in twin pairs that share vocabulary so BM25 alone can't reliably disambiguate. Each query has one ground-truth correct skill and a plausible distractor. Then 80 training episodes with epsilon-greedy pick (ε=0.25, seed=42) — same seed for both runs. Zero planted outcomes. No pre-engineered winners. The only difference between runs is whether the rank mode is feedback-sensitive.

Figure 1 · Training accuracy curves
BM25 vs. plasticity-weighted retrieval across 80 training episodes, then a final deterministic measurement per query.
100% 80% 60% 40% 20% 0 20 40 60 80 final episodes 80% 90% final (no exploration)
bm25 (control) weighted (plasticity)
Weighted's training-phase accuracy is lower than BM25's because exploration keeps demoting failing skills — forcing new candidates. The final deterministic measurement is where the learning lands.

BM25 plateaus at 80%. Weighted reaches 90%. That's a real delta — not cherry-picked, not synthetic, reproducible by running uv run python demo_neuroplasticity_bench/run.py.

The critical honest framing: a benchmark where BM25 already hits 100% would show zero gain. A workload where agents never report outcomes would show zero gain. Plasticity pays off when your workload has disambiguation signal. It is not a free lunch.

03But weighted search alone is too shallow

A +10pp win on a retrieval benchmark is nice. It's also just the tip. The bigger question: what if the library understood relationships, not just individual entries?

This is where Hebbian learning comes in. In biology: neurons that fire together wire together. In KAOS: skills that get used together in the same agent session should form an edge in a graph; memories that get retrieved together should link; and cross-modal — a skill the agent used should connect to every memory it retrieved.

Once you have that graph, skill_search("fraud") doesn't just return skills that mention fraud — it also surfaces feast-cold-start-fix because historical sessions that used fraud-detection skills also retrieved that memory entry. The graph emerges from usage. Nobody curates it by hand.

Same for failures. A fingerprint isn't just a stack-trace lookup — it carries a category (transient / config / code / infra / unknown), a root cause, and a suggested action. And critically: we track whether the suggested fix actually worked. A "known fix" that keeps failing after 5 attempts auto-downgrades so future agents stop applying broken suggestions. Plasticity applied to the fix itself.

Add one more layer: systemic alerts. If five agents in two minutes all hit "Connection refused: localhost:8000", the answer isn't "retry with backoff". The answer is your vLLM endpoint is down; stop spawning more agents. A sliding-window counter detects the pattern and raises an alert. Agents should check for active alerts before spawning — the whole point of failure intelligence is to prevent cascading thrash.


04How it costs nothing — the story of an embarrassing first try

Clever designs mean nothing if they're too slow. So I built a third benchmark: a microbenchmark that times every inline hook with plasticity ON and OFF, reports the delta.

The first implementation (v0.8.0) built the Hebbian graph synchronously. Every record_outcome triggered a query for every other skill the agent had used, then upserted an association edge for each pair. On a 100-skill library, that's N writes per outcome plus an extra COMMIT. On Windows where fsync is ~30 ms, that added up.

Figure 2 · Inline hook overhead (per-op, p50)
The first architecture was a mistake. The benchmark caught it. The rewrite fixes it.
v0.8.0 inline upserts 210 ms v0.8.1 batched 15 µs 14,000× faster after the architecture rewrite (`record_outcome` overhead, p50, 100-skill library)
The benchmark committed in the repo rejected the first build. Honest failures are how you get to right answers.

The fix was architectural, not an optimisation. Inline hooks stopped building the Hebbian graph. The raw telemetry (skill_uses, memory_hits) still writes in the caller's existing transaction — zero extra fsync. Graph construction moved to a single executemany that fires at agent completion.

This is actually how biological sleep consolidation works. Synaptic weights update continuously as neurons fire, but the structural rewiring — dendritic pruning, synaptic consolidation — happens offline, at scale, in bursts. The inline path is cheap; the structural path is batched and runs at natural checkpoints.

Figure 3 · Per-op overhead breakdown (v0.8.1)
200 ops, 100-skill library, plasticity ON vs OFF. Overhead is the delta.
record_outcome memory_search agent_complete skill outcome record with record_hits agent termination +15 µs essentially free ~0 (noise) −48 µs (within measurement noise) +872 µs batched graph rebuild + fingerprint 0 250 µs 500 µs 750 µs 1 ms
The 872 µs on agent_complete is the batched graph rebuild — a single executemany with every association edge for the session. One extra fsync at completion is invisible next to the LLM call that preceded it.

05Failures aren't patterns — they're diagnoses

The old failure feature was a grep index: "we've seen this stack trace before, here's the same stack trace from last time". Useful. Shallow.

v0.8.1 turns fingerprints into a triage system. Eight built-in heuristic diagnosers — pure Python, no LLM calls — cover the high-volume cases:

Figure 4 · Planted failures and their automatic categorisation
Seven realistic errors planted in demo_failure_intelligence_bench/, each diagnosed by heuristic alone.
transient 1 · rate limit config 1 · auth 401 code 2 · KeyError, AttrError infra 3 · vLLM down, disk full, DNS ⚠ Systemic alert fires at ≥ 3 agents / 120s Wave of 4 agents hit `ConnectionRefusedError: localhost:8000` inside the window. → Agents should refuse to spawn until resolved. ✂ Fix auto-downgrades after 5 failed attempts (success rate < 50%) Plasticity applied to the fix itself. Broken suggestions fade; working ones persist.
60/60 validations pass in the committed failure-intelligence scenario.

Each category maps to a different action. A rate limit wants backoff. A 401 wants human action. A KeyError wants a code fix. A local ConnectionRefused wants the service started. KAOS can't fix any of them itself — but it can tell you which bucket you're in without you reading the stack trace, and if many agents hit the same thing at once, it can stop the cascade.


06It all ties together

The story I was telling in the first five chapters sounds like a bag of features — weighted search, Hebbian graph, failure diagnosis, systemic alerts. It's not a bag. It's a loop. Here's where the new layer sits in the stack:

KAOS v0.8.1 architecture — 7 layers. The new Neuroplasticity layer (layer 4, pink) sits between Meta-Harness and the Knowledge Surfaces, with inline hooks firing into three storage surfaces and batched consolidation running at agent-completion.
Layer 4 (pink) is new in v0.8.1 — inline hooks on top, batched consolidation below, feedback loop back up to the Knowledge Surfaces.
Every agent operation feeds plasticity. Every skill outcome writes a skill_uses row. Every memory retrieval writes a memory_hits row. Every agent completion upserts an episode_signals row and rebuilds the association graph for that session. Every failure triggers diagnosis and checks for systemic patterns. Every 100 completions triggers consolidation that promotes hot memory to skills and prunes cold skills.

The feedback loop is closed. What flows out of the library as a search result came from what agents did before. What flows in as telemetry immediately starts reshaping future search results. There's no manual curation step. There's no "once a week, run the audit". It happens continuously, as a side effect of normal use.

That's the difference between a filing cabinet and a brain. The cabinet is passive — you put things in, you retrieve them, the cabinet has no opinion. The brain is a graph that changes its own structure in response to every signal that passes through it. KAOS v0.8.1 is the first version where the library is the second thing, not the first.


07The reality, measured

+10pp
retrieval accuracy
+15 µs
hot-path overhead p50
181
scenario validations passing

All three numbers live next to their source benchmarks in the repo. Reproducible. Auditable.

What I would not claim

What's next

M4 is the active feedback loop: the router picks models by per-task-class success history; the intake step (kaos run --ask) prunes questions against the cluster context the graph has learned; the wave coordinator reads the association graph to bundle skills per wave; the meta-harness proposer seeds its prompts with dream-identified strong tool sequences. Each one uses the library's opinions to change how agents are spawned, not just what they retrieve. Separate decision. Ship M4 when there's clear demand.

Until then: git pull origin main && uv sync && uv run python demo_neuroplasticity_bench/run.py. See the 80→90% in your own terminal.

Read the paper. A longer-form whitepaper with simulated adversarial peer review from systems, ML/retrieval, and cognitive science perspectives is at /papers/kaos-neuroplasticity-whitepaper.html.
Every number in this post is backed by a committed benchmark.
+10pp retrieval · +15 µs hot-path overhead · 45/45 dream use case · 60/60 failure intelligence · 76/76 ARC-AGI-3 · 423 unit tests. All reproducible. Run them yourself.