Simon Skade · FLF Epistemic Stack competition
0 — Executive summary
I built a 10-step pipeline (a Claude Code skill, python scripts and a deterministic runner) that turns one contested empirical question into a typed, navigable Obsidian knowledge graph whose Bayesian answer recomputes from the notes.
Why this shape (§1): contested questions rarely lack evidence; what they lack is a way to navigate it. Counting who says what, weighted by prestige, then manufactures false confidence, because correlated sources get treated as independent voices. So the pipeline builds its own technical model bottom-up from low-level observations.
How to read the graph. Each node is one markdown file with typed YAML frontmatter plus prose, and each edge is a frontmatter wikilink, so the analysis directory is a directed typed graph. The answer is a report computed over the graph, never cached in it. No posterior, prior or likelihood sits in frontmatter, because cached numbers go stale silently.
Node types:
| Type | Code | What it is |
|---|---|---|
source | S | Primary paper or dataset |
data-basis | D | Shared dataset, actor or instrument; observations sharing one are correlated |
observation | O | Empirical finding |
hypothesis | H | Candidate answer |
hypothesis-cluster | HC | Mutually-exclusive answers to a sub-question, residual last |
argument | A | Inference carrying no information of its own |
evidence-link | E | Obs→cluster edge, minted only where it discriminates |
correlation-group | CG | Joint likelihood for edges sharing a data basis |
Four stages:
- Ingest
- Find useful sources (steps 1 and 2, including data-reliability and provenance)
- Ingest into the knowledge base (3, extraction into observations)
- Structure
- Hypotheses, clustered (4)
- Graph edges, i.e. discriminating evidence links (5)
- Assess
- Argument validity (6)
- Bayesian analysis (7 and 8)
- Priors (7)
- Likelihoods (8)
- Additional abstract considerations, cluster review (9)
- Assemble the final report (10)
Key features
The delta over off-the-shelf deep research or a single-prompt Claude Code run, most important first:
- Correlated evidence is aggregated correctly. Observations resting on the same data-basis get one joint likelihood, not one factor each. Black-holes case: the folk “Earth and the Moon are still here” reassurance and the Giddings–Mangano stable-black-hole exclusion both resolve onto
D-1 — High-energy cosmic-ray flux spectrum; a source-counter sees two independent reassurances, this pipeline one witness.D-1’sknown_biasesnames their shared failure mode, fixed-target cosmic-ray kinematics mapped onto a collider’s near-symmetric kinematics. If that mapping is wrong, everything onD-1fails together. - Source-trust is separated from claim-truth, so prestige cannot launder in.
trust_scoreprices data-reliability from source features only; validity and truth are judged author-blind and must stay derivable with author and venue stripped. - Every number is an overridable named variable, its reason in the comment beside it. A skeptic re-prices any assumption and re-runs the whole model:
--set BLOCK:var=value. - Uncertainty stays live. Every non-exhaustive cluster carries a residual member (“the answer is something not listed here”) with its own argued weight, never
1 − sum(others).
Provenance
Every file in each analysis folder is generated autonomously from initial_prompt.md by the single per-case command in §7, no hand-editing; retired nodes and agent notes are kept, so discards are auditable. Worked analyses: black-holes · eggs · covid.
Stated plainly: the pipeline is implemented end to end, the runner passes a self-check, and all three cases have run the whole way through step 10 as low-N shakedown runs. The §5.1 posteriors are real and recompute from the notes, but are small-N draws from much larger scored pools — a demonstration of the method, not settled answers.
§7 turns the black-holes run into a three-command demo, including a re-run that distrusts the single paper most of its likelihoods route through and watches the posterior move. Start there if you have a terminal.
1 — Underlying principles
A contested question rarely suffers from too little evidence. It suffers from evidence nobody can navigate. The usual shortcut (count who says what, weight by prestige) manufactures false confidence: correlated sources count as independent voices, and prestige and motivated framing launder in as truth. So rather than summarizing the debate, I modelled the question bottom-up from low-level observations, letting its framings earn their place or not. The graph is the artifact; the answer is a report computed over it.
-
Bottom-up carving. Hypothesis clusters come from what papers state and what observations can pull apart: step 4b forbids importing a tidy taxonomy of the debate, and step 4a’s merge test is “does any curated observation in scope come out differently under these two?” Why: top-down carving inherits the debate’s framing and its rhetorical seams. Where the evidence discriminates is a fact about the corpus, not the argument, so it must be discovered.
-
Numbers-as-variables. Every estimate is a named python variable with its reason commented beside it, in runnable blocks in the notes; steps 7 and 8 require reasoning sketched first, values filled last. Three payoffs. Legibility: a reader disagrees with the one step I got wrong, not an opaque aggregate. Skeptic-override:
--set BLOCK:var=valuere-runs the whole model with one assumption re-priced. Scaling: a stronger model re-estimates one variable in place without touching surrounding structure. -
Trust ≠ truth.
trust_scoreprices data-reliability from source features only (design, statistics, power, preregistration, independent replication, venue and citations only as a floor), and step 2 makes it prestige’s only sanctioned channel. Claim-truth (steps 7–8) and argument-validity (step 6) are judged independently. Otherwise reputation counts twice: as reliability, then again as plausibility. Enforcement: the verdict must stay derivable with author and venue stripped. Surprisingness is banned from trust too; it belongs to the prior. -
Correlated evidence counted once. Observations resting on the same data-basis node get ONE joint likelihood on a
correlation-groupnode, stating P(all of them | H). Why: independent-counting of correlated evidence is the standard way Bayesian aggregation manufactures false confidence, and what most separates this from a source-tally. Live instance in §4: two apparently independent black-hole safety arguments on one cosmic-ray dataset. -
Explicit out-of-model residual. Every non-exhaustive cluster carries a residual member (“something not listed here”) with its own argued weight and real likelihood, never
1 − sum(others). Why: a complement is a rescaling artifact rather than an estimate, and it hides both failure modes. Too small and no evidence can lift it; too large and it soaks up every posterior. An argued weight keeps unmodelled explanations visibly competing.
Scalability
No hand-designed human bottleneck sits anywhere. One command per case produces every file, and the optional human-review mode is not load-bearing. The design improves with stronger models and more compute: better models re-estimate variables in place or extract more carefully, with no schema change; more compute buys more sources, deeper extraction, optional cross-model ensembling. Models are assigned per step by the orchestrator, on top of whatever session model the run was launched with (--model fable in §7). Judgment-heavy children (step 1’s planner and consolidator, both step-2 sub-steps) run on opus, searchers on sonnet, and everything else inherits the session model.
2 — The pipeline in detail
Ten steps, one orchestrator spawning every child. Each step fans out over disjoint slices and only adds to the previous step’s graph. No human checkpoints.

