Co-change repulsion: files that change together wire togetherSIMULATEDMEASURED
On hono's history FireTrident redid more work than locks. Part of it: agents took intents whose files are coupled to files other agents were changing, and read-set validation sent them back. TrunkDO now mines the trunk's history at import into a co-change map (Hebbian: a file's weight to a partner estimates P(partner changes | file changes) and decays only as that file changes again; STDP: an author's follow-up commit, or a follow-up intent, potentiates earlier-file → later-file edges). It learns every landing after that. ColonyDO.take adds 2 × coupling × claim for each claimed neighbor of a candidate's files, so a claim repels work on the files that change with it.
Does the map predict co-change? MEASURED on hono's real history (train on 200 commits, predict the next 100, sliding)
| predictor | precision@1 | precision@3 | recall@8 | files with neighbors |
| co-change map (as deployed) | 0.53 | 0.35 | 0.29 | 63% |
| most changed files | 0.13 | 0.11 | 0.15 | - |
Direction: ranking the files of the author's previous commit (the upstream change), recall@8 is 0.032 with STDP and 0.026 without. In hono the follow-up signal is weak (squash-merged PRs from many authors), so STDP mostly orders downstream work after upstream work rather than adding coupling.
hono workload, 20 simulated minutes, mean of 3 seeds (SIMULATED)
| agents | model | landed/min | rework % | conflicts/1k | claim to land p50 s |
|---|
| 100 | Baseline: locks + one merge queue | 56.2 | 5.7 | 72 | 76 |
| 100 | FireTrident as built | 62.1 | 12.3 | 75 | 64 |
| 100 | FireTrident + co-change | 63.4 | 10.0 | 63 | 64 |
| 100 | FireTrident + group commit | 62.0 | 12.7 | 77 | 64 |
| 100 | FireTrident + group commit + co-change | 64.5 | 10.1 | 65 | 63 |
| 1,000 | Baseline: locks + one merge queue | 51.9 | 21.7 | 318 | 563 |
| 1,000 | FireTrident as built | 85.6 | 42.5 | 397 | 355 |
| 1,000 | FireTrident + co-change | 85.9 | 38.7 | 354 | 325 |
| 1,000 | FireTrident + group commit | 205 | 45.3 | 319 | 72 |
| 1,000 | FireTrident + group commit + co-change | 192 | 40.4 | 279 | 69 |
| 10,000 | Baseline: locks + one merge queue | 51.9 | 21.7 | 318 | 563 |
| 10,000 | FireTrident as built | 85.8 | 41.4 | 386 | 355 |
| 10,000 | FireTrident + co-change | 85.8 | 38.6 | 365 | 326 |
| 10,000 | FireTrident + group commit | 206 | 46.9 | 334 | 70 |
| 10,000 | FireTrident + group commit + co-change | 192 | 40.3 | 279 | 69 |
Co-change weight: rework % @ landed/min, mean of 3 seeds (SIMULATED; 0 = off, 2 = deployed)
| agents | model | 0 | 1 | 2 | 3 | 10 |
|---|
| 1,000 | as built | 42.5% @ 86 | 39.8% @ 86 | 38.7% @ 86 | 37.8% @ 86 | 35.9% @ 86 |
| 1,000 | group commit | 45.3% @ 205 | 43.2% @ 197 | 40.4% @ 192 | 39.8% @ 186 | 36.4% @ 154 |
| 10,000 | as built | 41.4% @ 86 | 40.8% @ 86 | 38.6% @ 86 | 37.4% @ 86 | 36.4% @ 86 |
| 10,000 | group commit | 46.9% @ 206 | 43.0% @ 203 | 40.3% @ 192 | 39.1% @ 187 | 36.8% @ 152 |
Draining 1500 tasks to zero, seconds, mean of 10 seeds (median for the last task) (SIMULATED)
| agents | model | 50% landed | 90% | 95% | all (median) | rework % |
|---|
| 100 | Baseline: locks + one merge queue | 813 | 1,455 | 1,536 | 1,998 | 5.2 |
| 100 | FireTrident as built | 702 | 1,273 | 1,345 | 2,376 | 12.4 |
| 100 | FireTrident + co-change | 694 | 1,258 | 1,328 | 2,360 | 11.2 |
| 100 | FireTrident + group commit | 693 | 1,258 | 1,330 | 2,477 | 12.4 |
| 100 | FireTrident + group commit + co-change | 684 | 1,244 | 1,312 | 2,303 | 11.1 |
| 1,000 | Baseline: locks + one merge queue | 847 | 1,557 | 1,644 | 2,277 | 15.9 |
| 1,000 | FireTrident as built | 533 | 953 | 1,075 | 2,323 | 39.2 |
| 1,000 | FireTrident + co-change | 531 | 952 | 1,074 | 2,261 | 35.8 |
| 1,000 | FireTrident + group commit | 116 | 411 | 565 | 1,703 | 37.1 |
| 1,000 | FireTrident + group commit + co-change | 131 | 429 | 591 | 1,831 | 33.4 |
Live steering on a preview deployment (MEASURED: real import of hono, real mining, real takes)
Each round a fresh agent claims src/jsx/components.ts (priority 5), then a fresh agent chooses between Y (priority 2.5) and src/utils/body.ts (priority 2). In the coupled colony Y co-changes with the claimed file in hono's last 200 commits; in the control colony it never did.
| colony | Y | took Y | took Z | other | take p50 ms |
|---|
| coupled | src/jsx/streaming.ts | 2/20 | 14/20 | 4 | 319 |
| control | src/router/reg-exp-router/node.ts | 16/20 | 3/20 | 1 | 292 |
Two-sided Fisher exact test on took Y / took Z, coupled vs control: p = 3.0e-5. "Other" rounds are B declining both (the response-threshold rule) or A not taking X first.
- Rework falls, throughput holds on per-lane landing. At 1,000 agents rework drops from 42.5% to 38.7% at the same 85.9 landings/min; at 100 agents from 12.3% to 10.0% with slightly more landings. Locks stay at 21.7%.
- With group commit it is a trade. Rework 45.3% → 40.4%, landings 205 → 192/min at 1,000 agents, and draining a fixed backlog slows a little: repelled intents sit in the 200-oldest candidate window until their neighbors land. FT_COCHANGE_WEIGHT tunes it (0 turns it off).
- What is left of the gap to locks (SIMULATED analysis, not built): direct overlap is only soft repulsion, so the age term and reinforced thresholds let agents take claimed files anyway. Treating a direct overlap as exclusion takes rework to 33.9%, and adding co-change to 31.2% (weight 2) or 26.5% (weight 10), at 86 landings/min, against locks' 21.7% at 52.
- Method. The simulator's take adds the same core
neighborRepulsion ColonyDO uses, with neighbors mined from hono's last 200 first-parent commits (TrunkDO's import depth), top 8 per file, coupling ≥ 0.1. Tasks are drawn from the co-change graph of hono's whole history, while the colony's map knows only the last 200 commits, as a fresh import does. The simulated colony does not learn from its own landings (the deployed TrunkDO does).