# Coordination proactive — detail

Detail de [.claude/rules/proactive-coordination.md](../../.claude/rules/proactive-coordination.md). Voir aussi [subagents-reference.md](subagents-reference.md), [scripts-reference.md](scripts-reference.md).

## Plancher durci — « plusieurs grains dont un DEEP de CONTENU » (mandat user 2026-09-12, #15793)

R1 de [proactive-coordination.md](../../.claude/rules/proactive-coordination.md) et G-VAR-1 de [variation-protocol.md](../../.claude/rules/variation-protocol.md) portent un plancher **pluriel et durci** : **≥ 2 grains livrés entre 2 wakeups, dont ≥ 1 DEEP dans un genre de CONTENU**. Le MED et le META restent **bienvenus au-delà** du plancher — ils ne le tiennent plus.

**Ce qui le motive — mesure du 2026-09-12T16:39Z**, attribution par le tag `Grain:` (jamais `--author` : l'identité de poussée `jsboige` est partagée, L721) :

| Fenêtre | merges | DEEP | MED | LIGHT | CONTENU | META |
|---|---:|---:|---:|---:|---:|---:|
| 7 jours (depuis 09-05) | **583** (83,3/j) | 88 (**15 %**) | 400 | 88 | 314 | 260 |
| 48 heures | **196** (98,0/j) | 35 (**18 %**) | 113 | 45 | **82** | **109** |

Le volume n'est pas le défaut. Deux choses le sont : (1) la **dureté** — 15 % de DEEP sur 7 j, le plancher R1 d'alors (« ≥ 1 PR ») étant tenu par n'importe quoi, massivement par du MED ; (2) la **bascule CONTENU/META** — sur 7 j le CONTENU tient encore (314 contre 260), sur 48 h le META passe devant (109 contre 82) : la flotte construit l'atelier plus vite qu'elle n'y fabrique.

Le contraste par lane rend le mécanisme lisible : la lane au plus gros volume sur 7 j (`myia-po-2023:CoursIA`, 108 merges) est la plus faible en substance (39 CONTENU, **8 DEEP**), tandis que `myia-po-2027:CoursIA-2` rend 32 CONTENU et 10 DEEP sur 41 merges. La lane la plus META de la flotte est celle du coordinateur (`myia-ai-01:CoursIA` : 38 merges sur 7 j pour **1 DEEP** et 7 CONTENU — 0 DEEP et 1 CONTENU sur 48 h).

**Contre-poids anti-inflation, nommé et non inventé.** Exiger un DEEP crée une incitation à **sur-coter le tier**. Ce qui la couvre est déjà en place : le signal bot `TIER-INFLATION`, et le merge-gate qui **re-qualifie lui-même un tag mal dérivé** (§3 de [variation-protocol.md](../../.claude/rules/variation-protocol.md), ligne « Tag mal dérivé »). Le litmus DEEP reste objectif — *`main` contient-il désormais un résultat ou une capacité qui n'existait pas, dont la production a demandé du raisonnement de domaine ?* Le durcissement se paie en **lecture de tags par ai-01**, jamais en confiance.

**Ce durcissement normatif substantiel exigeait un sign-off user** conformément à CLAUDE.md §A : la PR #15793 qui l'a porté ne se self-merge pas. Les corrections non normatives suivent la même PR/review, sans sign-off supplémentaire.

## Backlog pickup — sources autorisees (ordre de priorite decroissant)

Wakeup sans dispatch neuf : prendre la **premiere source non-vide** et la consommer comme une **file sequentielle de grains atomiques**. Chaque grain garde sa PR propre ; une PR livree ne termine pas la session et la lane poursuit tant que sa fenetre est active.

| # | Source | Action | Pourquoi cet ordre |
|---|--------|--------|---------------------|
| 1 | Side-track Epic dispatchée propre au worker (cf mapping) | Avancer ses sous-taches atomiques dans l'ordre | Engagement deja pris |
| 2 | Ses propres PRs OPEN en attente review/iter | Drainer feedback, conflits et CI propres avant production neuve | Pas laisser pourrir |
| 3 | Issues `priority-high` non assignees dans son domaine | Self-assign + livraisons atomiques sequentielles | Urgences declarees |
| 4 | PRs languishing > 3 jours sans reponse (siennes ou domaine) | Re-iter ou cloturer, puis poursuivre | Hygiene cluster |
| 5 | Issues `priority-medium` du domaine | Self-assign + livraisons atomiques sequentielles | Travail cataloguable |
| 6 | Low-priority Epics stockees (#1646 Grothendieck, #1647 Conway Phase 2, #1650 Translation, #1651 Conway Phase 3 FWT) | Une PR atomique par phase/pilier, plusieurs a la suite | Stock activable "fenetres calmes" |
| 7 | Audit findings non traites (NanoClaw #488 etc.) | Reassessment puis PR si CONFIRMED, et continuer | Backlog d'enquete |
| 8 | Consolidation/documentation de son domaine | PRs atomiques, sans plafond d'une par session | Wiki Karpathy / SDDD documentaire |

## Anti-patterns wakeup vide (interdits)

- "Rien de nouveau, je me rendors" → **REFUSE** ; pioche backlog avant tout ScheduleWakeup
- "J'attends le prochain dispatch coord" → **REFUSE** ; le coord n'est pas un distributeur flux tendu
- Fusionner plusieurs sujets dans une PR « pour faire du chiffre » → **REFUSE** ; plusieurs grains se livrent sequentiellement, chacun dans sa PR atomique
- Annoncer "BLOCKED" alors que sources 5-8 non explorees → **REFUSE** (G.7 stagnation cross-cycle = escalade)

## Reporting wakeup vide

```text
[INFO] Wakeup R<N> vide (pas de feedback/dispatch nouveau).
Backlog pickup : source <#X> "<nom source>" → item #<NNN> "<titre>".
PR cible : #<MMM> en cours.
```

Pas de `[BLOCKED]` ni `[DONE pas de travail]`. Wakeup vide = opportunite backlog, pas blocage.

## Pourquoi il n'y a pas de mapping machine → track

Ce fichier a porté jusqu'au 2026-09-12 un tableau « Mapping machine → track principale + side-track (**cycle courant**) ». Le « (cycle courant) » de son titre était le défaut : un fichier versionné ne peut pas porter l'état d'un cycle, et celui-là ne l'a pas porté. Sur ses **7 assignations numérotées, 5 pointaient une issue CLOSED** — #1409 (fermée le 2026-07-26), #1273 (2026-06-02), #1385 (2026-05-26), #1455 (2026-06-11), #999 (2026-05-23) ; trois des quatre lignes worker avaient une track principale fermée, et po-2023 comme po-2025 avaient **les deux colonnes** fermées.

Un steer qui pointe une issue CLOSED est un **phantom** au sens de R5 de [coordinator-discipline.md](../../.claude/rules/coordinator-discipline.md) : le worker brûle son cycle à le réfuter au lieu de produire. Le tableau en fabriquait cinq, en permanence, depuis un fichier auto-référencé par le harnais.

**Ce qui le remplace est le tirage**, déjà la voie par défaut depuis le mandat du 2026-08-20 (R5 de [proactive-coordination.md](../../.claude/rules/proactive-coordination.md)) : `python scripts/pick_idle_grain.py --lane <machine:workspace> --prev-genre <genre>`. Le steering nommé du coordinateur reste l'exception qui passe devant — mais il se **grounde firsthand au moment où il est posé** (`gh issue view N`), ce qu'un tableau écrit une fois ne peut structurellement pas faire. Le roster des spécialistes async, lui, n'a jamais eu besoin d'être partitionné par machine : il vit dans [subagents-reference.md](subagents-reference.md), et R3 dit de déléguer la side-track au specialist qui couvre le sujet, quelle que soit la lane.

Cette section existe pour que le tableau ne soit pas **re-inventé** : son absence est un choix mesuré, pas un oubli de maintenance.

## Cadence

- ScheduleWakeup horaire (~3540-3555s jitter) re-arme a chaque tour coord/worker interactif (ping-pong) — cf CLAUDE.md global "Multi-Machine Ping-Pong".
- Le sous-agent async tourne **pendant** le sommeil entre wakeups : au reveil, recuperation notification + integre/PR.

## Anti-patterns cycle normal (interdits)

- Worker avec une seule track (BG seul, ou main seule sans side-track) → demander/prendre 2e track immediatement
- Refaire a la main une tache couverte par un specialist async (modernisation serie, batch notebooks, QC robustness…)
- Side-track sans Epic de rattachement (intracable, meurt a l'absence du coordinateur)
- Lancer 2 sous-agents editeurs sur le **meme** notebook/serie (collision) — read-only OK en parallele, editeurs = un par fichier

## Vérification pré-cycle (L282 / L283 / L786 / L915) — leçons post-c.264

Consolidees depuis les incidents cross-machine c.264 / c.288-289 / c.797 / c.796 et le PR feedback upstream. **Dures** : à executer AVANT tout claim, pas après.

**L282 — Mirror-lanes collision**. Une machine avec 2 lanes (CoursIA **et** CoursIA-2) = 2 workers distincts. Avant `gh issue claim` ou poster `[CLAIMED]`, verifie **le dashboard de la lane ciblee** : un doublon mirroir (incident fondateur #5640/#5641 sur #5635, 6 min apart, ai-01 arbitrage 13:51Z) est une **perte de cycle pour les deux workers** + overhead coordinateur. `roosync_dashboard(action:"read", type:"workspace", section:"all")` sur **les 2 dashboards** (workspace-CoursIA + workspace-CoursIA-2) avant de claim.

**L283 — Confiance hierarchique = `gh API > git log > dashboard intercom > status condense`**. Avant de propager un claim d'etat (merge / OPEN / contenu main) : `gh pr view N --json state,mergedAt,mergeCommit` + `git ls-tree origin/main <file>` + `git log origin/main --oneline -- <file>`. Le **status condense auto-92%** du dashboard peut **halluciner** un merge (po-2026 c.288 : "#5657 MERGED by jsboige" alors que PR OPEN CLEAN awaiting sweep). Source : condensation LLM hallucine des PRs sur 4 cycles consecutifs.

**L786 — Honest-drain diagnostic AVANT claim "lane exhausted"**. Avant de poster un statut terminal-idle (forensic-floor, drained, saturé, no-change, loophole) : `gh issue list --state open | wc -l` > 0 ne suffit pas. **Identifier le grain executable pour ta capability** (CPU-only-po et lecture OK, GPU-only exclu) — test : pour chaque issue ouverte, peux-tu (a) la lire, (b) la decomposer en sous-grain non-GPU, (c) executer le sous-grain ? Si ≥1 sous-grain executable → claim, sinon re-arme. Incident : 4 lanes "drained" simultanement c.796, pool offrait ≥6 grains CPU frais non-claimés (cf c.797 dispatch).

**L915 — PR OPEN MERGEABLE ≠ PR mergee**. Avant de claim un cycle N dependant d'une PR OPEN MERGEABLE upstream, verifie `gh pr view N --json mergedAt` (non-null = mergee). 2 options : (a) reporter c.N a `mergedAt` confirme ; (b) redécouper le grain en **substrate standalone** + c.N+1 (le livrable de N est comprehensible sans merger l'upstream). Source : worker-side claim triple phantom-dispatach (c.715 #7225, c.797 #8031, c.792 #7733) corrige par L715-L2.

**C715-L2 — Cross-repo work detection AVANT claim**. Avant de prendre un grain sur issue #X, triple-verification :
```
git log --all --oneline --grep "#<issue>"                    # 0 commit upstream
git ls-tree origin/main <path>                              # 0 fichier upstream
gh pr list --search "#<issue>" --state all                   # 0 PR antérieure
```
3 zeros = grain libre. ≥1 hit = verifier l'etat de l'upstream (`gh pr view N --json mergedAt`) et choisir (a) reporter, (b) redécouper, (c) rafraichir la substance.

**C715-L2 — Incident fondateur #8835/#8836 (po-2024, 2026-07-29)**. Deux écarts au triple-check ci-dessus ont laissé passer un doublon **déjà mergé** : (a) recherche `--state open` au lieu de `--state all` — la PR #8836 (mergée 14:03Z par jsboige) était invisible à toute recherche de PRs ouverts ; (b) recherche par **mots-clés** du titre (`"GradeBook is_absolute"`) au lieu du **numéro d'issue** (`"#8835"`) — le titre de #8836 (« top-tree rule form-invariant ») ne matchait pas les mots-clés choisis. Le correctif identique (même règle `_classify`, mêmes 2 fichiers, mêmes 5 critères d'acceptation) fut livré ~30 min après → PR #8838 fermée comme doublon CONFLICTING. **Le triple-check canonique l'aurait attrappé** : `gh pr list --search "#8835" --state all` retourne #8836 (son tag scope `notebook-tools,#8835` matche), et `git log --all --grep "#8835"` montre `6446bd7bd`. Leçon : avant de pousser un fix sur une issue, le triple-check C715-L2 est **non-négociable**, la recherche se fait par **numéro d'issue** (pas par mots-clés), et `--state all` (pas `open`) — un doublon mergé est le cas de collision le plus facile à manquer parce qu'il n'a aucune PR ouverte à voir.

**Routine verification first-and** (à coller dans `~/.claude/CLAUDE.md` personalisation ou alias bash) :

```bash
# Avant tout claim de PR dependante d'une upstream
gh pr view N --json state,mergedAt,baseRefOid 2>/dev/null | head -5
git merge-base --is-ancestor <sha> origin/main && echo "MERGED" || echo "NOT-MERGED"
```

---

## Vocabulaire d'idle-honnête — les labels bannis (R7, mandat user 2026-07-19)

Les règles R1-R6 interdisent l'idle ; les workers l'ont contournée en inventant un vocabulaire qui *sonne* comme du travail. Liste constatée, **bannie comme état terminal de cycle** :

`CLEAN_DONE 0-PR` · `0 grain frais` · `pool/lane exhausted/saturée` · `forensic-floor` · `drained-confirm` · `due-diligence contribution` · `NO-CHANGE-NEEDED honnête` · `Nᵉ cycle honnête` · `loophole applicable` · `rien à faire` · `en attente de dispatch`.

Cette liste est **indicative, jamais l'autorité** : un worker peut toujours inventer un nouveau synonyme. L'autorité est le **test de résultat** de R7 (`.claude/rules/proactive-coordination.md`) — *ai-je sorti un grain de substance du pool et l'ai-je transformé en PR ?* — qui couvre tous les synonymes présents et futurs.

## Incident fondateur R5/R7 — le silo qui se termine en ASK (2026-07-01, po-2025)

Séquence constatée : le worker audite la tranche README de **sa seule famille**, la trouve cohérente, puis poste un `[ASK coordinator]` pour un grain frais — alors que `gh issue list --state open` offrait des dizaines de grains exécutables cross-lane (#3360 bug RL Python, #4039 gittins bi-track, #3968/#2876 cross-famille, entrées ML.Net manquantes).

Un audit de tranche étroite qui se termine en ASK **est le silo, pas de la proactivité**. C'est l'origine de la formulation R5 « le pool n'est PAS ta famille » et de la clause « le coordinateur n'est PAS un distributeur de grains ».

Exemple chiffré du même registre (2026-07-19) : la famille ICT offrait à elle seule **≥6 grains CPU frais non-claimés** pendant que **4 lanes** se déclaraient « drained ».

## Picker — pondération, graine, mesures (organe #10853)

Détail de la R5 « le pool se tire ». La règle porte l'usage et les trois urnes ; ce qui suit sert quand on veut comprendre ou ajuster le tirage.

**Mesure fondatrice (2026-08-14).** Pool de 140 issues ouvertes, dont **89 créées dans les 7 derniers jours**. `gh issue list` plafonnant à 30 résultats triés par récence, la fenêtre visible s'arrêtait à **~6 jours** — le worker repiochait mécaniquement dans ce que le coordinateur venait de créer. Partition du même pool : **44 grains unitaires · 29 EPIC/umbrella · 67 `candidate-delivered`**.

**Pourquoi trois urnes et pas une.** Au moment de la mesure, **aucun** grain unitaire n'avait plus de 30 jours : tout l'ancien (#1453 prover, #1454 training, #2159 Grothendieck, #2874 Knot, #1621 QC, #1206 Z3.Linq) était umbrella. Une urne unique ne l'aurait jamais montré, quel que soit le poids donné à l'ancienneté. L'urne `delivered` est ce qui fait *refluer* le compte sans batch-close aveugle — le batch-close endort une série (précédent ICT : 24 fermetures justifiées item par item, série ensuite dormante).

**Pondération** — trois facteurs doux, tous imprimés à côté du candidat :

| Facteur | Formule | Ce qu'il sert |
|---|---|---|
| ancienneté | `1 + log2(1 + jours/7)` | « faire refluer doucement » — la traîne est là où le compte s'accumule ; 6 mois pèsent ~4× une issue de la semaine |
| anti-répétition de session | `×0.25` si le genre appartient aux `--prev-genre` répétés/CSV | **G-VAR-3 au tirage** ; `guard → docs → guard` reste pénalisé |
| contenu | `×2` si le genre est CONTENU | **G-VAR-1 au tirage** |

**Persistance des genres de session (#14591/#14704)** : `--prev-genre` est répétable et accepte les listes séparées par virgules. Le CSV par lane (`--csv-state <path>` + `--write-state candidate`) conserve l'ensemble sous `lane,last_genres,last_ts`, avec des genres séparés par `|`. L'ancien header `last_genre` et sa valeur scalaire restent lisibles comme un ensemble à un élément ; la prochaine écriture les migre sans perte. Chaque écriture ajoute le genre de la candidate aux genres déjà connus, au lieu d'écraser la mémoire par le dernier : la séquence `guard → docs → guard` reste donc pénalisée au troisième tirage. Le choix `--write-state=merged`, accepté mais sans effet, a été retiré. Toute `OSError` laisse le picker utilisable sans persistance pour le cycle.

Tirage pondéré **sans remise** (Efraimidis-Spirakis : clé `u^(1/w)`, top-k). Plus de pondération reproduirait une monoculture avec des étapes en plus : on se limite à ce que les gates du variation-protocol demandent déjà.

**Graine** = `sha256(lane | heure UTC | reroll)`. Deux lanes tirent des candidats différents à la même minute ; une même lane qui relance dans l'heure retrouve son tirage (idempotent, pas de thrash) ; `--reroll N` redistribue. Le genre affiché est **inféré** par regex sur titre+labels : c'est une aide au tri, pas un verdict — l'agent pose le vrai tag `Grain:` lui-même.

### Cache de payloads et filtres factuels (#13920)

Les trois lectures GitHub partageables sont mises en cache **avant dérivation** dans le cache utilisateur, jamais dans le dépôt : pool ouvert (10 min), PRs mergees de la fenêtre d'affluence (15 min), PRs avec fichiers de la saturation de séries (60 min). La clé encode dépôt, schéma, mesure et forme de requête ; les cutoffs temporels exacts restent dans le fetch, tandis que `age`, `idle`, citations et `saturation(prs)` sont recalculés à chaque lecture. Une écriture passe par un fichier temporaire puis `os.replace`; la rétention est bornée. `--cache auto|off|refresh`, `--cache-dir` et `--cache-status` rendent le mécanisme contrôlable. Un fallback stale n'est utilisé qu'après échec du refresh et reste signalé comme **NON MESURÉ** dans les verdicts qui en dépendent.

Le cache ne touche jamais les organes minute-sensitive : garde des PRs rouges, checks/reviews/nits, claims, `recent_delivery`, sécheresse de contenu et écritures de commentaire restent live.

Les rerolls successifs peuvent être remplacés par une requête plus expressive :

```bash
python scripts/pick_idle_grain.py \
  --lane myia-po-2025:CoursIA-2 \
  --prev-genre tooling \
  --grains 8 --umbrellas 4 \
  --exclude-issue 13920,13924 \
  --exclude-label blocked \
  --min-idle-days 7 \
  --urns grain,umbrella \
  --cache-status
```

Filtres disponibles, tous locaux et combinables : `--exclude-issue` répétable ; `--require-label` (AND) ; `--exclude-label` (ANY) ; bornes inclusives `--min/max-age-days` et `--min/max-idle-days` ; sélection `--urns`. Les comparaisons de labels sont insensibles à la casse. Le funnel attribue chaque exclusion au premier filtre qui la retire et expose en JSON `filters.active`, `filters.excluded`, `filters.funnel`. Si ces filtres locaux vident la première passe, le picker les relâche automatiquement et l'annonce ; exclusions explicites et urnes restent fermes. Il ne prétend jamais que le pool global est vide. Les filtres de tier/genre sont volontairement absents, car le genre du picker est inféré et n'est pas un verdict.

### Picker — les deux axes de pondération (complément)

La pondération du tirage porte **deux axes indépendants** (mesurés non redondants, pearson r = 0,334) : l'**âge de création** (la traîne, là où le compte s'accumule) et le **délaissement** — jours depuis la dernière activité, colonne `inact`. Le second est ce qui atteint « les EPICs qui méritent d'être conduites à leur terme et sont souvent délaissées devant les sujets du moment » : un sujet intouché depuis 53 j pèse ~2,4× un sujet du jour — assez pour remonter, trop peu pour devenir la seule veine.

Mesure du 2026-08-20 sur les 140 ouvertes : **91 avaient bougé dans les 24 h**, et sur les 12 les plus inactives, **9 étaient des EPICs**. C'est cette asymétrie qui a fait du tirage systématique le premier geste de chaque cycle (mandat user 2026-08-20) : tant que la sélection par défaut restait « ce que je vois », elle restait « ce qui est récent » — les sujets menés à leur terme étaient toujours ceux du moment, et les EPICs de fond ne l'étaient jamais.

## L721 — stale-tracker guard (leçon complète)

Avant **tout** claim de « 0 PR / saturated / idle » : la requête DOIT interroger le **tag de lane** (la même clé que `pick_idle_grain.py` lit), **PAS** `--author <self>`. Forme canonique (variable shell, robuste Git Bash / PowerShell / CMD) :

```bash
LANE="<machine:workspace>"  # ex. myia-po-2026:CoursIA
gh pr list --state open --limit 100 --json number,title,body \
  --jq ".[]|select(.body|test(\"lane $LANE\"))|\"#\(.number) \(.title)\""
```

**Pourquoi pas `--author <self>`** (#13870) : les workers poussent sous le compte `jsboige` partagé, pas sous leur nom de lane — `--author myia-po-2026` rend **0** alors que la lane porte 25 PRs ouvertes (mesure 2026-09-01, 59 PRs pool). Quatre lanes sur cinq voient `0` de leur propre travail avec la requête naïve, et une lane a escaladé `[URGENT] HIGH` (`msg-20260831T181622-9fucbh`) en croyant n'avoir rien livré alors qu'elle portait 12 PRs — exactement l'état terminal faux-négatif que ce garde devait prévenir.

**Contrôle positif obligatoire** : la requête corrigée se valide par ses **faux négatifs symétriques** — une lane connue pour avoir N PRs doit en rendre N, pas par le fait qu'elle rende un nombre. Mesure de référence du 2026-09-01 : `myia-po-2026:CoursIA` → 25, `myia-po-2024:CoursIA` → 11, `myia-ai-01:CoursIA` → 9, `myia-po-2027:CoursIA` → 5, `myia-po-2023:CoursIA` → 3 — toutes non-nulles (la mesure 2026-08-31 de l'issue #13870 incluait `myia-po-2025` à 1 PR qui a depuis été mergée).

**Repli** : si une PR ne porte **pas** le tag `Grain:` lisible, elle reste invisible aux deux requêtes (tag + author) ; c'est l'angle mort partagé avec `GRAIN-ORPHANS-SWEEP` (#13086). Traiter par le sweep dédié, **pas** en relâchant la requête ici.

## L1356 — preflight de claim, recette complète

L898 vérifie ce qui est **ouvert** ; une PR **MERGÉE** qui a livré l'issue en rider (référence à un autre numéro, pas de `Closes #N`) est invisible à ce filtre, et l'issue reste OPEN sans PR liée — **« OPEN + zéro PR liée » n'est PAS une preuve de fraîcheur** (incidents #13562/#13608, corroboré po-2025 sur #12794).

Avant tout `[CLAIMED]` :

1. `gh search prs "<N>"` (ou `gh pr list --state all --search "<N>"`) — une PR merged sur le sujet = grain livré.
2. `ls -d <repo-worktrees>/*<motif>*` — un **worktree orphelin** nommé pour l'issue signale une session antérieure : la compaction efface le souvenir du travail, pas le disque ; inspecter sa branche (`git log -1`) avant de créer quoi que ce soit.

Si livré : poster un commentaire `[INFO] candidate-delivered` avec preuve (commit + tests) pour fermeture par le coordinateur — **ne pas réimplémenter, ne pas closer soi-même**.

Cas fondateur du point 1 : le doublon mergé #8835/#8836 (po-2024, 2026-07-29) — deux écarts au triple-check (recherche `--state open` au lieu de `all` ; recherche par mots-clés du titre au lieu du **numéro d'issue**) ont laissé passer un correctif déjà mergé 30 min plus tôt. La recherche se fait par **numéro d'issue**, en `--state all`.

## Réparer son propre rouge — historique et mesures (R5, mandat 2026-08-22)

Le picker a longtemps rendu « REFUS DE TIRAGE » + **sortie 2, aucun candidat** quand la lane portait une PR bloquée >24 h : le fond était le même (la reprise passe d'abord), la forme disait *l'outil n'a rien pour toi*. La forme actuelle — **sortie 0, le grain rendu *est* la reprise** — date de ce mandat.

Pourquoi la reprise est assignée et pas négociable : **une PR rouge ne peut être réparée que par sa lane** — le coordinateur ne peut ni rebaser, ni corriger, ni lever un `CHANGES_REQUESTED` à sa place. Une lane à forte cadence accumule les PRs plus vite, déclenche `count`/`nits` plus tôt, et recevait donc l'ancien refus à **chaque** cycle — le boost causait le drain (incident lanes 2 sous crédit MiniMax, 2026-08-30).

La mesure qui a fondé le mandat (2026-08-22) : débit de merge sain — 65 mergées dans la journée — et pourtant **16 PRs bloquées de plus de 24 h réparties sur 7 lanes**, une au moins par lane. Les PRs du jour mergent vite pendant que les vieilles s'accumulent : c'est le résidu que seule la reprise par la lane absorbe.

## Veine plafonnée — historique et anti-pattern (R8, #11343 tranche 2)

Avant que le tirage ne devienne systématique (R5), le plafond de veine portait une « condition d'appel » du picker. Depuis, il ne reste de lui que l'**interdit ciblé** : la PR suivante de la lane ne peut pas re-claimer l'umbrella qui vient de saturer (`vein_cap=2` PRs citant la même umbrella par jour de lane), quel que soit ce que le tirage propose — la commande `picker_command` exposée par `variation_light_cap.py` sert à écarter l'umbrella saturée.

L'amendement ai-01 du 2026-08-16T22:53Z (verbatim : « on ne jette pas du travail écrit ») borne l'interdit : **le plafond ne bloque jamais la tranche en cours**, seulement la PR suivante.

Anti-pattern fondateur : continuer à claimer `#11224`/`#11271` après la 3ᵉ tranche sans passer par le picker = monoculture auto-référence, le défaut structurel que la veine-mesure a justement rendu visible.