Arrow colour and label name the node type flowing along it; stacked boxes are parallel children on disjoint slices. The chips on the right of a box give its model and its fan-out.
The streams
Beyond §0’s node types: D turns “these two findings share a basis” into node identity rather than a free-text match. A CG is a connected component of edges sharing a D.
The steps
- 1a planner. One child-prompt per slice. Judgment: how to slice, with independent data per slice and every side represented.
- 1b searchers.
Snotes (~4N written, ~8N read) plus an orientation note. Judgment: the primary artifact, not the review citing it. - 1c consolidator. Deduped pool, audited against 1a’s plan; a thin side or data axis triggers a top-up round.
- 2a scorers. Mint
trust_score/usefulness,Dnodes and each source’sdata_basis. Judgment: would this survive a clean replication. - 2b selector. Duplicate
Ds merged, ~N flaggedcurated. Judgment: the cut, trading a ranking point fordata_basisindependence. - 3 extractors. ~5 papers each, read in isolation; each
O/H/Arests on exactly one source. Judgment: the observation/hypothesis line, i.e. what a rival team on the same data must grant. - 3 consolidator. Merges duplicate
Ds and near-duplicategenerally_knownobservations. Judgment: is this the same fact; paper-derived observations are never merged. - 4a merge/drop. Near-duplicate
Hmerged, off-topic dropped. Judgment: discriminability; merge only where no in-scope observation would differ. - 4b cluster. Mints
HC, residual members, backlinks. Judgment: the carving (one sub-question, exclusive answers, comparable grain). - 5 linkers. Mint an
Eonly where a cluster’s members predict the observation materially differently. Judgment: diagnosticity; no numbers. - 5 consolidator. Attaches arguments via
affects_observations, mints theCGnodes, orphans unlinked observations. No judgment: two scripts plus a sweep. - 6 argument batches. ≤20 each;
approved/corrected/rejectedpluschecked/trustedout. Judgment: does the inference hold given its premises. Validity, not strength or truth. - 7 prior children. A
## Priorpython block per cluster,used_for_prior: trueon each edge spent. Judgment: the partition into base-rate and discriminating evidence. - 8 likelihood children.
## Likelihoodblocks, one perCGor per ≤3 lone edges. Judgment: P(obs given member) as ratios,tcapped by trust. - 9 cluster reviews. One per cluster, read-only. Judgment: what the model cannot express.
- 10 report writer. One answer out. Judgment: weighing sub-answers given in different currencies. Written, not computed.
Two structural facts
- The run is an auditable diff. Steps only add fields, never rewrite them; nothing is deleted. Retired nodes move to
non-curated/,merged/,dropped/,orphan/, links and content intact, reason recorded. - The conclusion lives in the notes.
runner/run.pyexecutes the step-7## Priorand step-8## Likelihoodpython blocks to compose each cluster’s posterior; no derived number is cached in frontmatter. It gates between steps 8 and 9, so no review is written on a broken model.
3 — Why this ontology
Each node type exists because some later step judges it alone. An observation’s data-reliability, a hypothesis’s prior, and an argument’s validity are three judgments, made by three steps (2, 7, 6) against three rubrics. Hence three types. Collapse any two and one step makes two judgments in one number, the same intuition moving the answer twice: double-counting. The same test justifies the rest. data-basis: “same dataset” must be node identity, not free-text matching, or step 5 cannot detect correlation mechanically. evidence-link: an obs→cluster edge is minted only where the observation discriminates, so the edge set is itself a claim. correlation-group: edges need one shared home for a joint likelihood.
The load-bearing separation is logical validity versus empirical reliability. Step 6 asks only whether an inference holds conditional on its premises, author-blind and indifferent to their truth. Truth is priced later, as a prior (step 7) or a likelihood (step 8); merging the two counts the same doubt twice. Hence a case the pipeline gets right and a source-counter gets wrong. The ADD large-extra-dimensions proposal yields perfectly valid derivations (step 6 would mark them approved), though its premise may not hold of our world. No truth-inflation: that premise’s plausibility lands in the cluster prior, where it can be argued and overridden. It runs both ways. A motivated source’s data can carry full trust_score while its selection of arguments is discounted downstream.
The payoff: prestige and motivatedness cannot launder into the conclusion. trust_score is the only sanctioned channel for venue and citations, and it prices data-reliability alone. The verdict stays derivable with author and venue stripped, and anyone wanting my structure but not my numbers can override the variables and re-run.
4 — The underlying Bayesian logic
Per cluster: a prior over mutually-exclusive members, times the likelihood of each discriminating observation under each member, composed by the runner into a posterior. Nothing else. Likelihoods exist only where an observation discriminates, meaning at least two members predict materially differently; one they all predict equally stays unlinked and marked orphan, never counted as “consistent with”.
The anti-double-counting core is correlation. Observations sharing a data-basis node get one joint likelihood on a correlation-group node spanning the connected component of edges on that basis: one evidence() call naming every observation, stating P(all of them | H) jointly, never a product of per-observation factors. Live instance: D-1 — High-energy cosmic-ray flux spectrum underlies both the naive Earth-and-Moon-survival argument and the Giddings–Mangano stable-black-hole exclusion, two reassurances a source-counter adds up, priced once.
Every number is a named variable in a ## Prior or ## Likelihood python block, reasoning in the comment beside it; --set BLOCK:var=value re-prices any assumption. A non-exhaustive cluster carries an explicit residual member with an argued weight, never 1 − sum(others).
The runner’s composition: normalise the prior, then each edge multiplies every member by t·lik[i] + (1−t)·marg, marg being the prior-weighted average likelihood. Equivalently, posterior = t·(posterior at t=1) + (1−t)·prior. So t is how far this evidence carries you off the prior: capped by the source’s trust_score, argued down for any gap between raw data and stated observation. Anchoring on the prior, not the running posterior, makes a cluster’s edges commute.
5 — Case studies
Three questions with contrasting difficulty profiles (near-closed physics, a vague nutrition question, a live politicized one) got the same command and the same case-agnostic schema, differing only in question and curated_target_N. All three ran all ten steps end-to-end as preliminary shakedown runs at N=5–10 curated sources (§5.1); larger runs are queued.
That claim is checkable in thirty seconds: across the pipeline’s 3152 lines of specification and code, the only occurrence of “black hole”, “egg” or “COVID” is one illustrative parenthetical in the skill’s one-line description.
- black-holes (primary): final report. Goes after what the consensus rests on, not the consensus (§4).
- eggs: final report. Tests whether the method can report that there is no strong crux. A pipeline that always emits one is broken, not impressive.
- covid (lower effort): final report. Live, high-stakes, deliberate information sabotage;
trust ≠ truthis built for it, but a graph can only assess arguments that reached it.
5.1 — Preliminary results
Read these as shakedown runs, not as answers. They live in analysis-tests/, separate from analyses/, where the full-N runs land after the deadline. Each ran at curated_target_N = 5 (black-holes, covid) or 10 (eggs): black-holes scored 23 sources, 18 cleared the trust baseline, top 5 curated. The evidence base is thin. Read them as what the pipeline does with evidence, not what is true. The numbers are genuine: all ten steps ran, every posterior recomputes via run.py (§7).
- black-holes. HC-3 (hole forms): H-8 0.94. HC-2 (evaporates): H-4 0.956 vs non-evaporation 0.044. HC-1 (trapped stable hole is dangerous): catastrophic H-1 0.035, harmless H-2 0.888, residual 0.076. Chaining the danger legs gives order 1e-4. That is my composition, not a model output, and a loose upper bound rather than a computed probability: the legs share
S-1andD-1, so are not independent. Structure matters more: nearly every empirical likelihood routes through one paper (Giddings–Mangano, trust-capped 0.74) and its cosmic-ray premiseD-1; dropping trust 0.74 → 0.3 moves HC-1’s danger mass ×4.7. - eggs. HC-2 (net direction on hard endpoints), by branch: null 0.639, direction-varies 0.308, protective 0.039, harmful 0.014. HC-1 (lipid mechanism): a real-but-saturating dietary-cholesterol effect at 0.810. HC-3 (heterogeneity): ~0.659 on “not uniform across people”. Mechanism and endpoint layers agree.
- covid. One cluster only: zoonotic spillover at Huanan 0.495, research-related incident 0.081, neither-listed residual 0.424.
Where they are weak.
- Small-N curation can delete an evidence class. Curation ranks by score; no constraint keeps each class alive. In covid it dropped case geolocation and epidemiology entirely (including the pool’s top-usefulness source), which is much of why the residual is 42.4%. That residual is structural: every curated market-side observation was sampled at Huanan, every research-side one concerns the WIV, so no likelihood discriminates against either leg. That is a fact about the observation set, not the world, and the report states it rather than allocates it.
- covid produced a single cluster, so cross-cluster weighing got no exercise there; only black-holes (4 clusters) and eggs (3) demonstrate it. Its lab-leak 0.081 is not a finding: one step-7 Fermi factor with no reference class, and defensible re-settings move it between 0.018 and 0.173.
- eggs’ HC-2 is not a clean partition. Three members assert a null under different scope riders, so they can co-hold and member-level ranking is undefined; the report ranks by branch. HC-1’s 0.810 is partly circular: its strongest edge comes from its own extraction source, and nothing detects that.
- black-holes covers one mechanism. Strangelets and vacuum decay, both named in the question, never became clusters at N=5. The LSAG report, which publicly put the risk to rest, scored below the cut as a synthesis, so the analysis reconstructs it from LSAG’s primary inputs.
What the small run did get right. The safety case turns on the cosmic-ray argument and its non-obvious repair: cosmic-ray-produced holes are relativistic and escape, so Earth’s survival says nothing about the slow, trapped holes the LHC would make. Hence white-dwarf and neutron-star survival is load-bearing. The graph carries both halves (O-3, O-1/O-2, O-4) plus the joining arguments: A-1 closes the relativistic-escape loophole via WD/NS stopping power, A-2 excludes fast accretion, A-3 excludes charged holes, and A-6 defuses a separate naive leg — Earth’s and the Sun’s own survival read as a safety bound — on observer-selection grounds, leaving neutron-star survival to carry the weight. Curation also caught itself: the script flagged D-1 as shut out of the top-5 cut, and the cut was adjusted to admit the one source resting on it.
6 — Further thoughts on limitations & improvement options
- The trust floor can silence the side most worth hearing.
combined_score = usefulness × (trust_score − baseline), baseline 0.8, cuts everything below baseline trust; contrarian positions are less-published, so it tracks dissent, not error. Step 2’s exception clause (curate one under-floor source per unrepresented position, logged incuration_reasonandagent-notes/curation.md) is discretionary, and at N=5 no one invoked it. It fired in the shipped run: Plaga’s metastable-black-hole paper (S-16,trust_score: 0.40,usefulness: 4.0,combined_score: −0.80) sat under the floor and was cut, as wasS-17at 0.00, so the strongest published challenge to the accretion machinery never entered the graph. Fix: class-coverage constraint, not score ranking. - Extraction is not reproducible. Steps 1–5 are model calls; two runs find different sources and carve at different granularity. Downstream is deterministic (arithmetic over named variables;
import,open,eval,execand dunder access refused), but variance in the inputs is quantified nowhere. - Assessment cannot exceed the ingestion schema. Steps 6–10 price only what step 3 recorded. Bounded by
source+locatoron every node and by “move, never delete” (non-curated/,merged/,dropped/,orphan/), but the reader must notice the omission to repair it. - Counter-argument search is deprioritized. Nothing hunts the argument no source had an incentive to make; step 9 names the gap but cannot fill it.
- The anthropic shadow is unsolved. Survival-based bounds are observation-selected: we could not observe the branch where the mechanism fired. The run corrected this.
A-6, sourced to Tegmark and Bostrom 2005 (S-23) and carryingreason_if_not_false: checked, holds Earth’s and the Sun’s survival cannot bound the catastrophe rate at all, while white dwarfs and neutron stars, observed independently of us, retain most of their evidential force. But that entered as an argument, not a schema feature: no field marks an observation as selection-affected, and a run missingS-23would have used Earth’s survival unchecked.
Improvements, by expected value. (1) Cross-model ensembling of steps 7–8, which measures limit 2. (2) An adversarial counter-argument pass per cluster after step 5 (targets limit 4). (3) Continuous distributions and hierarchical hypotheses where discrete clusters fit badly. (4) A duplicate-evidence audit after steps 5 and 8; the correlated-evidence guard is only as good as its data_basis attribution.
7 — How to run & reproduce it
Edited after the submission deadline. The demo and curated entry points below originally ran on sample-sahul-megafauna, a hand-written structural demo whose numbers were illustrative rather than pipeline-computed. It has been withdrawn from the site and its two passing mentions elsewhere (§3, §6) trimmed; both now use the real black-holes run instead. §6’s line count was also corrected from 3053 to 3152, the figure the shipped skill actually gives. Nothing else changed.
Browse it: https://epistack.simonskade.org. Every typed node is rendered and navigable by its frontmatter edges; the FLF iteration is frozen at https://epistack.simonskade.org/v1/. The pipeline specification itself is browsable at https://epistack.simonskade.org/v1/pipeline/.
Reproduce a run: clone https://github.com/SimonSkade1/flf-epistack. It ships the skill at .claude/skills/flf-epistack/ and the completed runs at content/v1/analysis-tests/<case>/. Each case is one command:
cd content/v1/analyses/black-holes && claude -p "/flf-epistack 0 — curated_target_N=25" --model fable --effort max --permission-mode bypassPermissions
cd content/v1/analyses/covid && claude -p "/flf-epistack 0 — curated_target_N=25" --model fable --effort max --permission-mode bypassPermissions
cd content/v1/analyses/eggs && claude -p "/flf-epistack 0 — curated_target_N=50" --model fable --effort max --permission-mode bypassPermissions
Every file in an analysis folder comes from its initial_prompt.md via that command, no hand-editing.
Interrogate the answer: a two-minute demo. Runs on a published analysis; needs only python 3:
python3 .claude/skills/flf-epistack/runner/run.py content/v1/analysis-tests/black-holes1
# HC-1 prior [0.3097, 0.5752, 0.115] posterior [0.0354, 0.8882, 0.0764] (2 evidence block(s))
One line per cluster, in HC.hypotheses order (here [H-1 catastrophic, H-2 harmless, H-6 residual]). Re-price any assumption with --set BLOCK:var=value: BLOCK is a note id, var a named variable in its python block. §6’s structural worry is that nearly every empirical likelihood traces back to one paper, S-1 (Giddings–Mangano), trust-capped at 0.74 — which caps both CG-1, the joint block over O-2/O-3/O-4, and the lone white-dwarf edge E-1. Distrust that paper and watch the danger mass move:
python3 .claude/skills/flf-epistack/runner/run.py content/v1/analysis-tests/black-holes1 --set CG-1:t_survival=0.3 --set E-1:t_wd=0.3
# posterior [0.1663, 0.7318, 0.1019] <- H-1's danger mass ×4.7
python3 .claude/skills/flf-epistack/runner/run.py content/v1/analysis-tests/black-holes1 --set CG-1:t_survival=0.0 --set E-1:t_wd=0.0
# posterior [0.3097, 0.5752, 0.115] <- distrust it entirely; recovers the prior exactly
Any disputed assumption is one flag from a re-run, and t=0 recovers the prior exactly — the ×4.7 figure §6 quotes is this command, not an assertion.
Self-check:
python3 .claude/skills/flf-epistack/runner/test_run.py
It rebuilds the step 7 and step 8 specs’ micro-examples and asserts the runner reproduces every number they publish, plus determinism, edge-order commutativity, and the sandbox guard rejecting import.
Curated entry points. Six links into the black-holes run, in reading order from the answer down to the dataset it rests on. It is a low-N shakedown run (§5.1), but every number below is computed, and its dependency-tracing is the most visible of the three.
- The main report: the answer, and what sits outside it.
- The cluster review: what moved the posterior, and whether the true answer is listed.
- The cluster itself. Three mutually-exclusive members, residual last, frozen order.
- The driver observation: the finding the safety case leans on hardest.
- The correlation group — read this one if you read one. One joint likelihood for three observations sharing a data basis, rather than three independent votes.
- The data basis: the premise all three rest on; its
known_biasesnames the shared failure mode.