Summary / 9. Causal reasoning / Document
2026-10-03
Shared copy: the operator's name and machine names are replaced by roles. Nothing else is changed.
Rendering…
# Piper Cognitive Component 9 — Causal Reasoning: Engineering Package Oct 3, 2026 · @Bink ## Status and summary No claim about Piper's causal ability, her graph, or the forty-edge result is supported yet: the current evidence cannot distinguish a thin graph from a broken instrument. The first job is to fix measurement, not to grow the graph. Why the existing numbers settle nothing: - **0/40 recovered.** The 95% Clopper–Pearson upper bound is 8.8%, so the graph side is near zero *on that procedure*. But the run had no ceiling or floor control on the same forty cases, so a zero is equally consistent with an empty graph, a query that cannot hit an intact edge, a normalization mismatch, or a scorer that cannot recognize a correct answer. - **Ceiling 5/5, separately 2/5.** Five cases cannot calibrate forty. 5/5 has a 95% lower bound of 48%; 2/5 spans 5–85%. Two different small samples disagreeing is what an unvalidated instrument looks like. - **`causal()` fails with an edge intact** (worker report). If reproduced, this is a query-path defect, and the 0/40 is uninterpretable until it is fixed. - **Zero tool calls; causal reasoning happens in worker prose.** This is a routing observation, not evidence that prose reasoning is better. That needs a matched comparison. - **Related prior finding.** The 2026-10-02 counterfactual review found `counterfactual.py` severs a node's *outgoing* edges (nullification), not its incoming edges (Pearl's do). Any "counterfactual surgery" result inherits that mismatch until fixed or relabelled. What this package delivers and what it cannot: | Delivered here | Needs Firefly | | --- | --- | | Operational definition, failure taxonomy, schemas, formulas | Actual graph schema, `causal()` source, extraction pipeline paths | | Verified research synthesis with links | Reconstruction of the forty-edge artifacts | | A reference SCM generator, exact oracle and scorer, cross-checked by an independent implementation | Running controls against the live graph | | Audit checklist and preregistered predictions per failure mode | Telemetry denominators and live results | Three questions are tracked separately throughout. Each has its own endpoint and none can stand in for another: 1. **Does Piper understand causation better?** Endpoint: generated-world accuracy on identification, intervention and counterfactual tasks, with abstention scored. 2. **Does the graph/tool contribute?** Endpoint: paired delta between graph and text-equivalent arms on the same worlds. 3. **Do real decisions improve at acceptable cost?** Endpoint: expected repair loss and sandbox repair success per dollar. The component is not improved because the graph grows, the tool gets called, or the prose sounds more scientific. ## Operational definition Causal reasoning, for Piper, is the ability to answer correctly — or correctly decline — three kinds of question about a system, using only the evidence and assumptions actually available. The three are distinct because each needs strictly more than the one before. | Rung | Question | Formal target | Needs | What it does *not* license | | --- | --- | --- | --- | --- | | Observation | What does seeing X tell us about Y? | P(y \| x) | Joint distribution or samples | Any claim about what changing X would do | | Intervention | What happens to Y if we set X? | P(y \| do(x)) | Causal graph + identification (back-door, front-door, do-calculus), or an experiment | Statements about a specific past case | | Counterfactual | In *this* episode, what would Y have been had X differed? | P(y\_x \| x′, y′) | Structural equations + a model of exogenous noise; abduction → action → prediction | Anything, if the mechanism family is unspecified — counterfactuals are generally not identified from graph + data alone | Adjacent notions that are often mislabelled as causal: - **Association / correlation** — symmetric, rung 1. - **Temporal precedence** — necessary for most causal claims, never sufficient; confounders also precede. - **Prediction** — a rung-1 quantity; a good predictor can be useless for intervention. - **Causal discovery** — inferring graph structure; from observational data, identifiable only up to a Markov equivalence class unless extra assumptions (non-Gaussian noise, equal variances, interventions) hold. - **Causal explanation** — a narrative selecting among causes; must be scored against the model, not for persuasiveness. - **Intervention planning** — choosing which do() to perform; a decision problem with costs. - **Root-cause diagnosis** — identifying which mechanism deviation, in this incident, would, if reversed, have prevented the symptom. This is a counterfactual (rung 3) claim about a single episode. A directed edge extracted from prose is a **causal claim** with a source, not a validated mechanism. The graph must say which it holds. ### Observable sub-capabilities Each is defined by a behavior a scorer can check without an LLM judge. | Sub-capability | Observable behavior | Primary metric | | --- | --- | --- | | Hypothesis formation | Lists the true cause among ≤ k hypotheses before testing | Recall@k of true root cause | | Confounder recognition | Names an open back-door path, or declines a causal reading of a correlation | Accuracy on association-vs-causation items | | Mechanism representation | States direction, sign, conditions and mediators that match the SCM | Edge/sign/condition F1 vs oracle | | Experimental discrimination | Picks the test that maximally separates live hypotheses | Information-gain regret | | Effect estimation | Returns the interventional quantity within tolerance | Absolute error vs exact oracle | | Intervention selection | Picks the repair that minimizes expected loss after cost | Decision regret | | Counterfactual consistency | Same-episode answer agrees with factual evidence (consistency axiom) and with the abduced noise | Absolute error; consistency violations | | Model revision | Retracts or reweights a hypothesis when an intervention contradicts it, keeping independently supported beliefs | Revision accuracy; collateral retractions | ### Boundary with Component 8 (inference) Component 8 owns *belief under incomplete information*: grounding, posterior updating, calibration, provenance, evidence acquisition and abstention. Component 9 reuses all of that unchanged. It adds only what requires a causal model: - distinguishing rung 1, 2 and 3 queries and refusing to answer one with the machinery of another; - identification — deciding whether a causal quantity is computable from what is available; - intervention and counterfactual computation; - choosing *interventions* as evidence, where Component 8 chooses *observations*. Shared infrastructure, no duplicates: Component 8's belief ledger stores causal hypotheses as ordinary beliefs with a `rung` field; its value-of-information code is extended, not copied, to cover interventional tests; its provenance schema is the edge provenance schema here. ## Research synthesis The literature points one way: formal causal benchmarks are hard for LLMs, generated benchmarks are easy to game by accident, and in incident diagnosis no causal-discovery method dominates. Piper's benchmark should therefore score identification and abstention, not only point answers, and must audit its own generator for shortcuts. ### Verified sources (pages opened 2026-10-03) | Source | Establishes | Assumptions / does not establish | Transfer to Piper and adaptation | Scoring | | --- | --- | --- | --- | --- | | [Pearl, *Causal diagrams for empirical research*, Biometrika 82(4):669–710, 1995](https://www.ics.uci.edu/~dechter/courses/ics-295cr/2022-23_Q2_Winter/reading/biometrika_1995.pdf) | Nonparametric framework in which a causal diagram is queried to decide whether available assumptions identify an effect from nonexperimental data; introduces the three-rule do-calculus | Assumes the diagram is correct. Says nothing about learning it. | The audit must ask whether `causal()` ever decides identifiability. Adaptation: an `identify(query, graph)` step that can return "not identifiable — need experiment on X". | Deterministic given graph | | [Jin et al., *CLadder*, NeurIPS 2023 (arXiv 2312.04350)](https://export.arxiv.org/abs/2312.04350); [data](https://huggingface.co/datasets/causalNLP/cladder), [code](https://github.com/causalNLP/cladder) | \~10K questions across associational, interventional and counterfactual rungs, generated from graphs with answers from an oracle inference engine then verbalized; LLMs found it hard | Small textbook graphs; binary variables; answers are yes/no so chance is 50%. Does not test evidence gathering or tools. | Use as an external smoke test of the bare model vs Piper. Adapt its generate-symbolic-then-verbalize pipeline for Piper's own worlds, adding a "not identifiable" label and tool access. | Deterministic, exact | | [Jin et al., *Corr2Cause*, ICLR 2024 (arXiv 2306.05836)](https://arxiv.org/abs/2306.05836v2); [data](https://huggingface.co/datasets/causalnlp/corr2cause), [code](https://github.com/causalNLP/corr2cause) | Task: given correlational statements, decide whether a causal hypothesis is implied. Seventeen LLMs scored near random; fine-tuning helped in-distribution but broke under variable renaming and paraphrase | Assumes faithfulness and causal sufficiency; label means "implied in every graph of the equivalence class". | Direct template for the association-vs-causation family. Its OOD collapse is the reason every Piper item needs semantic mutation variants (renaming, paraphrase, domain skin swap). | Deterministic | | [Hauser & Bühlmann, JMLR 13:2409–2464, 2012](https://jmlr.org/papers/v13/hauser12a.html) | Under reasonable intervention designs, interventional Markov equivalence is a strictly finer partition than observational, so experiments improve identifiability; gives GIES | Assumes known intervention targets and faithfulness. | Formal basis for scoring "choose a discriminating experiment": a good experiment shrinks the interventional equivalence class. Score structure against the I-essential graph, not the true DAG. | Deterministic | | [Zheng et al., *DAGs with NO TEARS*, NeurIPS 2018 (arXiv 1803.01422)](https://papers.nips.cc/paper/2018/hash/e347c51419ffb23ca3fd5050202f9c3d-Abstract.html); [code](https://github.com/xunzheng/notears) | Recasts DAG structure learning as continuous optimization with a smooth exact acyclicity constraint | Linear SEM by default; the paper itself does not claim causal identifiability (see next row). | A baseline arm for the discovery track only. Not a component to install in Piper. | Deterministic up to optimizer seed and threshold | | [Reisach, Seiler & Weichwald, *Beware of the Simulated DAG!*, NeurIPS 2021](https://neurips.cc/virtual/2021/poster/26590); [code](https://github.com/Scriddie/Varsortability) | In generically simulated additive-noise models, marginal variance tends to rise along causal order; a trivial sort-by-variance baseline matches continuous learners, which then fail on standardized data | — | **Direct threat to Piper's generator.** Any world with numeric observations must report varsortability, standardize, and include the sort-by-variance baseline. Same principle for text: report whether answer can be read off surface cues (node order, naming). | Deterministic | | [Pham, Ha & Zhang, *RCA for microservices based on causal inference: how far are we?*, ASE 2024](https://web3.arxiv.org/abs/2408.13729) | Evaluated nine causal-discovery and twenty-one RCA methods; no method stands out across situations, and synthetic-data performance may not reflect real systems | Metric data; microservice topologies. | Do not expect a discovery algorithm to rescue the graph. Keep synthetic and replayed-real tracks separate and report both. | Deterministic (ranking metrics) | | [Pham et al., *RCAEval*, WWW 2025 (arXiv 2412.17015)](https://huggingface.co/papers/2412.17015); [code](https://github.com/phamquiluan/RCAEval) | 735 labelled failure cases from three microservice systems, 11 fault types, fifteen reproducible baselines, coarse (service) and fine (indicator) evaluation | Labels are injected faults; root cause = injected fault by construction. | Source of replayable incidents for the end-to-end track and of non-LLM baselines (e.g. BARO). Adapt: wrap cases as tool-access episodes for workers. | Deterministic (AC@k, Avg@k) | | [Xu et al., *OpenRCA*, ICLR 2025](https://iclr.cc/virtual/2025/poster/32093); [code](https://github.com/microsoft/OpenRCA) | 335 failures, 68+ GB telemetry; LLM must name root-cause elements. Best model with a dedicated RCA agent solved about 11% | Some labels may not be recoverable from provided telemetry ([critique](https://www.traversal.com/blog/openrca-isnt-root-cause-analysis-why-that-matters)). | Model for the tool-mediated debugging track. Its authors also report models reaching correct answers through flawed reasoning ([talk](https://cs.hku.hk/seminars-events/eventdetail/509/-/rethinking-software-root-cause-analysis-with-llm-agents)) — the reason Piper is scored on the causal chain, not just the label. | Deterministic element match | **Lead, not fully read:** *OpenRCA 2.0* (arXiv 2606.27154, 2026) adds step-wise causal-path annotations and names "premature commitment" and "ungrounded diagnosis" as dominant agent failures ([summary](https://www.emergentmind.com/papers/2606.27154)). This post-dates my training; I saw only a third-party summary. Verify before relying on it — if it holds, it is the closest existing benchmark to Piper's end-to-end track. ### Foundational sources cited from memory — verify before external use These are standard references I did not re-open today. Titles and venues are believed correct; confirm before citing. - Pearl, *Causality*, 2nd ed., Cambridge University Press, 2009 — SCM semantics, the three-step counterfactual procedure, probabilities of causation. - Spirtes, Glymour & Scheines, *Causation, Prediction, and Search*, 2nd ed., MIT Press, 2000 — PC algorithm; faithfulness; output is a CPDAG. - Zhang, "On the completeness of orientation rules for causal discovery in the presence of latent confounders and selection bias", *Artificial Intelligence*, 2008 — FCI output is a PAG. - Shpitser & Pearl, AAAI 2006 — identification algorithm is complete; do-calculus is complete for identification. - Bareinboim & Pearl, "Causal inference and the data-fusion problem", *PNAS*, 2016 — selection bias and transportability. - Tian & Pearl, "Probabilities of causation: bounds and identification", 2000 — counterfactual quantities are often only bounded from data. ### What the literature does not settle for Piper No source above measures whether an agent *routes* to causal machinery when it should, or whether a stored causal graph helps an LLM agent beyond the same facts given as text. Those two questions are Piper-specific and are answered by the ablations below, not by citation. ## Instrument audit Audit before measuring: until a known-present edge is retrieved and scored correctly, every graph number is uninterpretable. The audit runs on Firefly; this section fixes what to inspect, the probe for each of the six failure modes, and what result would implicate each. ### A. Record what actually exists Read code, not `piper/cognition-parameter-map`. For each item, record the real path and a commit hash in the audit log. 1. `causal_edges` DDL: columns, types, indexes, uniqueness constraints, and whether direction is encoded by column order or a flag. 2. Writers: every code path that inserts or updates `causal_edges` (`grep -rn causal_edges`), with the extraction prompt used and the model that runs it. 3. Endpoint normalization: casing, lemmatization, entity IDs vs raw strings, alias table if any. Run `SELECT cause, COUNT(*) ... GROUP BY lower(trim(cause))` to find near-duplicate nodes. 4. `causal()` signature and body: lookup, BFS/DFS, scoring, or LLM call; hop limit; whether it matches on IDs, strings, or embeddings; what it returns on no match. 5. `counterfactual v1.0`: which edges surgery removes. The 2026-10-02 review found it severs *outgoing* edges; confirm on current HEAD. 6. `memory_graph_score.py`: what it withholds, what it queries, what it counts as a hit, and how it parses the response. 7. Router: the condition under which any worker or Piper herself calls `causal()`; why the count is zero. ### B. Edge semantics census Sample 100 edges uniformly at random (seed logged). Two raters, blind to each other, label each: | Field | Values | | --- | --- | | Relation kind | causal claim · correlation · dependency ("requires") · chronology ("then") · part-of · other | | Direction correct vs source text | yes · reversed · undirected in source | | Provenance retained | source ID · time · scope/conditions · confidence · contrary evidence — each yes/no | | Mechanism status | asserted · inferred by extractor · experimentally supported | Report Cohen's κ. If more than 20% of edges are not causal claims, the graph is a mixed relation store and "causal recall" is the wrong metric for it. ### C. Probes that separate the six failure modes Each probe uses the unchanged production query path and is run before any repair. | # | Failure | Probe | Signature that implicates it | | --- | --- | --- | --- | | 1 | Never acquired | For each of the forty withheld edges, search raw episode and source stores (not the graph) for any text stating it | Edge absent from all sources → acquisition, not graph | | 2 | Extraction / storage lost it | Re-run the extractor on the source text found in probe 1; diff against stored rows | Source present, extractor output lacks it or corrupts direction/endpoint | | 3 | Query failed to retrieve | Insert the exact target edge as a row; call `causal()` with the scorer's exact wording | Row present, `causal()` returns nothing or wrong → query defect (the worker report) | | 4 | Retrieved but misreasoned | Log the raw `causal()` return and the agent's final answer separately | Correct edge in tool output, wrong final answer | | 5 | Machinery bypassed | Count causal opportunities (independent detector, see Runtime instrumentation) vs `causal()` calls | Opportunities > 0, calls = 0 (already observed) | | 6 | Scorer wrong | Feed the scorer hand-written correct, reversed, aliased and empty answers | Scorer marks correct as miss or empty as hit | At least probes 3 and 6 are cheap and decisive. Run them first: if either fails, stop and fix before running anything else. ### D. Query behavior unit tests Run against a scratch copy of the database, never production. - Intact direct edge A→B, query "what does A cause" → must return B. - Same edge, query "what causes B" → must return A. - Reversed query "does B cause A" → must answer no or not-supported, not yes. - Alias: edge stored as "disk full", query "disk space exhausted" → document behavior; decide whether alias handling is in scope. - Two-hop A→B→C, query A→C → must return the path, and must not invent A→C as a direct edge. - Polarity: "A prevents B" stored; query "does A cause B" → must not return yes. - Conditional edge "A causes B when C" → returned with condition, or flagged as conditional. - Missing node, malformed input, empty string → defined error, not a silent empty result scored as a miss. ### E. Held-out procedure and leakage What "withheld" means decides what recovery means. Record which of these the forty-edge run removed: database rows only · source text · episodes · summaries · embeddings · the whole mechanism. - If only rows were removed, the source text is still there, and recovery means *re-extraction*, not *reasoning*. - If source text was also removed, ask what legitimate evidence remains. If none, 0/40 is the correct answer and says nothing about the graph. - Leakage scan: for each withheld edge, grep summaries, belief ledger, embeddings' source text, and other edges for both endpoints co-occurring. Any hit is a leak path; list it. ### Reporting rule Report four numbers separately, never one blended score: graph validity (census), graph coverage (probe 1–2), query reliability (probe 3 + §D), agent reasoning (probe 4–5). ## Matched controls The forty-edge result becomes interpretable only when six conditions run on the same forty cases with identical query wording, schema and scorer. The positive control must pass essentially perfectly, because it tests deterministic lookup; anything less is an instrument defect, not a finding about the graph. ### The control matrix Each row changes only the graph contents. Query text, harness, parser and scorer are byte-identical across rows (hash them in the run manifest). | Condition | Graph contents | Expected if instrument works | Pass bar (n = 40) | If it fails | | --- | --- | --- | --- | --- | | C0 Original | Production graph, target edges withheld as in the original run | Unknown — this is the measurement | — | — | | C1 Target present | C0 plus the exact target edge row | Every target recovered | 40/40, or each miss traced to a named defect | Query or scorer defect (probes 3, 6) | | C2 Minimal oracle | Only the target edge and its endpoints | Every target recovered | 40/40 | Defect independent of graph noise | | C3 Full oracle | The complete true mechanism for each case, including multi-hop paths | Every target, including via paths | ≥ 38/40; misses explained | Traversal or hop-limit defect | | C4 Empty / unrelated | Empty graph, then a graph of unrelated edges of equal size | Nothing recovered | 0/40 false positives | Scorer accepts wrong or empty answers | | C5 Corrupted | Target edge reversed, then sign-flipped, then endpoint swapped to a sibling | Reported as absent or contradicted | ≤ 1/40 accepted per corruption | Direction or polarity not checked | C1–C5 diagnose the instrument. None of them is evidence that Piper discovered anything. ### Run each case through four separated stages Log the output of every stage so a failure is located, not inferred. 1. **Database lookup** — direct SQL for the target: present or not. 2. **Tool response** — raw return of `causal()`. 3. **Harness parse** — what the harness extracted from the tool response. 4. **Score** — scorer verdict. A case where stage 1 is present but stage 4 is a miss is localized by the first stage whose output is wrong. No case is dropped: a crash, timeout or unparseable output is logged as its own outcome class and counted. ### Case coverage within the forty Tag each case and report results per tag, so a pass on easy cases cannot hide a failure on hard ones: direct edge · 2-hop · 3+-hop · alias in query · negative polarity · conditional edge · reversed query · unsupported relation (should return none). ### If the original artifacts are missing Say so in the audit log. Build a fresh cohort of forty, generated before looking at any results, stratified by the tags above in fixed proportions, with the seed and selection rule committed first. Do not substitute five easy cases and call them the ceiling of the original forty — the reviewer's 2/5 vs 5/5 shows why. ### Validate the oracle and scorer before using them on Piper - Twenty hand-computed cases with answers written down before running. - A second, independently written scorer (different author or model, no shared code). The two must agree on 100% of the hand cases and on ≥ 99% of 1,000 generated cases; every disagreement is examined. - Report the scorer's own confusion matrix on C1, C4 and C5. ## Generated-world benchmark Every item comes from a known structural causal model whose exogenous noise is finite, so observational, interventional and counterfactual answers are computed exactly by enumeration. Piper sees only the public problem; the evaluator holds the equations, the sampled episode and the answer. ### World model - **Variables** are binary in v0. Each endogenous variable V has exogenous U\_V that picks one *response function* from parents to {0,1}. Latent confounders are unobserved roots. - **Mechanism families:** noisy-OR (encoded exactly as a mixture of response tables), AND-interaction with failure noise, and random response mixtures for random DAGs. - **Counterfactuals** use abduction → action → prediction over the *same* episode's exogenous state: keep only U consistent with the factual evidence, intervene, re-solve. A fresh post-intervention draw is a rung-2 answer and is scored wrong on rung-3 items. - **Numeric exact tasks** expose the full mechanism (parents and response-table probabilities). **Structure tasks** expose only samples or the observed joint and are scored against the identified equivalence class. - **Public listing order is shuffled.** The reference generator originally listed variables in causal order; edge direction agreed with listing position 100% of the time. After shuffling: 49%. Any surface cue that predicts the answer must be audited the same way (variable naming, sentence order, domain priors). ### Item types and when "not identifiable" is correct | Item | Agent sees | Reference answer | | --- | --- | --- | | Exact rung-1/2/3 | Full mechanism | Exact probability, tol ±0.02 | | Identification from observation | Graph (possibly with latent X↔Y) + observed joint | Value if identifiable (back-door/front-door), else `not_identifiable` | | Discovery | Samples or joint, stated assumptions | Markov (or interventional) equivalence class, not the true DAG | | Counterfactual without mechanism | Graph + data only | Usually `not_identifiable`, or bounds when the item asks for bounds | | Diagnosis | Incident telemetry via tools, budgeted experiments | Root cause, or the identified set if evidence cannot separate causes | Non-identifiability is proved per item, not asserted: the generator builds a **witness** SCM with the same observed distribution but a different interventional answer. Items are kept only when the witness gap exceeds the tolerance, so answering anyway is detectably wrong. ### Task families and generator coverage | Family | Latent structure | Reference generator v0 | | --- | --- | --- | | Association vs causation | fork, latent confounder | built | | Confounding / reverse causation | confounded, latent\_confounded, reversed-query variants | built | | Collider / selection | collider; selection by conditioning on C | built | | Mechanisms and mediation | mediation (direct + indirect) | built; NDE/NIE estimands to add | | Multi-hop chains | chain, random DAG | built | | Interventions | all motifs, do() items | built | | Counterfactuals | all motifs, same-episode items | built | | Multiple causes / interaction | AND-interaction | built | | Root-cause debugging | incident simulator over SCM | next stage | | Negative evidence | sensor coverage + delay nodes | next stage | | Active experiments | hypothesis set + experiment menu + costs | next stage | | Sequential revision | evidence and interventions released in stages | next stage | | Provenance / source dependence | duplicated reports of one source | next stage | | Insufficient evidence | witness-backed non-ID items | built | | Tool-mediated reasoning | same items behind log/graph/calc/sandbox tools | next stage | Domain skins rename variables only (Linux incident, Binx washroom ops, logistics, sensors, physics, fiction) over the same latent structure. A skin must not add causal information the structure lacks; test this by running each item under a nonsense skin ("blorp", "quix") and flagging items where accuracy differs by more than noise. ### Data contracts Five records, each versioned. Private records never enter Piper's process, memory, or the self-improvement loop's readable paths. ```json {"public_case": { "case_id": "w0412-q3", "world_id": "w0412", "bench_version": "0.1.0", "family": "confounding", "rung": "intervention", "skin": "linux", "stage": 1, "question": "If we force swap_pressure high, what is P(oom_kill)?", "evidence": {"kind": "mechanism|joint|samples|telemetry", "ref": "blob:..."}, "assumptions": ["no unmeasured confounding except as shown"], "allowed_tools": ["calc", "graph", "logs", "sandbox"], "permitted_interventions": [{"id": "set_swap", "cost": 2.0}], "answer_format": "status|value|label|confidence|experiment"}} ``` ```json {"private_oracle": { "case_id": "w0412-q3", "scm_hash": "sha256:...", "seed": 412, "estimand": "P(Y=1|do(X=1))", "identifiable": true, "value": 0.374, "tol": 0.02, "identified_set": null, "witness_hash": null, "factual_episode": {"u": {"...": 0}}, "cf_link": "w0412-q2", "expected_loss": {"set_swap": 3.1, "rollback": 5.0}, "eig_bits": {"exp_a": 0.62, "exp_b": 0.11}}} ``` ```json {"agent_response": {"case_id": "...", "arm": "piper_current", "status": "answer|not_identifiable|abstain|need_info", "value": 0.37, "label": null, "confidence": 0.7, "experiment": null, "edges": null, "claim_text": "...", "t": "2026-10-03T14:02:11Z"}} ``` ```json {"tool_event": {"case_id": "...", "arm": "...", "seq": 4, "tool": "causal", "args": {}, "result_hash": "...", "tokens_in": 812, "tokens_out": 96, "latency_ms": 340, "cost_usd": 0.0004}} ``` ```json {"run_manifest": {"run_id": "...", "bench_version": "0.1.0", "generator_commit": "...", "scorer_hash": "...", "harness_hash": "...", "model": "deepseek-...", "arm": "...", "split": "dev|val|test", "world_ids": ["..."], "budget": {"tokens": 20000, "tool_calls": 12}, "exclusions": []}} ``` ## Deterministic scorer The reference oracle and scorer pass every validation check after one correction to my own hand-written key, and the control harness detects all three injected instrument defects. No LLM grades anything; every endpoint below is computed from the agent's structured response and the private oracle. ### Endpoints and formulas Numeric causal quantity (rung 1, 2 or 3), tolerance τ = 0.02: ```latex \text{correct}_i = \mathbb{1}\big[\,|\hat v_i - v_i| \le \tau\,\big], \qquad \text{AE}_i = |\hat v_i - v_i| ``` Identification is a three-way outcome. Each response falls in exactly one cell: | Truth \\ response | value | `not_identifiable` | abstain / need\_info | | --- | --- | --- | --- | | Identifiable | correct or wrong (by τ) | false non-ID | abstain | | Not identifiable | **false answer on non-ID** | correct non-ID | abstain | Calibration on any item with a stated confidence c: ```latex \text{Brier} = \frac{1}{n}\sum_i (c_i - \mathbb{1}[\text{correct}_i])^2 ``` Decision regret for repair or intervention choice a, with expected loss including cost under the posterior given only the evidence delivered so far: ```latex \text{Regret}(a) = \mathbb{E}[L(a)\mid e] - \min_{a'} \mathbb{E}[L(a')\mid e] ``` Information regret for experiment choice x over hypothesis set H (a separate endpoint — information and decision value are not interchangeable): ```latex \text{EIG}(x) = H(\mathcal H) - \mathbb{E}_{o\sim p(o\mid x)}\,H(\mathcal H\mid o), \qquad \text{IRegret}(x) = \max_{x'}\text{EIG}(x') - \text{EIG}(x) ``` Structure: directed precision, recall and F1 against the true DAG; reversed-edge count; and `same_mec` (identical skeleton and v-structures) against the equivalence class the evidence identifies. Exact-DAG recovery is reported only when the item's assumptions identify the DAG. Diagnosis outcomes distinguish being right from being justified: | Outcome | Meaning | | --- | --- | | correct | Named cause is the true cause *and* the delivered evidence identifies it uniquely | | lucky | Named the true cause, but evidence left several causes indistinguishable | | consistent\_unjustified | Named a cause in the identified set that was not the true one | | justified\_abstain | Asked for more evidence when evidence did not identify the cause | | wrong | Named a cause outside the identified set | A restart that happens to clear the symptom is not evidence for the claimed root cause; only the oracle's identified set decides. Revision items (multi-stage): score each stage against the evidence available at that stage. Report revision accuracy (changed when the new evidence should change the answer), stale-belief rate (kept an answer the new evidence refutes), and collateral retraction rate (dropped a belief that remains independently supported). ### Validation of the reference implementation Run with `python3 validate.py` in the attached bundle. All checks pass on the final version. | Check | Method | Result | | --- | --- | --- | | Hand-computed cases | 11 answers derived by hand before running: confounding, probability of necessity, collider, consistency axiom | 11/11 after one correction (below) | | Oracle vs independent implementation | Monte Carlo twin network, 200,000 draws, separate code reading only the shuffled public JSON; 60 worlds (42 motifs + 18 random 6-node DAGs, half with latents) | 359 queries across all three rungs; max \|z\| = 3.21, none beyond the 5σ alarm | | Back-door adjustment from observed joint | Computed only from the observed table, compared with the do-oracle | 80 queries, max difference 4.4×10⁻¹⁶ | | Non-identifiability witnesses | Same P(X,Y), unconfounded model | 34/40 worlds differ in P(Y\|do X) by > 0.02; median gap 0.041 | | Scorer self-tests | Correct, out-of-tolerance, answer-on-non-ID, false non-ID, empty value, lucky label, justified hedge, MEC equivalence, regret edge cases | 12/12 | Two findings from this run change the design: - **My hand key was wrong once.** I wrote P(X=1 | Y=1, C=1) = 2/3 for C := X OR Y. The oracle said 1/2. Re-deriving: given Y=1, C=1 is certain, so conditioning on C adds nothing. The independent check exists precisely for this; a single hand-written key would have silently mis-scored every agent that got it right. - **Default confounding is too weak.** A median witness gap of 0.041 is barely twice the tolerance. The generator must draw confounder strengths from a range that makes rung confusion costly (target median gap ≥ 0.10), and drop items below the tolerance. ### Control-matrix demonstration `controls.py` runs the six-condition matrix on forty tagged cases with two query implementations and two scorers. The naive query uses exact strings, one hop and ignores direction; the lenient scorer counts a hit when both endpoints appear anywhere in the returned path. | Query × scorer | C1 target present | C5 reversed: false accepts | C5 sign-flipped: false accepts | | --- | --- | --- | --- | | naive × lenient | 22/40 | 20 | 20 | | naive × strict | 14/40 | 12 | 12 | | fixed × lenient | 40/40 | 0 | 36 | | fixed × strict | 40/40 | 0 | 0 | The naive query passes all 12 direct-edge cases and fails every 2-hop, 3-hop, alias, polarity and conditional case. **A five-case ceiling drawn from direct edges would read 5/5 on a broken instrument** — the reviewer's 2/5-then-5/5 is what sampling from a mixed pool looks like. The lenient scorer hides polarity errors completely: 36 false accepts on sign-flipped edges with a correct query. One harness bug was also found and fixed: flipping the sign of *every* edge on an even-length path cancels and is not a corruption. The C5 sign-flip now flips one edge per path. ## Runtime instrumentation Log causal opportunities with a detector that does not share code or prompts with the router, and write every claim and outcome to an append-only store Piper cannot edit. Graph calls are a diagnostic count, never a reward. ### Events One table per event type, all keyed by `episode_id` and `seq`, written by a logger running as a separate Unix user from Piper and her workers. Piper's process gets INSERT on a socket, not file access. ```sql CREATE TABLE causal_opportunity ( episode_id TEXT, seq INT, t TEXT, detector_version TEXT, -- independent of router kind TEXT, -- why | what_if | which_fix | root_cause | will_X_help text_span_hash TEXT, routed_to TEXT -- filled after the fact: causal() | graph | prose | none ); CREATE TABLE causal_claim ( claim_id TEXT PRIMARY KEY, episode_id TEXT, seq INT, t TEXT, author TEXT, -- worker id or piper rung TEXT, -- observation | intervention | counterfactual cause TEXT, effect TEXT, polarity TEXT, condition TEXT, confidence REAL, status TEXT, -- hypothesis | asserted | retracted evidence_ids TEXT, -- what was in context at claim time competing_ids TEXT, -- alternatives recorded alongside prev_hash TEXT, hash TEXT -- hash chain ); CREATE TABLE causal_tool_call (episode_id TEXT, seq INT, tool TEXT, args_hash TEXT, result_hash TEXT, returned_edges INT, latency_ms INT, cost_usd REAL); CREATE TABLE experiment (exp_id TEXT, episode_id TEXT, proposed_seq INT, executed_seq INT, -- NULL if never run discriminates TEXT, predicted TEXT, observed TEXT, cost REAL); CREATE TABLE intervention_prediction (pred_id TEXT, claim_id TEXT, action TEXT, predicted_effect TEXT, p REAL, executed INT, observed_effect TEXT, resolved_t TEXT, resolver TEXT); CREATE TABLE graph_write (episode_id TEXT, seq INT, op TEXT, cause TEXT, effect TEXT, provenance TEXT, source_claim_id TEXT); CREATE TABLE model_revision (rev_id TEXT, claim_id TEXT, old_status TEXT, new_status TEXT, trigger_evidence_id TEXT, seq INT); ``` ### Denominators that do not depend on the router A zero-call count means nothing without a denominator. The opportunity detector is a fixed regex pass over worker and Piper transcripts ("why", "because", "caused by", "root cause", "what if", "would have", "if we restart") followed by a small classifier, frozen by version. Measure its quality monthly on 100 hand-labelled turns: report precision and recall; if recall falls below 0.8, opportunity coverage numbers carry that caveat. Routing rate = opportunities with a `causal()` call ÷ opportunities. Report it beside, never instead of, the useful-conclusion rate below. ### Separate intent, action and outcome - `experiment.proposed_seq` without `executed_seq` is an intention. Count executions only when a tool event confirms it. - `intervention_prediction.observed_effect` is filled by an independent resolver (system telemetry, a sandbox result, or Bink), never by Piper's later statement that she was right. - `resolver` records which. A prediction with no resolver is **unresolved**, not failed and not successful; report resolution coverage as a first-class number. ### What counts as useful A verified useful causal conclusion is a `causal_claim` with status asserted whose `intervention_prediction` resolved as predicted, *or* whose root cause was confirmed by an independent fix-and-recheck. Report the count per week and per 100 opportunities. ### Test isolation Benchmark episodes run under a separate `episode_id` namespace in a scratch database and memory store. A pre-run and post-run row count on production memory tables must match exactly; any difference fails the run. ## Diagnostic hypotheses and ranked upgrades Repair the instrument first, then give workers a lightweight competing-cause protocol, then decide whether the graph earns a place. Graph growth comes last and only if the text-equivalent ablation shows the graph adds something. ### Diagnostic hypotheses (test before building) Each is falsifiable by a probe already specified. Predictions are written now so results cannot be reinterpreted afterwards. | ID | Hypothesis | Test | Supports it | Refutes it | | --- | --- | --- | --- | --- | | D1 | `causal()` cannot retrieve an intact edge under some query forms | Probe 3, §D unit tests | Any C1 miss traced to query | C1 = 40/40 | | D2 | The 0/40 reflects withheld *source text*, so nothing was recoverable | Audit §E | Source text absent for ≥ 35/40 | Source present for most | | D3 | Edges are a mixed relation store, not causal claims | Census §B | > 20% non-causal edges | ≤ 5% | | D4 | Scorer accepts reversed or sign-flipped answers | C5 | Any false accept | 0 false accepts | | D5 | Counterfactual surgery severs outgoing edges | Inspect + rung-2 items with known parents | do(X) changes X's parents' downstream | Matches oracle | | D6 | Zero calls is a routing gap, not a capability gap | Ablation: Piper vs Piper + forced graph access on graph-answerable items | Forced access raises accuracy | No change | | D7 | Workers commit to the first plausible cause | Count competing hypotheses recorded before first fix in replayed incidents | Median = 1 | Median ≥ 2 | | D8 | The bare model already matches Piper on causal items | Bare vs current Piper, matched | Difference within noninferiority margin | Piper clearly better | ### Ranked proposals | Rank | Proposal | Gated on | Primary endpoint | Cost | | --- | --- | --- | --- | --- | | 1 | Query, normalization and direction repair | D1 | Control matrix pass rate | Hours | | 1 | Strict scorer plus independent second scorer | D4 | Scorer confusion on C1/C4/C5 | Hours | | 2 | Worker debugging protocol: ≥ 2 competing causes + one discriminating check before a fix | D7 | Expected repair loss on replayed incidents | 1–2 days | | 3 | Rung-typed queries and a calc/identify tool | — | Accuracy on exact and non-ID items | 2–3 days | | 4 | Correct do-surgery; rename current op "nullification"; return `not_identifiable` for counterfactuals without mechanisms | D5 | Rung-2/3 accuracy via graph arm | 1 day | | 5 | Typed, scoped, provenance-preserving edges | D3 | Census validity; revision accuracy | 2–4 days | | 6 | Conditional routing gate to the graph | D6 | Useful conclusions per 100 opportunities | 1–2 days | | 7 | Decision-value experiment selection | 2 | Decision regret | 3–5 days | | 8 | Dependency-aware revision and failure memory | 5 | Stale-belief and collateral-retraction rates | 3–5 days | | 9 | Better extraction with independent verification | 5 + graph shown useful | Edge precision on census | Days–weeks | ### Proposal details Every proposal runs behind a feature flag, on dev worlds first, and is decided on a fresh validation cohort. **P1 — Query repair.** Hypothesis: normalized, direction-aware, multi-hop, polarity-aware lookup raises C1 from its measured value to 40/40 without new C4/C5 false accepts. Control: current `causal()`. Independent variable: query implementation only. Endpoint: control-matrix pass rate on 40 matched cases plus 200 generated tagged cases. Power: deterministic, so the bar is 100% with every miss explained. Failure mode: alias table over-merges distinct nodes, raising C4 false accepts. Acceptance: all C1–C3 pass; zero false accepts. Cost ceiling: no added LLM calls. Rollback: any production `causal()` latency p95 > 2× baseline. **P2 — Worker debugging protocol.** Hypothesis: requiring each debugging worker to record at least two competing causes and run one check that separates them, before any repair, lowers expected repair loss. It targets the premature-commitment failure the OpenRCA authors describe. Control: current worker prompt. Endpoint: decision regret on simulated incidents; secondary: justified-correct rate and "lucky" rate. Power: about 200 independent worlds detects a 10-point paired improvement at 25% discordance (two-sided α = 0.05, power 0.8, McNemar). Failure mode: ritual compliance — two hypotheses where the second is a strawman; detect by scoring whether the second hypothesis is in the oracle's live set. Acceptance: regret reduced by ≥ 15% with non-inferior latency (≤ +30%). Cost ceiling: +25% tokens per incident. Regression guard: no drop in time-to-first-useful-action on easy incidents. Rollback: canary repair success falls ≥ 5 points. **P3 — Rung typing and identify/calc tool.** Hypothesis: classifying each causal question as observation, intervention or counterfactual before answering, with a calculator for exact items, reduces rung confusion. Control: bare reasoning. Endpoint: accuracy and false-answer-on-non-ID rate. Failure mode: over-abstention; watch false non-ID rate. Acceptance: false-answer-on-non-ID halved with false non-ID ≤ 5%. **P4 — Surgery correctness.** Hypothesis: severing *incoming* edges to X reproduces oracle do-answers on graph-represented worlds. Endpoint: absolute error on rung-2 items via the graph arm. Acceptance: matches oracle within τ on all items where the graph holds the full mechanism. If the graph stores no numeric mechanisms, the module must return `not_identifiable` for numeric counterfactuals rather than a number. **P5 — Typed edges.** Add `relation_kind`, `polarity`, `condition`, `status` (asserted | inferred | tested), `source_id`, `observed_at`, `scope`, `contrary_ids`. Endpoint: census validity and revision accuracy. Failure: migration silently drops rows; guard with pre/post row counts and a sample diff. **P6–P9** follow the same template and are not specified further until their gates pass. Nothing here runs on every turn: the protocol applies to debugging workers, the graph to routed causal opportunities, the calculator to exact items. ## Baseline and ablation matrix The single most important comparison is graph versus the same facts as plain text: it is the only one that separates "the graph helps" from "more information helps". Every arm uses the same model, worlds, evidence access, budget and initial state. | Arm | Model | Prompt | Causal facts available | Graph tool | Worker protocol | | --- | --- | --- | --- | --- | --- | | A0 Bare | delegate | minimal | public case only | no | no | | A1 Bare + equivalent prompt | delegate | Piper's causal instructions, no tools | public case | no | no | | A2 Current Piper | Piper | current | public case + production memory (scratch copy) | as is | as is | | A3 Piper − graph | Piper | current | same as A2 minus graph | removed | as is | | A4 Piper − counterfactual module | Piper | current | same as A2 | graph yes, cf module off | as is | | A5 Piper + repaired query | Piper | current | A2 | P1 | as is | | A6 Piper + routing gate | Piper | current | A2 | P1 + P6 | as is | | A7 Piper + worker protocol | Piper | current | A2 | as is | P2 | | A8 Piper + typed edges | Piper | current | A2 | P1 + P5 | as is | | A9 Text-equivalent | Piper | current | same edges rendered as sentences in context | no | as is | | A10 Oracle graph | Piper | current | true mechanism in graph | P1 | as is | | A11 Combined candidate | Piper | current | A2 | accepted set | accepted set | | A11−k | Leave-one-out of each accepted component | | | | | ### What each comparison isolates | Comparison | Isolates | | --- | --- | | A1 − A0 | Prompt content | | A2 − A1 | Piper's architecture beyond prompting (memory, tools, workers together) | | A2 − A3 | Graph contribution as it exists today | | A10 − A9 | Graph **format** with information held equal | | A9 − A3 | Access to the causal facts, regardless of format | | A10 − A5 | Graph **content** (oracle vs production) with the same query path | | A5 − A2 | Query repair alone | | A6 − A5 | Routing alone | | A7 − A2 | Worker reasoning protocol alone | | A11 − A11−k | Marginal value of component k inside the combination | ### Rules - Reset state between independent worlds; keep state within a multi-stage episode. - Record per arm the evidence actually obtained (tool events), so an arm that fetched more logs is not credited with better reasoning. - Benchmark memories go to the scratch namespace and are deleted after the run. - Analyse paired by world. Clustered bootstrap over independently generated worlds for intervals. Stages within a world, domain skins of one world, and repeated samples are not independent cases. - An inconclusive difference is reported as inconclusive with its interval. It is not evidence of equivalence and not grounds to delete a component; equivalence requires the interval to sit inside the preregistered margin. ## Acceptance, regression and contamination Thresholds are fixed after a pilot that estimates variance and before any candidate is compared; each intervention is judged on the endpoint it targets, with guardrails against regression elsewhere. ### Preregistration sequence 1. Pilot: A0, A2 and A9 on 60 dev worlds, two samples each. Estimate per-endpoint variance and discordance rates. 2. From the pilot, write down for each candidate: primary endpoint, useful-effect threshold, noninferiority margins on guardrails, sample size, and the analysis script hash. Commit before running validation. 3. Run validation once per candidate. Final test runs once per release. ### Starting margins (revise only at step 2) | Candidate type | Primary endpoint | Useful effect | Guardrails (noninferiority margin) | | --- | --- | --- | --- | | Query / scorer repair | Control-matrix pass rate | 100%, every miss explained | Production `causal()` latency ≤ 2× | | Reasoning aid (rung typing, calc) | Accuracy on exact + non-ID items | +8 points paired | False non-ID ≤ +3 pts; tokens ≤ +30% | | Debugging policy | Expected repair loss | −15% | Useful coverage ≥ −3 pts; latency ≤ +30%; tokens ≤ +25% | | Graph / routing | Useful conclusions per 100 opportunities | +5 | Non-causal task accuracy (Component 8 suite) ≥ −2 pts | State integrity is a hard gate for every candidate: zero unexpected writes to production memory during evaluation. ### Splits and contamination | Split | Use | Who can read it | | --- | --- | --- | | Dev | Unlimited iteration, including by Piper's self-improvement loop | Piper + loop | | Validation | Candidate decisions; count every look | Harness only | | Fresh final test | Once per release, regenerated from new seeds and held-out structures | Harness only | | Live canary | Bounded, reversible rollout | Telemetry | - Procedural generation with private seeds; held-out *structure* families (e.g. front-door, M-bias) in final test that never appear in dev. - Transfer checks: domain skin swap, nonsense skin, variable renaming and paraphrase (the Corr2Cause failure). - Private oracle files live under a Unix user Piper cannot read. The loop receives aggregate scores only, never per-item answers. - Scan Piper's memory, belief ledger and summaries after each validation run for world IDs, variable names unique to validation worlds, or oracle values. Any hit retires that cohort. - Adaptive exposure: every candidate scored on a validation cohort leaks a little about it, even if each is scored once. Retire the cohort after 20 candidate evaluations or one month, whichever comes first. - Report all exclusions, harness failures and selection rules with every result. ## Shadow evaluation and weekly report Shadow mode can grade predictions about things that happen anyway; it cannot grade the effect of a repair nobody executed. Interactive policy effects need sandbox replay first and a bounded, reversible rollout second. ### Three tiers 1. **Shadow.** For each live incident or causal question, record Piper's claims, competing hypotheses, probabilities and predicted intervention effects at claim time. Resolve later from independent outcomes (telemetry, Bink's confirmation, a subsequent confirmed fix). Audit a random 10 apparent successes and 10 unresolved cases each week by hand. 2. **Sandbox replay.** Replay captured incidents in a container snapshot; let the candidate policy run experiments and repairs; score repair success, side effects and regret against the simulator's ground truth. 3. **Bounded canary.** Only reversible actions (config toggles, restarts with rollback), capped at N incidents per week, with automatic rollback on the criteria in the ranked-upgrades section. Expensive or risky changes ask Bink first, consistent with Piper's presence loop. ### Weekly report (one page) | Block | Metrics | Note | | --- | --- | --- | | Bench | Bench version; paired deltas vs A2 and A9 with 95% intervals | Dev and validation shown separately | | Coverage | Opportunities detected; routing rate; detector precision/recall (monthly) | Denominator from independent detector | | Query reliability | Control matrix pass rate; false accepts on C4/C5 | Should be 100% / 0 after P1 | | Claim support | Share of causal claims with evidence IDs; rung-labelled; competing causes recorded | From `causal_claim` | | Experiments | Proposed vs executed; discriminating (in live set) share; outcome matched prediction | Intentions are not executions | | Diagnosis and repair | Justified-correct, lucky, wrong, unresolved; sandbox repair success; canary success | Lucky reported separately | | Revision and provenance | Revision accuracy; stale beliefs; collateral retractions; source-duplication flags | — | | Cost | Tokens, tool calls, latency, $ per incident and per useful conclusion | — | | Resolution coverage | Resolved ÷ claims older than 7 days | Low coverage caps every other number | The report ends with the three questions, each answered yes / no / inconclusive with its endpoint and interval: 1. Does Piper understand causation better? 2. Does her graph/tool contribute? 3. Do real decisions and debugging outcomes improve at acceptable cost? ## Failure taxonomy A label is assigned only when a matched diagnostic variant or a traced operation shows the mechanism; a wrong answer alone gets the label `unexplained`. The "evidence" column is the test that licenses each label. | Label | Evidence that licenses it | | --- | --- | | Correlation-as-causation | Wrong on fork item; correct on matched item where the edge is real | | Reverse causation | Correct on A→B, asserts A→B on the reversed-structure twin | | Confounding neglect | Answers P(y\|x) where P(y\|do x) was asked; correct when Z is removed | | Collider / selection bias | Conditions on C and reports induced dependence; correct on unconditioned twin | | Mediator misuse | Adjusts for M when total effect asked; correct when M is unobserved | | Temporal-order shortcut | Wrong on item where order and direction disagree; correct where they agree | | Mechanism fabrication | Claims an edge or path absent from all delivered evidence (traced) | | Symptom / root-cause confusion | Names a descendant of the true cause; counterfactual on that node does not prevent symptom | | Omitted joint cause | Interaction world: names one of two necessary causes as sufficient | | Observation / intervention confusion | Rung-2 answer equals the rung-1 value on a confounded world | | Intervention / counterfactual confusion | Rung-3 answer equals the population rung-2 value instead of the abduced one | | Incorrect noise reuse | Rung-3 answer computed with fresh exogenous draws (matches resampled, not abduced, value) | | Non-identifiability ignored | Returns a value on a witness-backed non-ID item | | Source duplication | Treats N copies of one report as N independent sources (provenance trace) | | Stale mechanism | Uses a mechanism revised in an earlier stage of the same episode | | Unsupported certainty | Confidence ≥ 0.9 on wrong or non-ID items (calibration bin) | | Experiment avoidance | Commits to a repair when the identified set has ≥ 2 members and a cheap discriminating test exists | | Costly irrelevant experiment | Executed experiment with EIG ≈ 0 over the live set | | Graph retrieval failure | Edge present in DB, absent from tool output (stage log) | | Routing bypass | Detector flagged opportunity; no tool call; graph held the answer | | Oracle leakage | Validation world identifiers found in Piper's memory or reasoning | | Scorer / harness failure | Stage 2 output correct, stage 4 verdict wrong; or harness exception | The rung-confusion labels are detectable mechanically because the oracle computes the rung-1, rung-2, resampled and abduced values for every item; a response matching the wrong one within τ gets that label. ## Cost report Evaluation compute is negligible; model tokens dominate, led by the one-time full ablation matrix and the debugging-policy validation. The token figures are planning estimates, not measurements — replace them with pilot numbers. ### Measured (reference bundle, this container) | Component | Measured cost | | --- | --- | | Exact oracle, 6 observed + 1 latent node, per query | 6.0 ms | | Full validation suite (hand cases, 359 MC queries at 200k draws, back-door, witnesses, scorer tests) | 4.9 s | | Control matrix, 8 conditions × 40 cases × 1 variant | 0.5 s | Exact enumeration scales with the product of response-table counts per node: comfortable to about 10–12 nodes, impractical past \~15. Larger worlds need variable elimination or junction-tree inference; keep v0 small. ### Estimated model usage per stage Tokens are input + output. Multiply by the delegate model's current per-million price, which I have not looked up. | Stage | Volume | Tokens per unit (est.) | Total (est.) | | --- | --- | --- | --- | | Pilot | 60 worlds × 5 items × 3 arms × 2 samples = 1,800 calls | 2k | \~3.6M | | Validation, one reasoning candidate | 200 worlds × 5 items × 2 arms = 2,000 calls | 2k | \~4M | | Validation, debugging policy (P2) | 200 incidents × 2 arms | 20k per episode | \~8M | | Full ablation matrix (13 arms) | 200 worlds × 5 items × 13 arms | 2k | \~26M | | Weekly shadow overhead | claim logging only | \~0 extra model tokens | — | Cost ceilings per candidate are set in the ranked-upgrades section as relative token increases, so they stay valid when the model or price changes. Run the full 13-arm matrix once, after P1 and P2 are decided, not per candidate. ## Staged implementation prompt Hand this to Piper (or a coding worker on Firefly) together with the reference bundle `piper_causal_bench/` (`scm.py`, `check.py`, `scorer.py`, `controls.py`, `validate.py`). Each stage ends at a gate; a failed gate stops the sequence and is reported, not worked around. ```markdown # Component 9 — causal reasoning: staged build You are working on Firefly. Bink approves anything that touches production memory, costs more than the budget below, or cannot be rolled back. Do not edit files under /srv/piper-eval/private (you cannot read them; do not try). Budget for stages 1–4: 10M model tokens. Ask Bink before exceeding it. ## Stage 1 — Audit (read-only) 1. Record real paths + commit hashes for: causal_edges DDL, every writer to causal_edges, causal(), counterfactual v1.0, memory_graph_score.py, the router condition for causal(). 2. Write AUDIT.md answering every item in the package's Instrument audit, parts A–E. Quote code, do not paraphrase docs. 3. Find the forty-edge artifacts. If missing, say so; do not reconstruct from memory. Gate 1: AUDIT.md exists and states, for the forty-edge run, exactly what was withheld. ## Stage 2 — Instrument controls 1. Copy controls.py. Replace causal_naive/causal_fixed with a wrapper around the real causal(), and score_* with memory_graph_score.py's scoring function. Change nothing else. 2. Run C1–C5 on the forty original cases (or a fresh preregistered cohort; commit the seed and selection rule BEFORE running). 3. Write a second scorer independently (no shared code). Run both on C1, C4, C5. Gate 2: C1–C3 = 40/40 with every miss explained, C4/C5 zero false accepts, scorers agree. If the gate fails, the defect found is the next thing you fix (P1/P2-scorer), behind a flag, and you rerun stage 2. Report the 0/40 as uninterpretable until this gate passes. ## Stage 3 — Generated-world oracle 1. Run validate.py; it must print ALL PASS. Add the generator change: confounder strengths drawn so the median non-ID witness gap is >= 0.10; drop items below tol. 2. Generate dev (60 worlds), validation (200), final test (200, held-out structures). Write validation and test oracles to /srv/piper-eval/private only. Gate 3: validate.py ALL PASS on the modified generator; listing-order shortcut <= 0.55. ## Stage 4 — First controlled comparison Run arms A0, A1, A2, A9, A10 on dev, two samples each (this is the pilot). Report per-endpoint means, paired deltas, discordance, cost. Then preregister margins (package: Acceptance section) and commit them. Then run the same arms once on validation. Gate 4: report answers, with intervals, whether A2 beats A1 (architecture vs prompt) and whether A10 beats A9 (graph format vs same facts as text). ## Stage 5 — Fix the strongest measured defect Pick the single defect with the largest measured effect from stages 2–4 (likely P1 query repair or P2 worker protocol). Implement behind a flag. Evaluate on validation with the preregistered endpoint. Accept, reject, or report inconclusive. Do not stack candidates. ## Stage 6 — Expand only as needed Add task families (incident simulator, active experiments, sequential revision, provenance) only when a stage-5 decision needs them. ## Stage 7 — Final test and live Fresh final test once. Then shadow logging (package: Shadow evaluation section), sandbox replay, bounded canary with automatic rollback. Weekly report every Monday. Throughout, never mark this component improved because the graph grew, the tool was called, the prose sounds rigorous, or a score rose on an unvalidated evaluator. ``` What Bink should expect back after stage 2, before anything else is built: a single table with C1–C5 results per case tag, and a one-line verdict on whether the 0/40 measured the graph or the instrument.