{"path":"research/session20-the-strength-layer-and-the-unspecified-middle.md","content":"# Session 20: The Strength Layer, and the Unspecified Middle\n\n**Date**: 2026-08-17\n**Type**: Audit, two shipped fixes, two papers read, and a design space worked out. **The organising pattern is narrower than Session 19's: every defect found today lived in a layer that had no specification**, and the day's most useful output was recognising that as a class rather than a coincidence.\n\n---\n\n## 1. What started it\n\nA throwaway line at the end of a terminology answer — *Deliberus doesn't iterate* — which the founder pulled on:\n\n> \"Ooh. That's an excellent find. I want and need more insight into all of this. The paper and theory around it. How it relates to our graph and the architecture around it.\"\n\nReading the module to answer that turned up three defects the iteration point had been sitting next to.\n\n---\n\n## 2. The correction that matters most, because it was mine\n\nThe first version of the audit reported that *a claim with 61 supporters displayed `NO DATA`*. **That was wrong.** It tested the aggregation function with empty input rather than the endpoint, which had an incoming fallback four lines further down that I had not read.\n\nThe accurate picture is different and, in one respect, worse:\n\n- **Polarity was ignored entirely** — `compute_claim_badge` never read an edge's *type*, so a well-supported attack raised a claim exactly as a defence did. **1,567 incoming `ATTACKS` edges counted as supports.**\n- **Direction preference was backwards** — outgoing first, incoming only as fallback. 54 claims read from the wrong side; 3,401 with no incoming evidence still displayed a confident badge.\n\n**The severe defect was the one I had not noticed, and the one I reported first was the one I had got wrong.** That is worth recording because the failure was not retrieval — the endpoint was open in front of me. It was read-depth, which no amount of context loading fixes.\n\n### The fix, and why the scope grew\n\nFixing direction alone would have made things **worse**: once incoming edges are read, unsigned attacks would actively strengthen the claims they attack. So polarity came along by necessity, and once contributions are signed an arithmetic mean is meaningless, so the aggregation came too.\n\n**None of that was invention.** The module's own file header specified `Energy = sum(supporter_strengths) - sum(attacker_strengths)` all along. The claim-level function simply never implemented what the file said it did.\n\n**Corpus impact: badges fell from 3,828 to 427**, with 3,401 now honestly reading `NO DATA`. A large visible change to the site, in the right direction — a confident badge with no evidential basis was the graph asserting something it had no grounds for.\n\n---\n\n## 3. Where it came from, and the class it belongs to\n\nGit archaeology and the March design spec answer this cleanly, and the answer is not incompetence.\n\nThe spec specifies `compute_edge_strength` and `strength_to_badge`. **Both edge-level. Both correct.** It never specifies a claim-level aggregate. That arrived in the same commit as an undesigned extra, and when the endpoint was wired months later, *\"the edges attached to this claim\"* was implemented as outgoing.\n\n**The generalisable form, which the founder pushed toward and then sharpened:**\n\n> \"I feel like this is yet another example of a situation where more/richer/fuller relevant context being brought in to CC sessions especially before making changes to code is crucial.\"\n\nHalf right, and the half that is wrong is instructive. **The convention it violated was documented**, and whoever wrote the endpoint plausibly knew it. What was missing was not information but **a question**: an unspecified layer has no moment at which *\"which direction should this read?\"* must be answered, so it never was.\n\nAnd the tests could not catch it for a structural reason: **they pinned the implementation rather than the intent.** A test authored from the code cannot falsify the code — the fabricated-fixture rule one level up, where the same author writes both sides of the comparison.\n\nSearching the corpus for prior work on this found an asymmetry worth naming: **every piece of context work in dotfiles is about spending less; none is about checking there is enough.** Full treatment, including the three distinct failure classes of which only one is a retrieval problem: `~/dotfiles/docs/context_sufficiency_before_code_changes_2026_08_17.md`.\n\n---\n\n## 4. The founder's framing on the fix, which set the register\n\n> \"Fix the badge direction bug please. And investigate if it occurred by mistake or misunderstanding or what. But I guess it doesn't matter as much as getting it right for the future.\"\n\nWorth keeping because it names the correct weighting for archaeology: **find the origin because it identifies the class, not because it assigns fault.** The origin here turned out to be load-bearing precisely for that reason — it produced the unspecified-layer finding, which is more useful than the fix.\n\n---\n\n## 5. Theory before formula, at the founder's insistence\n\n> \"The subtree energy and evidence computation is solvable, I'm sure. We just need theoretical clarity. Search for academic papers on this and closely related topics and problems.\"\n\nThat instruction changed the outcome. The instinct was to reach for `min` instead of `sum`. **The literature had already named the distinction, formalised it, and proved a duality**: deductive, necessary, evidential and backing support are four interpretations developed independently and later unified.\n\n**Dr Smith's parts are necessary support** — the inverse closure principle, where accepting the whole requires accepting the part. And the mechanism follows from the reading rather than being bolted on: under a necessary interpretation, **an attack on any one part induces an attack on the whole**.\n\n### Then he made me read the papers I had flagged unread\n\n> \"Read up!! :)\"\n\nFair, and it changed two things. I had cited arXiv:2603.06067 as *\"unread, and it should be read\"* — which is honest labelling, and not a substitute for reading. **Reading it reframed the remaining work**: its central move is that attackers and supporters should be aggregated **separately**, in three independently constrainable stages, *because they do not play symmetric roles*. That is precisely the layer built here with no spec, and it says the answer is more parametrisation rather than one better formula.\n\nIt also exposed two divergences from canonical QE that were previously accidental and are now deliberate and documented: **we feed the energy term with edge strengths where the paper feeds supporters' own acceptability degrees recursively**, and **we sum offsets from 0.5 where the paper sums raw degrees** (defensible here, since 0.5 means \"unanswered\" rather than \"half-strength\").\n\nAnd it handed over a ready-made ontology constraint: **no argument may both support and attack the same target.** Measured live: zero violations. **First concrete rule for the approved Gate-2 layer, arriving from a paper rather than from a bug.**\n\n---\n\n## 6. The implementation, and the test that caught my design error\n\n`support_interpretation` on `DECOMPOSES_INTO` (`necessary` | `corroborative` | `undecided`).\n\n**The first version was wrong and a test found it.** I had necessary parts contribute a *negative energy term* — but energy is additive, so a failed requirement is simply outvoted by strong siblings, and a whole whose domain match had collapsed would still read well. The literature's mechanism is stronger than a term. **The correct behaviour is a ceiling: a whole can be no stronger than the weakest thing it requires.**\n\n**Two properties verified rather than asserted.** A `necessary` reading **eliminates the splitting incentive** — three parts and twelve parts at 0.70 both yield 0.502, because a minimum cannot rise by adding to it. And **nothing moved**: all 131 decomposition edges are untagged, `undecided` reproduces the shipped aggregation exactly, and unrecognised values fail open.\n\n**No backfill, deliberately.** Guessing an interpretation for edges written before the distinction existed would be inventing evidence. The layer is inert until someone records one, and *who records it* is an open founder decision.\n\n---\n\n## 7. Two other threads, both closing rather than opening\n\n**Should Deliberus exist?** Asked directly after the compounding caution. The question splits three ways, only one is live, and mapping Antikythera's four failure causes shows the undetermined variable is **whether anyone reads it** — not quality, novelty, or whether the thesis is right. Full answer, including what would move the verdict in both directions: [conviction-and-critique.md](conviction-and-critique.md) § Should this exist.\n\n**The daemon vision** was documented until today only as a clause inside the registered prediction. Both founder metaphors turned out to have homes in the corpus already, and both yield hard constraints — constructor theory gives *an enzyme changes the rate, never the equilibrium*, and the corpus's own T-cell analysis is a **warning about pre-digestion** rather than an endorsement: [graph-daemons-design-space.md](graph-daemons-design-space.md).\n\n---\n\n## 7b. Two hooks, and one deliberately not built\n\nThe mechanism question was settled against the fleet's own precedent rather than from scratch:\n`rust-skill-preeval` already exists because proactive skill loading fails ~80% of the time, which is\nstructurally the same failure as proactive corpus querying. And the fleet has an unwritten language rule\nthat the inventory makes obvious — **Rust for hot paths and LLM callers, Python for guards and nudges** —\nso neither addition warranted Rust.\n\n**The read-a-part cue and the memory-for-record words** went onto the existing per-turn directive-scan\nhook, no new mechanism. The first version led with its anchoring incident and buried the class; the\nfounder caught that it would age badly, so the rule now leads — *describing a whole from having read a\npart* — with the incident as illustration.\n\n**`spec-gap-nudge` was built**, PostToolUse on Write/Edit/MultiEdit, conditional-silent, six branches\nverified. Post-edit and non-blocking on purpose: a spec gap is not a hazard, the goal is the sentence\nrather than the block, and a gate that blocks harmless work is one a reader learns to wave through.\n\n**The corpus probe stayed un-hooked**, deliberately. It is a judgment a shell script cannot make, and a\nreminder on every edit becomes wallpaper. The directive's trigger was reforged instead.\n\n## 7c. The late arc: three founder decisions, a deploy, and one number that changes the brief\n\n**The third decomposition shape shipped.** `alternative` — competing routes where one surviving is enough, so the strongest part decides. Verified it cannot be padded: two, three and eight weak routes all give exactly 0.500. The founder's naming insight is recorded in the module: these three are the classical **linked / convergent** distinction plus a disjunctive case, and **deductive-versus-inductive is a different axis** — about the *inference*, already absorbed into the scheme on the edge in April, with his own verbatim concern from that session in the doc.\n\n**Positive labels now require answered questions**, and tracing where the labels came from produced the day's sharpest finding: they are the founder's own words from a 2009–2013 hand-drawing, and **his sketch specified the state the specification lost**. Full provenance and the structural-sycophancy analysis: [strength-layer-audit.md](strength-layer-audit.md) §2b.\n\n**My first version of that gate was wrong and a test caught it.** It checked whether the summed energy was zero, which mislabels a claim whose support and attack genuinely cancel — **balance is a finding, not an absence.** The gate now tests the inputs. And an existing test had *encoded the defect*, asserting a positive colour for all-unanswered with a comment explaining the threshold that produced it.\n\n**The similarity guard was deployed**, after a Darwin preflight that came back clean (load 0.25, 11 GB free) 90 minutes after the wedge. Live verification: six simultaneous requests, **four shed with 503**, exactly as designed. Graph integrity confirmed at **4,769 claims and 25 sources** — the pre-incident numbers, nothing lost.\n\n**One measurement changed the brief, and the brief was then acted on.** A single similarity request against production took **20 seconds**, not the 2.15 the local benchmark suggested, and two of six concurrent calls never completed — so removing the full-corpus fetch moved from a safety fix to a functional requirement.\n\n**Decomposed by stage, the assumption was wrong about where the cost sat**: embedding the query 0.18s, **fetching and parsing 3,577 embeddings 15.08s**, cosine over all of them in Python 0.19s. The Python arithmetic everyone had blamed, including me, is **one percent** of the cost; the cost is moving 3.7 million floats across the wire per request, which is also where the 160 MB went. FalkorDB 4.16.7 computes cosine distance itself, so the whole thing became a query change. **Live at 0.31s.** Equivalence verified before speed: identical claims, identical order, largest delta 9.24e-07.\n\n**Two of my own defects surfaced in that work and are worth keeping.** The support probe was cached in a module global, so one graph's answer — or a test double's — would decide for every graph in the process, and a transient failure would have been remembered forever; it is now cached per graph object. And the threshold test encoded only the old row shape, so it never covered the fallback path at all; it now covers both paths and the complement case. **Three call sites remain unwired**, deliberately, because they feed auto-connect and a silent regression there is the April-2026 shape.\n\n## 8. Open, and named rather than left implicit\n\nWhich vocabulary the support interpretation uses — **the third shape, alternatives, is in the live graph and unimplemented, and it must be settled before the first tag is written** or everything tagged has to be re-read. Then who records one, and through what affordance. Whether the splitting incentive should be normalised away for corroborative parts. Whether methodology should reach a claim damped, as it now does. Whether to iterate. The hooks question is **settled and no longer open** — see §7b: `spec-gap-nudge` was built, the call-path check went onto the existing trip-wire, and the corpus probe stayed un-hooked because a reminder that fires on every edit becomes wallpaper.\n\n---\n\n## Cross-references\n\nWritten this session: [strength-layer-audit.md](strength-layer-audit.md) · [support-semantics-and-the-conjunction-problem.md](support-semantics-and-the-conjunction-problem.md) · [graph-daemons-design-space.md](graph-daemons-design-space.md) · [assembly-theory-and-the-reuse-mechanism.md](assembly-theory-and-the-reuse-mechanism.md) · [complexity-transitions-and-why-now.md](complexity-transitions-and-why-now.md) · `~/dotfiles/docs/context_sufficiency_before_code_changes_2026_08_17.md`\n\nShipped: `deliberus/graph/qbaf.py` (direction, polarity, energy) · `deliberus/support_semantics.py` (new) · `deliberus/slug.py` (new) · Unicode-aware comparison keys in `api.py` and `reported_speech.py`.\n"}