Offline Consolidation · module 2 of 9 · Time, Scale, Multiplicity, Trust · lesson 52 of 84 · ~55 min
$
uv run python curriculum/advanced/reflection-and-insight/lab/lab.py
Reflection and Insight¶
In one line. Three well-supported, fully traceable derived beliefs — and every way of putting them in the store makes the answer worse.
Where this sits¶
Stage: evolve · Level: advanced · ~55 min
You need first: Background Job Mechanics
Concepts assumed: Slot · Salience · Element Cost
This unlocks: Promotion as a Release
The problem¶
The obvious generator is similarity: find beliefs that look related and synthesise. I3 already measured that this signal cannot separate a refinement from a corroboration from a contradiction — 0.669, 0.505, 0.439, with no threshold between them — which is why evolve/promote.py promotes nothing.
Point it at reflection and it fails harder. 20 candidate pairs, ranked:
0.557 Priya drinks tea + Priya drinks three coffees a day
0.478 Priya drinks tea + Priya works at Calico Systems
0.449 Priya is a staff engineer + Priya prefers shorter answers
0.433 Priya mostly does pipeline work + Priya is pescatarian
The top-scoring insight candidate is a contradiction — the coffee reversal I4 already arbitrated. Second pairs a drink with an employer, third a job title with a formatting preference. 14 of 20 pair facts from unrelated slots, and the first genuine dietary relation sits sixth.
Why this isn't RAG¶
Summarising a corpus is a read-time convenience: generate a summary, show it, throw it away. If it is wrong the user sees a bad summary of documents they can go and check.
A derived belief is written back. It joins the store as a first-class memory, gets ranked, gets retrieved, and gets stated as though the user had said it — with no document behind it to check against. Reflection is the one stage that manufactures beliefs, so it is the one stage where an unsupported claim enters looking exactly like a supported one.
Mechanism¶
Generate from structure. Live beliefs sharing a SLOT — the same table that serves conflict detection, ranking and now scheduling. Four groups have two or more:
| slot | live | |
|---|---|---|
| diet | 4 | fish, no meat, pescatarian, gluten |
| beverage | 2 | tea, three coffees |
| employer | 2 | Calico Systems, staff engineer |
| occupation_other | 2 | Samira is a charge nurse; Sam still works nights |
Compose, do not write. The insight is a template over its members, and every member id goes in derived_from so A1's cascade can retire it the moment a source goes. A composed belief can be checked against its sources; a generated sentence has no way back to the evidence.
The suppression that matters is the fourth row. Slots are keyed on what is claimed, not on who it is claimed about, so occupation_other groups two facts about Priya's partner. Composing them writes a belief that Priya works night shifts as a charge nurse. Any third-party entity disqualifies the group — and it is the only rule here that catches anything, because retired members are not a reason to refuse: they simply are not members, so the slot's history is untouched and temporal-questions still answers it.
Three groups survive. All three are correct.
And they are invisible¶
derived beliefs, as created: tier=working
eligible pool: 18 of 40
insights in the pool: 0 of 3
retrievable_only keeps LONG_TERM whenever any exists, and nothing has scored the new beliefs — reflection ran after the decay pass. A belief a background job derives cannot be read until something scores it, which is the same ordering trap as anchoring before reconciliation, in a different stage.
Score them, and it gets worse¶
Run the scoring pass and the composites are the highest-salience beliefs in the store — the diet composite at 0.899, the highest of anything. They take ranks 1 and 2:
1. 2.690 employer: Priya works at Calico Systems; Priya is a staff engineer
2. 2.939 diet: Priya eats fish; Priya does not eat meat; Priya is pescatarian; ...
3. 2.899 Priya does not eat meat
4. 2.562 Priya works at Calico Systems
5. 2.415 Priya is a staff engineer
Ranks 3–5 are the composites' own members. The insight joined its sources instead of replacing them, so the context now carries the diet twice.
| policy | lowest passing budget | store |
|---|---|---|
| no reflection | 51 | 37 |
| insights added alongside | 55 | 40 |
| insights retire their sources | 56 | 40 |
(slot-value reported 52 because it swept discrete budgets and never tried 51. Sweeping every value gives 51, and the finer metric is what this lesson needs — a change that costs five tokens of headroom is invisible to a check at one budget.)
Both policies are worse, and retiring the sources is the worse of the two. The reason is the one I8 spent a module on: the packer selects memories. It can take three of the four diet facts and drop the one the question does not need. It cannot take three quarters of a composite. Composition destroys the packer's ability to drop things — the property that made 51 reachable at all.
flowchart LR
subgraph SRC["the diet facts, as memories"]
direction LR
MA["eats fish"]
MB["no meat"]
MC["pescatarian"]
MD["gluten"]
end
CI["<b>the same facts, as one composite</b><br/><i>every member id in derived_from</i>"]
MA --> CI
MB --> CI
MC --> CI
MD --> CI
SRC --> PA{"packer, tight budget"}
CI --> PB{"packer, tight budget"}
PA -->|"drop the one the<br/>question does not need"| OK["<b>the answer survives</b>"]
BAD["<b>composition removes the choice</b><br/><i>and the insight joined its sources,<br/>so the context carries diet twice</i>"]:::bad
PB -->|"whole, or not at all"| BAD
style CI fill:#f9e79f,stroke:#b7950b
style OK fill:#aed6f1,stroke:#2874a6,stroke-width:2px
classDef bad fill:#f5b7b1,stroke:#c0392b,stroke-dasharray: 4
Design decisions¶
So reflection ships unwired, like promote() before it, and for the same reason: the measurement says the signal does not carry the decision. Keeping it as code with a passing test makes the deferral visible and re-testable; deleting it makes the next person rediscover it.
When would it pay? When retrieval is the bottleneck rather than the budget — a store large enough that four diet facts never rank together, where one composite that always ranks is better than four atoms that individually do not. That is a property of scale, and this corpus is 37 memories. scaling-the-store is where the condition gets measured rather than asserted.
Why not compose only what the question needs, at read time? That is a good idea and it is not reflection — nothing is written back, nothing is derived, and there is no derived_from to maintain. It belongs in assemble, and it would not have this lesson's failure mode because a read-time composite is never in competition with its own sources.
Why keep beverage? Tea and three coffees a day are both live and both true; the contradiction was does not drink coffee, which I4 retired. The composite is correct. It is also useless, which is the point — correctness was never the thing in doubt.
Lab¶
You'll implement: groups, _refuse, and compose.
Run:
uv run python curriculum/advanced/reflection-and-insight/lab/lab.py
Expected output: the similarity generator's 20 candidates with a contradiction on top, then 7 slots examined and 3 derived with occupation_other refused as third-party, then 0 of 3 eligible before scoring, and the budget table — 51 / 55 / 56.
Stretch: drop the third-party rule and read what gets written. occupation_other: Samira is a charge nurse; Sam still works nights enters the store as a belief about Priya, scored, ranked, and indistinguishable from something she said. The rule that catches nothing on most corpora is the one you cannot omit.
What this adds to the capstone¶
memlab.sleep.reflect — Refusal, Group, groups, compose, reflect. Deliberately not wired into any pipeline; @I* and @A1 are unmoved because nothing calls it.
Failure modes¶
| Symptom | Cause | How to detect | Mitigation |
|---|---|---|---|
| Insights synthesise contradictions | Candidates generated by similarity | Rank the candidates; read the top one | Group by slot |
| A belief about someone else, about the user | Slot keys what is claimed, not who | Check entities on every member |
Refuse third-party groups |
| Derived beliefs never retrieved | Created after the scoring pass | Count them in the eligible pool | Score before reading |
| Budget regresses after reflection | Composite joins its sources | Measure the lowest passing budget | Measure before wiring |
| An insight cannot be traced | Generated rather than composed | Follow derived_from to sources |
Compose from members |
Check yourself¶
The three derived beliefs are correct and fully traceable. Why not ship them?
Because correctness was never in question. They make the lowest passing budget worse under both policies — 51 to 55 joined, 51 to 56 replacing — and a belief that degrades the answer is not improved by being true. The measurement to run before wiring a stage is not "is the output right" but "is the system better".
Why does retiring the sources make it worse than leaving them?
Because a composite is one memory and the packer selects memories. Four atomic diet facts let it keep three and drop the one the question does not need; a single composite is all-or-nothing and costs more than the subset would. Retiring the atoms removes the option that made 51 reachable.
The third-party rule fires on exactly one group. Is it worth having?
It is the only rule that fires at all, and what it prevents is the worst failure in this module: a belief that Priya works night shifts as a charge nurse, written in her own store, ranked, and retrievable with nothing to distinguish it from something she said. A rule's value is the cost of the case it catches, not how often it fires.
Connections¶
Stage: evolve · Level: advanced · ~55 min
You need first: Background Job Mechanics
Concepts assumed: Slot · Salience · Element Cost
This unlocks: Promotion as a Release