Panout review packOverviewFinal memoMVPLive outputsJudge roundsAppendicesDecisions

Source: docs/research/2026-09-03-defensibility/appendices/I-retrospective-contract-backtest.md

Appendix I: Retrospective contract backtest on the founder's repos (2026-09-04)

Read-only. Script: scripts/backtest_contracts.py (evaluation) and scripts/analyze_contracts.py (summary). Raw output kept at /tmp/panout-site/analysis/founder.json on the founder's machine; not committed because it names paths and authors.

Why this exists

The round-5 judge said the predictive measurement (does a contract result at commit time predict rework?) could not exist before 2026-10-05 because it needs 30 days of commit-time capture. That is wrong in a useful way. A contract evaluates the diff, the message, and the paths, and all three exist in git for every historical commit. So the four default contracts can be evaluated on every commit made in the last 180 days exactly as the prepare-commit-msg hook would have evaluated them, and joined to the same 30-day rework proxy the audit uses. Because every one of those commits landed, every retrospective failure is what the hook would have recorded as an override.

This appendix does that for the founder's repos. Appendix J repeats it on six public repos that are not the founder's.

Method

Headline numbers

value
commits measured (non-bot)3,221
agent-attributed934 (29%)
exposure: commits with at least one non-skip contract result100%
retrospective override rate: commits that failed at least one contract26%
override rate, agent commits43%
override rate, human commits19%
per-repo override rate range (12 repos)9% to 48%

The memo needed contracts to fire on roughly 30% of commits for the day-60 test to be powered. The default set fires on 26% of the founder's commits and on 43% of agent commits without any tuning.

Fail rate per contract

contractevaluatedfail, allfail, agentfail, human
tests-touch1,31628%21% (n=695)36% (n=621)
no-secrets3,2041%2% (n=923)1% (n=2,281)
size-guard3,22115%29% (n=934)10% (n=2,287)

Agent commits exceed 400 added lines three times as often as the founder's own. Agent commits also touch tests more often than the founder does when touching source, so tests-touch fails less on agent work.

Predictive test, unadjusted

Rework within 30 days, given the contract result at commit time. Line-identity (R1) first, corrective (R3) second.

contractpopulationrework if failrework if passdelta (pp)95% CI
size-guardall85% (489)49% (2,708)+37+33 to +40
size-guardagent85% (270)56% (653)+29+23 to +35
no-secretsall89% (28)54% (3,169)+35+21 to +46
tests-touchall46% (366)72% (941)−26−31 to −20
tests-touchagent55% (141)74% (548)−19−28 to −10
any contractall68% (827)49% (2,370)+18+15 to +22
any contractagent74% (396)57% (527)+17+11 to +23

Corrective rework (R3) shows the same shape with larger magnitudes: size-guard +45 (CI +41 to +49), any-contract +31 (CI +27 to +35), tests-touch −31 (CI −37 to −26).

Read naively, a contract failure predicts 18 to 31 points more rework and the day-60 test is already passed. That reading is wrong, and the next table is why.

The size confound

Within bins of added lines, the any-fail versus all-pass difference disappears.

added linesnrework if any failrework if all passdelta (pp)
0 to 2042828% (80)32% (348)−5
21 to 10093444% (124)46% (810)−2
101 to 4001,34651% (134)57% (1,212)−5
over 40048985% (489)n/a (0)n/a

Every commit over 400 lines fails size-guard by definition, so the last bin has no pass group. In the three bins where a comparison exists, failing a contract predicts slightly less rework, not more. The +18 pooled delta is entirely the fact that big commits get reworked more, which size-guard restates rather than detects.

Per contract, size-adjusted (n-weighted average of within-bin deltas; bins with fewer than five commits on either side are marked thin):

contract0 to 2021 to 100101 to 400over 400weighted deltaagent-only weighted
tests-touch−8 (79/58)−4 (123/201)−28 (130/321)−1 (34/361)−13−3
no-secretsthinthin+44 (6/1,340)+15 (17/472)+23+15
size-guardthinthinthinthinnot separable from size

Per-repo view

repo (anonymised by role)commitsagentoverride rateagent reworkhuman reworkgap (pp)
dotfiles48212817%64%31%+32
team monorepo (minority committer)5496429%60%40%+20
gateway service694448%55%38%+17
router18211039%65%56%+9
browser6005228%48%40%+8
poe2 (agent-majority)54548441%67%71%−4
pooled3,22193426%65%50%+15

The agent-versus-human gap replicates appendices A and F in direction (+15 pooled, range −4 to +32). In dotfiles, size-guard failure predicts +66 points of rework for the founder's own commits but only +15 for agent commits (CI −3 to +32): the founder's large commits are rare and messy, the agent's are routine. That is the kind of per-repo, per-contract, per-author fact the product exists to surface, and it was learnable in about four minutes of git blame with no capture period.

What this changes

  1. The 30-day wait is not needed to power the predictive test. The backtest runs on install, on any repo, in minutes. The memo's "labels from 2026-10-05" timeline was too conservative for the predictive question; it remains correct for the override question, because whether a human saw and overrode the failure is the one thing history does not record.
  2. The default contracts measure exposure, not risk. Two of the three predictive defaults carry no information once size is controlled. This is not a failure of the design; it is the design's first result: autonomy per contract means tests-touch would never earn trust on these repos, and the product would say so on day one instead of after sixty.
  3. Size is the only strong observational predictor found so far, and it is free. Anyone can compute it. What is not free is knowing which of a team's own contracts, beyond size, carries signal, and that is exactly the per-contract fault-injection and override record the memo already proposes.
  4. The override rate is the product's exposure guarantee. 26% of all commits and 43% of agent commits would have produced a recorded override under the defaults. The buyer's audit trail is not empty on day one.

Limits