Better memory made it worse
at saying “I don’t know”.
A ~2B vision-capable model was paired with an external memory system and post-trained three ways: LoRA SFT, DPO, then on-policy distillation. Retrieval worked — answerable recall went from 0.16% to 15.13% — and abstention on unanswerable questions fell from 13.75% to 8.75%. The model learned to answer more and to admit ignorance less, in the same step. Restoring the abstention signal took UAR to 96.25% and drove false abstention on answerable questions to 69.2%. The rebalanced final system sits at 70.0% / 32.1%.
Everything here is measured on PMB — 688 adversarial probes across 8 personas and 8 categories, 608 answerable and 80 unanswerable — by a single seed, a single run, and an LLM judge. Nothing on this page is replicated. The site prints only numbers a committed file holds; what that leaves out is listed under what is not claimed.
Can a ~2B model hold a persona across years of conversation — and know when to stop?
Small models cannot carry a long history the way a large context window can: they forget, invent memories, or answer confidently when they should not. The question was how much of that gap closes without scaling parameters — by pairing the model with a retrieval memory, post-training it, and measuring the whole thing on a benchmark built to catch abstention failures and false memories, not just recall.
Pinned at commit 3e22461f65e89153144f8adb70e3b8c2cc9845a7 in the two SFT configs — 4.63B parameters as reported by llama-bench (errata E6: the parameter count is restated from run logs and is not recomputable from this repository); ~2B effective. Multimodal: the processor loads as Gemma4Processor.
Dense embeddings (intfloat/multilingual-e5-small, 384-dim) fused with BM25 by reciprocal rank fusion, into a token-budgeted context builder. Memory extracted from the personas’ real turns: 505 claims extracted, 503 accepted, 8 stores. docs/day3_memory_results.md
Rank 16, alpha 32, all-linear. SFT v1: 2,480 examples, 2 epochs, 128 steps. DPO v1_scale: 2,049 preference pairs, 1 epoch, 257 steps. Distillation v1: 2,008 prompts, 125 steps, from gemma-4-E4B-it as teacher. Dataset counts are line counts of the committed JSONL, not values quoted from a datasheet; epochs and step counts come from docs/proper_scale_results.md, docs/dpo_results.md and docs/distillation_results.md.
2 of 6 training configs pin the base model by SHA; the rest use main. No result in Study 001 was run twice, and no human reviewed any output.
The ladder below is the whole study in one chart. A is the raw model, B adds SFT with no memory, D adds memory with no SFT, E adds both, and E-distill adds on-policy distillation. Each rung isolates one change — which is exactly what the audit found missing from the first write-ups of the distillation result (errata E2).
Six rungs, one benchmark, no borrowed cells.
Every row is the full PMB: the same 688 probes, scored the same way, each read from its own committed metrics.json.
Memory buys recall. It does not buy the ability to say “I don’t know”.
The point of the ladder is rung D. Adding retrieval to the raw model moved recall by +15.0pp and moved abstention −5.0pp in the wrong direction (13.75% → 8.75%): better recall and more confident fabrication arrived together. Rung E is what it costs to buy abstention back — UAR 33.75% at v0 scale, 70.0% at v1. pra_strict is 0.0% at every rung and is not plotted.
source: results/v0.1/{A_raw,B_sft,D_memory,E_sft_memory}/metrics.json · results/v1_scale/{E_sft_memory,E_distill}/metrics.json, at tag study-001
| System | pra_lenient | UAR | pra_strict | UAR 95% bootstrap CI | n | Read from |
|---|---|---|---|---|---|---|
| A · raw model, no memory | 0.16% | 13.75% | 0.0% | 6.25% – 21.25% | 688 | results/v0.1/A_raw/metrics.json |
| B (v0) · LoRA SFT, no memory | 0.16% | 16.25% | 0.0% | 8.75% – 25.0% | 688 | results/v0.1/B_sft/metrics.json |
| D · raw + hybrid retrieval memory (k=8) | 15.13% | 8.75% | 0.0% | 3.75% – 15.0% | 688 | results/v0.1/D_memory/metrics.json |
| E (v0) · LoRA SFT + memory | 17.76% | 33.75% | 0.49% | 23.75% – 43.78% | 688 | results/v0.1/E_sft_memory/metrics.json |
| B (v1) · LoRA SFT, no memory | 0.16% | 21.25% stale | 0.0% | 12.5% – 30.0% | 688 | results/v1_scale/B_sft/metrics.json |
| E (v1) · LoRA SFT + memory, rebalanced | 15.3% | 70.0% | 0.0% | 60.0% – 80.0% | 688 | results/v1_scale/E_sft_memory/metrics.json |
| E-distill · + on-policy distillation | 18.59% | 71.25% | 0.0% | 61.25% – 81.25% | 688 | results/v1_scale/E_distill/metrics.json |
Every row is the whole benchmark (688 probes). B (v1) is stale in the artifact: results/v1_scale/B_sft/metrics.json holds UAR 21.25% (20/80 before the abstention detector was fixed), while the corrected value in docs/proper_scale_results.md and docs/distillation_results.md is 25.0%. The artifact is printed as it is and excluded from the chart above. pra_strict is 0.0% in every row because acceptable_alternatives is empty in 688/688 probes.
The DPO checkpoint has no row here, and that is the point. No full-PMB measurement of any DPO checkpoint exists in results/. DPO appears once, in a pairwise comparison — printed two sections below — and any pra_lenient or UAR figure attributed to it is unsupported by the artifacts (errata E1).
| Training corpus | Train | Val | Total | Counted from |
|---|---|---|---|---|
| data/sft/v0 | 202 | 23 | 225 | data/sft/v0/train.jsonl and val.jsonl |
| data/sft/v1 | 2,232 | 248 | 2,480 | data/sft/v1/train.jsonl and val.jsonl |
| data/dpo/v0 | 200 | 23 | 223 | data/dpo/v0/train.jsonl and val.jsonl |
| data/dpo/v1_scale | 2,049 | 228 | 2,277 | data/dpo/v1_scale/train.jsonl and val.jsonl |
| data/distill/v1 | 2,008 | 224 | 2,232 | data/distill/v1/train.jsonl and val.jsonl |
Line counts of the committed corpora, generated by scripts/build-study-data.mjs so no count on this page is typed. The audit found five separate stale hand-written counts in the repository’s own docs — 40 personas against 8, 451 tests against 473, 21 bugs against 27, “~15k lines” against 66,383, two GGUF size sets in one file — which is why guardrail G4 says counts are generated, not typed.
Better recall and worse abstention arrived together.
This is the result the study is named for, and it is a tradeoff, not a win. It was found twice — once as a regression, once as an over-correction — and the final configuration is a compromise between them.
Teaching it to say “I don’t know” made it say it to questions it could answer.
The original v1_scale run: ~227 intended abstention examples had collapsed to 1 (model_quirks #16).
Abstention examples restored, ratios untouched: the model hedges on most answerable questions.
Abstention 10%→5% and irrelevant 15%→6%, four paraphrases per template instead of one.
This is the finding the study is named for. Restoring the abstention signal the dedup bug had deleted took UAR from 16.25% to 96.25% — and took false-abstention from 9.9% to 69.2%, which is not a fix but a swap. Rebalancing the ratios and diversifying the templates landed at 70.0% UAR with false-abstention cut to 32.1% — still 224% higher than where the broken run started at 9.9%. Detector counts as committed: 16.25% (13/80) · 9.9% (60/608) · — | 96.25% (77/80) · 69.2% (421/608) · — | 25.0% (20/80) · 39.8% (242/608) · —. None of these three rates has a confidence interval.
source: docs/proper_scale_results.md §“The full three-point trajectory (all rescored with the fixed detector)” · final point’s UAR and pra_lenient also in results/v1_scale/E_sft_memory/metrics.json
Two of the three points in that trajectory are numbers in a document, not in a JSON artifact. The runs behind them had their metrics.json files overwritten by the final rebalanced run, so the committed record of the first two is docs/proper_scale_results.md itself. This page parses that table rather than retyping it, and says where each figure lives (guardrail G7). The honest description of the final state is: 32.1% false abstention is a real cost, above where the broken run started at 9.9%, and a genuinely optimal ratio was never searched for.
How much memory to retrieve is not a free parameter.
H10 predicted a non-monotonic relationship between the number of retrieved memories and answer quality, peaking at four to eight. The sweep ran k over 0, 2, 4, 8, 16 on the retrieval-only pipeline.
Recall peaks at 8 memories and falls at 16.
A clear inverted U, peaking exactly at k=8 — which is why D runs at k=8. Read this chart for its shape, not its level. It ran on 120 probes, not all 688, so its k=8 point (17.14%) is not comparable with D’s full-set 15.13%, and its UAR at k=4 and k=16 is 6.67% on the subsample’s unanswerable probes — one or two questions. The column axis is set to 20% so the shape is legible; the two bars charts above and below use 0–100%.
source: results/v0.1/ksweep/summary.json · probe count from results/v0.1/ksweep/k8/metrics.json
Read the shape, not the level. The peak is at k=8 (17.14% on 120 probes) and falls to 14.29% at k=16 — which is why every other system on this page runs at k=8. But 120 of 688 probes is a stratified subsample, so the numbers are not comparable with the full-set rows above, and the UAR points at k=4 and k=16 are 6.67% on eighty unanswerable probes in the subsample: one or two questions. The sweep also inherits the retrieval-only pipeline’s unmeasured prompt framing, which the project describes as “prompt engineering under time pressure, not a principled context-format ablation”.
The regression was two bugs and one real tradeoff.
Scaling the training data tenfold initially looked like it had made the model worse at abstaining. It had not. The apparent regression was two independent defects compounding, and once both were fixed the real third problem — over-correction — became visible for the first time.
- A dedup step was collapsing 227 intended examples to 1.model_quirks #16
The data generator deduped by exact assistant-response text. Abstention and irrelevant-retrieval examples use a fixed template response by design, so after the first one every subsequent example looked like a duplicate and was dropped — in both SFT v0 and SFT v1. The datasheets recorded the casualty honestly:
"abstention": 1, "irrelevant_retrieval": 1. Fixed by deduping only the memory-relevant category by response text. - The eval harness could not recognise the right answer.model_quirks #17
After the dedup fix, the model began producing the exact abstention phrase it had been trained on — and the rule-based detector’s phrase list did not contain it, so correct abstention scored as non-abstention. UAR appeared to fall to 2.5%. Rescoring the saved responses with the phrase list fixed gave 96.25%.
- Over-correction, which is the actual finding.the finding
With the intended proportion of templated abstention and irrelevant-retrieval examples restored (~17% of the corpus), the model over-generalises the canned hedge as a low-risk default — 69.2% of answerable probes. The project’s own explanation is that short fixed-string targets are easy wins for the loss function next to generating a correct, specific, longer answer. Reducing the ratios and adding four paraphrases per template recovered most of it; it did not remove the cost.
The lesson the project wrote down as a guardrail. A rule-based grader silently drifts out of sync with the training-data phrasing it is meant to detect, and a dedup step applied to templated data is a silent dataset-size bug. Both were caught only because a number looked wrong in a specific direction — which is why the project keeps a “suspiciously good or bad is a bug signal” discipline.
Distillation improved recall. DPO improved preference. Both, once.
Negative results are published here, so the positive ones have to be sized honestly too. Three results survive the audit, and none of them is replicated.
Pairwise is the only thing the DPO checkpoint has.
Win-rate gaps are computed from win rates rounded to one decimal, which is how the documents print them (45.7% − 21.0% = 24.7pp). 105 stratified answerable probes, dual-order judge. 53.3% ties: essentially a coin flip. The largest pairwise gap in the project. This is the DPO checkpoint’s only evaluation — it has no full-PMB metrics. The distillation-specific measurement, because both sides contain the DPO stage. Read 7.6pp as the distillation-specific gap — that comparison is the number to cite for H23; the +3.3pp full-set difference between E (v1) and E-distill spans the DPO stage as well, because the distilled model was trained from the DPO output.
source: results/v0.1/C_vs_E_pairwise/summary.json · results/v1_scale/C_vs_E_pairwise/summary.json · results/v1_scale/C_vs_F_distill_pairwise/summary.json
Distillation improved recall without breaking the persona.
Both measures favour the distilled model, and the stylometric drift between pre- and post-distillation is 0.8725 (1.0 would be identical style) — so the win is not a style change in disguise. The judge measure is a 60-response sample of 688, single seed, and the judge is the same self-reported teacher model the rest of the pipeline used.
source: results/v1_scale/pcs_judge_analysis/summary.json · results/v1_scale/pcs_stylometric_analysis/summary.json
A training curve that looked broken produced a working model. The distillation run’s loss was flat, its gradient norm unstable and 98.75% of completions hit the 128-token cap — yet real evaluation showed a clean improvement. That is written down as a case where the evaluation harness was worth more than the training metrics. H6’s predicted effect size (+0.5 to +1.0 on a persona-consistency score) was never measured in its own units: MUR and the original PCS scale are both absent from every committed artifact.
29 claims the artifacts did not support.
Audited at commit ae75ca9. Every one of the seven evaluated runs’ metrics.json was recomputed from its own saved raw.jsonl and matched exactly — the measurements are sound. All 29 findings are about the claims made around them: 5 HIGH, 12 MED, 12 LOW, all public, and 25 resolved, 2 partial, 2 still open.
Those figures belong to SFT-v1 + memory. The tracked artifact results/v1_scale/E_sft_memory/metrics.json names its system E_sft_memory_v1 and records UAR 70.0% and pra_lenient 15.3%. No full-PMB measurement of the DPO checkpoint exists anywhere in results/ — dpo-v1-scale appears only in the pairwise comparison, and the published cards have not been re-pushed.
reports/ERRATA.md §E1 · findings.json #1The distilled checkpoint was trained from the DPO output, so that delta spans DPO as well as distillation. The isolating measurement holds the DPO stage constant on both sides: F wins 38.1% against C's 30.5%, a +7.6pp gap. Cite that for H23.
reports/ERRATA.md §E2 · findings.json #2PMB has 8 personas (86 probes each). 40 is the count of SFT training personas in data/benchmarks/sft_personas_v1/, not the benchmark. Counted from data/benchmarks/pmb_v0_full/probes.jsonl: 688 probes, 608 answerable / 80 unanswerable.
reports/ERRATA.md §E3 · findings.json #3True for 2 of 6 configs. 4 still use "main" (dpo.yaml, dpo_v1_scale.yaml, dpo_v1_more_epochs.yaml, distill_v1.yaml), and distill_v1.yaml also uses "main" for its teacher. Runs launched from those configs are not revision-pinned and should not be described as reproducible until the SHAs are recorded.
reports/ERRATA.md §E4 · findings.json #4"Every dataset directory has a hash.txt" and, in the audit's first pass, "all six hash.txt files match once CRLF is normalised to LF".
The six split in two. The four SFT/DPO hashes (data/sft/v0, data/sft/v1, data/dpo/v0, data/dpo/v1_scale) DO match, under sha256 over concatenated LF-normalised sorted *.jsonl. The four benchmark hashes do NOT match under any of thirteen tested algorithms: path as str() vs as_posix(); data as raw bytes, text, or LF-normalised; sha256 over path+data, data-only, or probes.jsonl-only; walk order vs name order; DATASHEET.md included or excluded. The mismatch is present on a clean git worktree of ae75ca9, the commit that introduced the files, so it is not caused by the audit's edits. Cause undetermined. The first-pass sentence generalised from the four that passed to all six.
Found by verifying the freeze against a clean checkout — not by reading a document. RESOLVED: All nine dataset hashes were recomputed on 2026-09-13 with the algorithm now documented in docs/reproduction.md, and all nine verify. The four benchmark corpora and the four SFT/DPO corpora use their respective generators' algorithms; data/distill/v1/ gained the hash.txt it never had. A regression test (test_dataset_hashes.py) now recomputes every hash on each CI run, so this cannot rot silently again -- which is the failure mode that let it go unnoticed. The original cause of the four mismatched values is still undetermined and is recorded as such in ERRATA E29. The fix landed in scripts/recompute_hashes.py (new), tests/unit/test_dataset_hashes.py (new), docs/reproduction.md, README.md, docs/GUARDRAILS.md G9/G9b, reports/ERRATA.md E29. This page counts 9 hash.txt files at the tag (4 benchmark, 5 training) rather than quoting a count, which is what finding #29 was about.
reports/ERRATA.md §E29 · findings.json #29E29 is the one finding the audit did not reach by reading. It came from verifying the freeze against a clean checkout of the commit that introduced the files — and what it found was that four of the six integrity pins the repository published had never verified under any algorithm the auditor tried (the ledger’s own wording is quoted in the card above). The cause is still undetermined and is recorded as such; the fix was to recompute all 9 corpora with a documented algorithm, create the one pin that was missing, and add a regression test so a hash cannot rot silently again. The page counts the 9 pins at the tag — 4 benchmark and 5 training — rather than quoting a number, because a typed count is what went stale in the first place.
The rest of the record
| Finding | Correction, as published |
|---|---|
| E5/E6 | Two mutually inconsistent GGUF size sets are published, and every quantization number — sizes, throughput, perplexity, even “4.63B params” — is restated from run logs that are not in the repository. *.gguf is gitignored and results/ holds no quantization output, so nothing is re-derivable from a fresh clone. The page therefore prints no quantization number at all. |
| E7/E18 | The reported quantization floor understated the verified result: Q2_K is broken for this model family (reproduced on two independently trained checkpoints) and one card called Q3_K_M the safe minimum while its own table named Q3_K_S. Q3_K_S is the smallest verified-coherent level; the checks described are manual llama-cli runs, with no automated quality check evidenced. |
| E9/E10 | CITATION.cff was stale on four of its fields — title, version, author and URL (the cited GitHub URL does not exist) — and the card template declared the wrong licence for all seven cards it generated. |
| E11/E12 | Contamination status was asserted in both directions at once: the SFT datasheets said unchecked, the distill datasheet said checked clean and cited an SFT datasheet that said the opposite. The check had been run and the data was clean. Separately, both SFT datasheets cited seeds that appear nowhere in the repository; persona generation was not reproducible as documented. |
| E13 | The DPO v1_scale pairs were not built from the final SFT v1: 2,263/2,277 user turns match but 0/2,277 system texts match, because SFT v1’s dedup fix and ratio rebalance landed after the pairs were generated. The cause was never root-caused. |
| E14 | Stale counts in the README: “451 passing tests” against 473 `def test_` functions, and “21 bugs” against docs/model_quirks.md numbering 1 through 27. |
| E15 | The hardware claim conflated two machines. LoRA training ran on a rented workstation GPU; the quantization box had no system-wide CUDA toolkit, llama.cpp was built CPU-only, and the distill-v1 GGUF build ran on Modal. |
| E16/E26 | The stated reproduction chain contained commands that cannot run: `make figures` and the `paper` target referenced files that do not exist, and a results file was referred to as “if it exists”. Figure regeneration is unavailable, and the doc now says so. |
| E19/E20/E27/E28 | Two training-persona datasheets and a fixture smoke test were all titled the same thing as the benchmark; the benchmark datasheet contradicted itself about its own persona count; the imatrix calibration corpus was described as “~15k lines” when it is 66,383; and docs/proper_scale_results.md’s headline table reported a superseded run while the README linked to it as current. |
| E21 | data/distill/v1/ had no hash.txt, contradicting “every dataset directory has a hash.txt”. Resolved with E29 on 2026-09-13: that corpus gained the hash.txt it never had, and all 9 corpora now verify. |
| E22 | The acceptable_alternatives field is empty in 688/688 probes — which is exactly why pra_strict is ~0 throughout and pra_lenient is the reported metric. Guardrail G12 exists because of this. |
The discipline, not the checkpoint, is the reusable artifact. The corrections above were not applied silently: the findings ledger records each one, the errata records what the artifacts show instead, and the guardrails file turns each mistake into a rule for the next pass. findings.json resolutions.json
The 13 rules that came out of it
Read out of docs/GUARDRAILS.md by the build script, so the list cannot drift from the file. G9b is here because the E29 correction above rewrote G9 and split the partial-verification failure out of it.
- G1A result's label comes from the script that produced it, never from the write-up
Findings #1 and #2 — the highest-severity items in the audit — both came from trusting prose over code. The 70.0% UAR was credited to the DPO checkpoint for months because a table said "SFT+DPO+memory", while `run_system_e_v1.py:35` loaded `outputs/sft/v1/merged` and named the system `E_sft_memory_v1`. The one-line check that would have caught it is reading `HFEngine(...)` in the generating script. *Check:* for every number in a write-up, name the script and the checkpoint path that produced it. If the artifact filename and the prose label disagree, the filename wins.
- G2A two-stage comparison must isolate one stage
Finding #2: distillation was credited with a +3.3pp gain measured between `outputs/sft/v1/merged` and `outputs/distill/v1/merged` — but the distilled model was trained *from the DPO output*, so the delta included DPO. The isolating measurement was the pairwise run that held DPO constant on both sides. *Check:* for any "X improved Y by Z" claim, list every stage that differs between the two systems. If more than one differs, report it as a combined effect or run the isolating comparison.
- G3Absence of a measurement is stated as absence
There is no full-PMB `pra_lenient`/UAR measurement for any DPO checkpoint. A blank cell in a results table reads as "not yet run", but a borrowed number reads as a result. *Check:* an em dash for a metric that was never computed, plus one sentence saying so, and never a number carried over from a neighbouring row.
- G4Counts are generated, not typed
Every stale count in this audit (#3, #14, #17, #21, #27) was a hand-written number that drifted: 40 personas vs 8, 451 tests vs 473, 21 bugs vs 27, "15k lines" vs 66,383, two GGUF size sets in one file. *Check:* where a count can be derived from an artifact, generate it or say when it was last verified. Datasheets are generated — fix the generator, not the generated file (finding #19 was caused by a hardcoded title in `scripts/build_pmb.py`).
- G5"Done" and "not done" live in one place
Finding #8: `docs/research_questions.md` still described imatrix quantization as an undone next step while `docs/quantization_results.md` documented it as completed. Finding #25: three files called `dpo-v1-scale` current-best while four others named `distill-v1`. *Check:* a project-state claim appears in exactly one document and is linked from anywhere else. When state changes, the stale claims are found by grepping the old state's name.
- G6Configuration claims are verified per file, not per pattern
Finding #4: "base model revision is pinned by commit SHA in each config" was true for 2 of 6 configs; the other four used `"main"`. *Check:* a claim about "all configs" is backed by a command that reads all of them.
- G7A number with no committed artifact is labelled as reported, not measured
Finding #6: every quantization figure — throughput, perplexity, file sizes, parameter count — is restated from run logs that are not in the repository, and `*.gguf` is gitignored so nothing can be recomputed. *Check:* the docs carry an evidence note naming what is not reproducible from a fresh clone.
- G8A referenced artifact must exist
Findings #16 and #26: `make figures` was part of the stated reproducibility chain and `scripts/make_figures.py` never existed; a results file was referred to as "if it exists". A command in a reproducibility chain that cannot run invalidates the claim. *Check:* every path and command in a doc is executed or resolved during the same pass that writes it.
- G9A hash is recomputed from a clean checkout before it is trusted
This rule was written wrong the first time. It originally said "all six `hash.txt` files verify, once CRLF is normalised to LF". Four of them do — the SFT/DPO datasets — and **four do not, under any algorithm**, which was only established by testing them against a clean checkout of the commit that introduced them (finding #29). The original claim came from a partial check that was generalised to all six. *Check:* recompute every hash, from a clean checkout, before writing that they verify. Record the algorithm and the line-ending assumption next to the hash; this repo had two undocumented and incompatible algorithms in use. Where a hash cannot be reproduced, name the artifact that supersedes it rather than leaving a broken pin in place, and remove the claim that it is a pin.
- G9bA partial verification is not a verification
The specific failure above: four hashes were confirmed and the sentence was written about six. Nothing about the four that passed said anything about the other two. *Check:* a claim about "all N" is backed by a check that covered N, and the check's coverage is stated.
- G10Published cards are corrected by re-push, and errata in the meantime
Six model cards were already on the Hub carrying the misattributed figure. The repository cannot fix a published card by committing. *Check:* after correcting a card source, re-push; if a number is already public and wrong, add it to `reports/ERRATA.md` so the repository's own record supersedes it.
- G11Contradictory status text is worse than no status text
Finding #11: the SFT datasheets said contamination was unchecked, the distill datasheet said it was checked clean, and it cited the SFT datasheet as its source. The data was clean the whole time; only the documentation was wrong. *Check:* a claim that cites another file must match that file. Where two files state the same fact, generate one from the other or state it once.
- G12A field documented as populated must be populated
Finding #22: the README said probes ship with "acceptable alternatives"; the field is empty in 688/688, which is precisely why `pra_strict` is ~0 and `pra_lenient` is the reported metric. *Check:* every documented schema field is either populated or documented as unpopulated with the consequence spelled out.
The list that makes the rest of the page readable.
Taken verbatim from the freeze manifest’s own not_claimed array, with the page’s additional limits below it. A reader who skips everything else on this site should read this section.
- No full-PMB pra_lenient or UAR measurement exists for any DPO checkpoint; dpo-v1-scale appears only in pairwise comparisons.
- No quantization figure in this repository is backed by a committed artifact (*.gguf is gitignored and results/ holds no quantization output).
- data/distill/v1/ has no hash.txt, so its contents are not integrity-pinned here. — stale: 9 hash.txt files exist at the tag, including that one. See the discrepancy table below.
- The acceptable_alternatives field is unpopulated in 688/688 PMB probes, which is why pra_strict is ~0 throughout and pra_lenient is the reported metric.
- Every result is a single seed and a single run. Nothing here is replicated.
- No human evaluation exists; no reviewer log or teacher transcript was retained, so the teacher identity is self-reported and unverifiable from this repository.
The repository’s canonical study definitions carry the up-to-date version of this list; this page prints the frozen manifest’s copy verbatim and flags the one item that has gone stale rather than silently editing either.
- pra_lenient has no bootstrap confidence interval anywhere: the harness computes intervals only for pra_strict and UAR, and pra_strict is 0.0% in every system because the benchmark’s acceptable_alternatives field is empty.
- MUR (memory utilisation rate), the metric H5 was written against, is 0 in every committed metrics.json — defined, never measured. It is reported here as 0.0, which is what the files hold, and it should not be read as a finding.
- The k-sweep ran on 120 of the 688 probes (a stratified subsample), not the full benchmark, so its k=8 point (17.14%) is not comparable with D’s full-set 15.13%.
- The pairwise comparisons used 105-probe subsamples of answerable probes, not the full set.
- Judge-scored persona consistency sampled 60 of 688 responses per system.
- The 2B + scaffold vs 8B crossover (H16/H17) — Study 001’s own thesis question — was never run.
- H7 (alignment tax) was never tested: no IFEval-style instruction-following benchmark accompanies the DPO result.
Where the site and the repository disagree, the artifact wins, and the disagreement is printed. 7 were found while building this page — the last two by checking the manifest against the tree rather than trusting it:
| Quantity | What the prose says | What the artifact holds | What this site does |
|---|---|---|---|
| D · raw + memory, pra_lenient | docs/STUDIES.md and README.md print 15.10% | 15.13% — results/v0.1/D_memory/metrics.json holds 0.1513157894736842 | The artifact wins. The page prints 15.13% everywhere, so nothing on this site repeats the 15.10% figure. |
| B (v1) · SFT v1 alone, UAR | docs/proper_scale_results.md’s corrected pass and docs/distillation_results.md print 25.0% (20/80) | 21.25% — results/v1_scale/B_sft/metrics.json holds 0.2125, which is 20/80 pre-rescoring | The artifact is shown in the table as it is and labelled stale; it is excluded from the ladder chart. scripts/generate_linkedin_figures.py states the same thing about this file. |
| C-vs-E at v1 scale, pairwise gap | docs/proper_scale_results.md prints 39.0% vs 20.0% (19.0pp) in its headline table | 45.7% vs 21.0% (+24.7pp) — results/v1_scale/C_vs_E_pairwise/summary.json | The artifact wins, and it matches the same document’s later corrected section: the headline table is the superseded bugged run. |
| Study 001’s own freeze commit | the freeze manifest’s own git_commit field reads f2735aa | the study-001 tag resolves at 4d9e5a0 — resolved with git, not read from the manifest | The manifest is written from `git rev-parse HEAD` at freeze time, so it lags a commit that only repaired hashes and docs. This page prints the commit the tag actually points at, shows the manifest’s own value beside it, and never treats the manifest field as evidence of the tag’s state. |
| false-abstention rate | docs/proper_scale_results.md, with the second and third points also in scripts/generate_linkedin_figures.py | no results/**/metrics.json records a false-abstention rate at all | The rates are parsed from the committed document by scripts/build-study-data.mjs and labelled as document-sourced, not artifact-sourced. Only the final point’s UAR and pra_lenient are also in a JSON artifact. |
| the manifest’s not_claimed list | it still says data/distill/v1/hash.txt has no hash.txt | data/distill/v1/hash.txt exists at the tag — 9 hash.txt files do (4 benchmark, 5 training) | The list is typed text in the manifest and is not regenerated from the tree, so it went stale when the hash repair created that file. This page prints the list verbatim and this row beside it. Item 3 of 6. |
| a guardrail citation with no guardrail | the E29 corrections cite “guardrail G13”, “G13” in reports/ERRATA.md and in the two new scripts | docs/GUARDRAILS.md defines 13 rules and no G13/G13: the rules are G1, G2, G3, G4, G5, G6, G7, G8, G9, G9b, G10, G11, G12 | The rule describing that mistake already exists as G9b, “A partial verification is not a verification” — it was added by the same correction. This page reads the rules out of the guardrails file, so it prints G9b. |
The stale SFT-v1-only row is the one case where this page prints a number the project itself considers corrected: the artifact is shown as it is, flagged, and left out of the chart, because a site about publishing corrected results should not quietly launder one.
76 artifacts, pinned by SHA-256, and a check that fails if one moves.
Study 001 stopped changing on 2026-09-13. Its evidence is hash-pinned at the tag study-001; the manifest is regenerated and compared by scripts/freeze_study_001.py --check, which fails on any drift.
make validate — make validate — freeze + hashes + recomputed metrics + claims matrix. The script behind that target is not in the frozen tag, so this page does not claim it exists; it will be reported here when it lands.The living documents are excluded on purpose. The errata grows as mistakes are found and the guardrails gain rules; hashing them into the enforced set would mean every future correction “breaks the freeze”, which is backwards. The hash is computed over LF-normalised bytes so it verifies on a fresh clone — the naive sha256sum fails on all of them because the working tree is CRLF, which is the mistake guardrail G9 exists to prevent.
4d9e5a09c67fa0cef688309dc4a3cc556353c073f2735aa11f89fdd6b3646b9a8cc7d38fb03d8ae9one commit behind — the freeze writes the field from HEAD at freeze time, and a later commit that only repaired hashes and docs did not re-take itThe commit is resolved with git, not read from the manifest. A generated field is not evidence of the state it names: the manifest’s own git_commit is stale here, and taking it at face value would have put the wrong commit on this page. Both values are shown.
The freeze was re-taken, and each re-take is logged
The freeze was re-taken. Each entry records what moved and why it did not change a Study 001 finding. An unrecorded re-freeze makes the manifest meaningless, so this list is part of the artifact.
| Date | What moved | Why it did not change a finding |
|---|---|---|
| 2026-09-13 | hf_readmes/generate_cards.py | Formatting only (one long line wrapped by black to satisfy E501; no values touched). Verified after the change that the generator still emits the corrected card content: no '70.0%' UAR on the DPO card, 'not measured' in the DPO eval table, and '15.30% / 70.0%' attributed to SFT+memory. No Study 001 finding depends on this file's byte content, only on the cards it produces. |
| 2026-09-13 | README.md | Corrected to name the four hash.txt files that verify and to point at this manifest as the authoritative pin for the benchmark corpora (finding #29). A correction to a claim about the artifacts, not a change to any artifact. |
Every number on this page, and the file it came from.
No figure above was typed by hand. scripts/build-study-data.mjs reads each artifact out of the frozen study-001 tag with git show and writes src/studies/001/study-001.json, which the page and its charts import. The one dataset that has no JSON artifact is parsed from the committed document that holds it, and says so.
| Path in small-mind-companion | What was read out of it |
|---|---|
| results/v0.1/A_raw/metrics.json | A: pra_lenient 0.16%, UAR 13.75%, per-category breakdown |
| results/v0.1/B_sft/metrics.json | B (v0): pra_lenient 0.16%, UAR 16.25% |
| results/v0.1/D_memory/metrics.json | D: pra_lenient 15.13%, UAR 8.75% |
| results/v0.1/E_sft_memory/metrics.json | E (v0): pra_lenient 17.76%, UAR 33.75% |
| results/v1_scale/B_sft/metrics.json | B (v1): UAR 21.25% — labelled stale on this page |
| results/v1_scale/E_sft_memory/metrics.json | E (v1): pra_lenient 15.3%, UAR 70.0% |
| results/v1_scale/E_distill/metrics.json | E-distill: pra_lenient 18.59%, UAR 71.25% |
| results/v0.1/ksweep/summary.json | k-sweep pra_lenient and UAR at k = 0, 2, 4, 8, 16 |
| results/v0.1/ksweep/k8/metrics.json | k-sweep probe count (120) |
| results/v0.1/C_vs_E_pairwise/summary.json | C-vs-E win rates and ties at v0 scale |
| results/v1_scale/C_vs_E_pairwise/summary.json | C-vs-E win rates and ties at v1 scale |
| results/v1_scale/C_vs_F_distill_pairwise/summary.json | F-vs-C win rates and ties — the distillation-specific measurement |
| results/v1_scale/pcs_judge_analysis/summary.json | judge-scored persona consistency for B, E and E-distill |
| results/v1_scale/pcs_stylometric_analysis/summary.json | stylometric self-consistency and pre/post-distillation drift |
| data/benchmarks/pmb_v0_full/probes.jsonl | probe, persona and category counts (688 probes, 8 personas, 608/80 split) |
| docs/proper_scale_results.md | the false-abstention trajectory (9.9% → 69.2% → 32.1%), its UAR and pra_lenient companions, and the detector numerator/denominator counts |
| reports/data/study-001-freeze.json | 76 frozen artifacts, 7 groups, the hash method, and the verbatim not-claimed list |
| reports/audits/001-claim-audit/findings.json | 29 findings (5 HIGH), their category counts, and the verbatim claim/actual text of every HIGH finding — including #29, BROKEN_INTEGRITY_PIN |
| reports/audits/001-claim-audit/resolutions.json | resolution status (25 resolved, 2 partial, 2 open) and the verbatim resolution note and fix locations |
| docs/GUARDRAILS.md | all 13 rules, parsed rather than typed — including G1, G2, G3, G4, G5, G6, G7, G8, G9, G9b, G10, G11, G12 |
| git ls-tree / git grep at the tag | 9 dataset hash.txt files (4 benchmark / 5 training), and the check that the manifest’s not_claimed list and the repository’s guardrail citations still hold |
| configs/training/*.yaml | which configs pin the base model revision (2 of 6) |
The experiment that answers this study’s own question was never run.
Study 001 asked whether a small model plus a scaffold can substitute for scale. It measured the scaffold, the post-training and the tradeoff, but it never ran the 2B + scaffold against an 8B with no memory — the crossover script is written and unrun, and it needs a GPU. That, and replication of every headline number, is Study 002.