Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude/commands/continue.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Reprendre le travail sur cette lane : lire les directives coordinateur, puis enc
**Un seul geste ouvre le choix, et il etablit la priorite tout seul :**

```bash
python scripts/pick_idle_grain.py --lane <machine:workspace> --prev-genre <genre1[,genre2]> [--prev-genre <genre3>]
python scripts/pick_idle_grain.py --belt --lane <machine:workspace>
```

**P0 — Reparer SON PROPRE rouge.** La commande rend en **sortie 0** une file de reparation quand cette lane porte des PRs **bloquees et ouvertes depuis plus de 24 h** : la premiere devient `grain`, la liste complete reste dans `backlog`. **C'est la premiere file de la session**, avant tout grain neuf ; la drainer ne termine pas la session. La raison est mecanique, pas disciplinaire : une PR rouge ne peut etre reparee **que par sa lane** — le coordinateur ne peut ni rebaser ni corriger a sa place — donc tant que la lane ne revient pas dessus, elle reste ouverte indefiniment pendant que les PRs du jour, elles, mergent. C'est exactement ce qui produit le residu de vieilles PRs.
Expand All @@ -34,9 +34,9 @@ python scripts/pick_idle_grain.py --lane <machine:workspace> --prev-genre <genre

**P1 — Missions coordinateur** : DM HIGH, puis steers dashboard de ma lane.

**P2 — Travail en cours** : tache `[CLAIMED]` par cette lane non terminee, deep-queue de la lane si posee sur le dashboard.
**P2 — Travail en cours** : tache `[CLAIMED]` par cette lane non terminee.

**P3 — Le tirage** (sortie 0) : les candidats rendus forment une **file sequentielle**, pas un menu limite a un seul choix. Le pool est **tout l'ouvert, cross-lane** — la lane est une etiquette de reporting, pas une frontiere de travail : rien n'est "le turf d'un autre". Prendre les candidats compatibles dans l'ordre, poser le claim avant chaque edition, livrer, puis passer au suivant sans attendre review, CI, DWELL ou merge du precedent. Les filtres de labels/age/inactivite orientent la premiere passe ; s'ils la vident, le picker les relache automatiquement tout en conservant exclusions explicites, urnes autorisees, claims et signaux de livraison. Une poignee locale epuisee n'est jamais une fin de session. Le cache borne est automatique ; `--cache refresh` force les mesures partageables, `--cache off` diagnostique sans disque, `--cache-status` explique `hit/miss/stale`. Regles completes : [proactive-coordination.md](../rules/proactive-coordination.md) (plancher multi-grain, variete R6, "rien a faire" avec >0 issues ouvertes = echec de methode).
**P3 — Le tapis** (sortie 0) : `--belt` rend **tout l'ouvert, cross-lane**, trie par **derniere visite** (derniere PR mergee qui cite l'issue ; une issue jamais servie passe devant), la plus ancienne en tete. La lane est une etiquette de reporting, pas une frontiere de travail : rien n'est "le turf d'un autre". Prendre les candidats **dans l'ordre**, poser le claim avant chaque edition, livrer, puis passer au suivant sans attendre review, CI, DWELL ou merge du precedent ; une issue servie repart en queue d'elle-meme. Le tapis ne pondere pas et ne refuse jamais : pas de preference de famille, de serie ni d'EPIC, et pas de repli sur une EPIC "maison". Un candidat ne se saute que pour une barriere reelle (GPU-only, vision-only, second LIGHT du meme genre d'affilee selon G-VAR-3), dite dans le rapport de cycle, et l'issue sautee reste en tete pour le tirage suivant ; un claim vivant d'une autre lane est deja retenu par le tapis. Un EPIC en tete se sert par un sous-grain, jamais par un claim de l'EPIC entier. Le tirage pondere (sans `--belt`) reste un outil de diagnostic, il ne choisit plus le grain. Regles completes : [proactive-coordination.md](../rules/proactive-coordination.md) (plancher multi-grain, "rien a faire" avec >0 issues ouvertes = echec de methode).

### Phase 3 : Travailler et livrer

Expand Down
10 changes: 5 additions & 5 deletions .claude/rules/proactive-coordination.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ S'applique à **tous les workers du cluster CoursIA** (po-2023/2024/2025/2026) e
**Un script exécuté sur la branche remplace plusieurs allers-retours de commentaires.** Quand le défaut est **mécanique** — fixer canonique existant, geste déterministe, diff vérifiable — l'exécuter sur la branche de la PR plutôt que poster un commentaire qui coûte une session entière à son porteur. Quatre bornes, toutes requises : la PR est à **lane unique** (un `[CLAIMED]` vivant d'une autre lane renvoie au commentaire, cf [lane-claim-protocol.md](lane-claim-protocol.md)) · le commentaire ou le dossier **nomme le script et le commit** (un commit muet sur la branche d'autrui est indiscernable d'un détournement) · ça **ne lève aucune réserve de tiers** (B.0 : une phrase lève, pas un SHA) · si une cellule source de notebook est touchée, **la re-exécution redevient due** (C.2).

1. **≥2 grains livrés entre 2 wakeups, dont ≥1 DEEP portant un genre de CONTENU = PLANCHER, jamais plafond.** Une PR livrée ne **clôt pas** la session : re-pioche **immédiatement** et enchaîne autant de PRs que la fenêtre le permet (débit nominal ~2 PR/h). S'arrêter après 1 PR alors qu'il reste du temps **et** 60+ issues ouvertes = **sous-régime**, pas « cycle terminé ». Le plancher est **pluriel et durci** (mandat user 2026-09-12) : un cycle dont le plat principal est MED ou META **n'a pas de plancher tenu**, même avec plusieurs PR livrées. La mesure qui motive ce durcissement — 15 % de DEEP sur 7 j, et sur 48 h le META passant devant le CONTENU — est déposée datée dans [détail, section Plancher durci](../../docs/reference/proactive-coordination-detail.md) ; le tier et le genre se tranchent en [variation-protocol.md](variation-protocol.md) (G-VAR-1).
2. **2 tracks en flight minimum** : une **track principale** (dispatchée, Epic) + une **side-track autonome** que le worker avance **même si le coordinateur s'absente 1-2 jours**.
2. **2 tracks en flight minimum** : une **track principale** (mission dispatchée, sinon la tête du tapis) + une **side-track autonome** que le worker avance **même si le coordinateur s'absente 1-2 jours**.
3. **Side-tracks → sous-agents spécialistes async (HARD).** Quand un specialist `.claude/agents/` couvre la side-track, la déléguer en **`run_in_background: true`** pendant que le worker interactif tient la main track. Roster : [docs/reference/subagents-reference.md](../../docs/reference/subagents-reference.md).
4. **Backlog pickup au wakeup vide (HARD).** Sans nouveau feedback / directive / tâche : **ne pas s'arrêter** — piocher dans le backlog et produire la PR du cycle.

Expand All @@ -28,14 +28,14 @@ S'applique à **tous les workers du cluster CoursIA** (po-2023/2024/2025/2026) e
**Le pool ne se scanne plus à la main — il se TIRE, et le tirage est le PREMIER GESTE DE CHAQUE CYCLE (HARD, mandats user 2026-08-14 et 2026-08-20).** `gh issue list` plafonne à **30 résultats** triés par récence : un scan manuel ne voit que le récent et referme la boucle de monoculture — la troncature demande un organe, pas plus de vigilance (mesures : [détail, section Picker](../../docs/reference/proactive-coordination-detail.md)). Un steering nommé du coordinateur est **un grain de plus dans la main de la lane**, jamais une condition de départ : on le brûle s'il est déjà là, on ne constate pas son absence. Son équilibrage entre familles et genres reste dû au coordinateur (§4 de [variation-protocol.md](variation-protocol.md)).

```bash
python scripts/pick_idle_grain.py --lane <machine:workspace> --prev-genre <genre du grain précédent>
python scripts/pick_idle_grain.py --belt --lane <machine:workspace>
```

Tirage pondéré dans **trois urnes** : **grain** (issue unitaire → la livrer) · **umbrella** (un EPIC → piocher ou **créer un sous-grain dedans**, jamais claimer l'EPIC entier) · **delivered** (une `candidate-delivered` → **réservée au coordinateur et à l'adjoint**, mandat user 2026-09-07 [#15069, garde `DELIVERED_URN_LANES` dans `pick_idle_grain.py`] : une lane worker qui en rencontre une poste `[INFO] candidate-delivered` avec sa preuve et rend la main). Pondération deux axes (âge de création + délaissement) : [détail, section Picker](../../docs/reference/proactive-coordination-detail.md).
**Le tapis roulant** (`--belt`, #18832, mandat user 2026-10-02) sert l'ouvert entier par **dernière visite** : la plus ancienne en tête, une issue servie repart en queue, ni pondération ni refus. Les urnes restent un **filtre** : **grain** (issue unitaire → la livrer) · **umbrella** (un EPIC → piocher ou **créer un sous-grain dedans**, jamais claimer l'EPIC entier) · **delivered** (une `candidate-delivered` → **réservée au coordinateur et à l'adjoint**, mandat user 2026-09-07 [#15069, garde `DELIVERED_URN_LANES` dans `pick_idle_grain.py`] : une lane worker qui en rencontre une poste `[INFO] candidate-delivered` avec sa preuve et rend la main). Le tirage pondéré historique (âge de création + délaissement : [détail, section Picker](../../docs/reference/proactive-coordination-detail.md)) ne sert plus qu'au diagnostic.

**Sur un tirage d'urne `umbrella`, confronter le body au réel AVANT de juger l'actionnabilité (#11900).** Le tirage pondère le **délaissement** ; or plus une EPIC est ancienne, plus son body a de chances d'être faux — **plus le picker fait ce qu'on lui demande, plus il remonte d'impasses apparentes**. Deux tirages consécutifs ont remonté deux EPICs entièrement actionnables dont le paragraphe bloquant avait survécu à sa propre résolution (PR déjà mergée ; décision déjà prise par le user le jour même). Conclure « bloquée sur autrui » sans mesurer, c'est reposer un grain disponible et croire le pool saturé. Le **geste** obligatoire et ses trois organes (l'artefact sur `main`, le plateau `gh pr list --search head:`, le commentaire de fermeture) sont portés par [verify-before-claiming.md](verify-before-claiming.md) règle 5, auto-chargée ; le corpus des cas et la **précondition toxique** — une condition de reprise que *rien ne mesure* ne peut jamais être constatée atteinte, et diffère indéfiniment contre la volonté de qui l'a posée — vivent dans [picker-delaisse-detail.md](../../docs/reference/picker-delaisse-detail.md).

**Le picker ne décide pas.** Il propose ; l'agent tranche selon les critères de variété de sa lane et pose son `[CLAIMED]` (`check_lane_claim.py` avant d'**éditer**, cf [lane-claim-protocol.md](lane-claim-protocol.md)). Plutôt que rejouer aveuglément : demander davantage de candidats et passer les exclusions factuelles (`--exclude-issue`, labels, bornes age/inactivité, `--urns`) ; `--reroll` reste le dernier recours ; le cache ne touche jamais les organes minute-sensitive ([détail §Cache](../../docs/reference/proactive-coordination-detail.md)). **Aucun résultat vide filtré ne justifie un `[ASK coordinator]` ni un statut idle.**
**Le tapis ordonne, l'agent ne re-trie pas.** Il prend les candidats dans l'ordre et pose son `[CLAIMED]` (`check_lane_claim.py` avant d'**éditer**, cf [lane-claim-protocol.md](lane-claim-protocol.md)) ; un candidat sauté l'est pour une barrière réelle (GPU-only, vision-only, second LIGHT du même genre d'affilée selon G-VAR-3 de [variation-protocol.md](variation-protocol.md)), dite dans le rapport de cycle. Les exclusions factuelles (`--exclude-issue`, labels, `--urns`) restent disponibles. **Aucun résultat vide filtré ne justifie un `[ASK coordinator]` ni un statut idle.**

**Sur un tirage d'urne `umbrella`, confronter le body au réel AVANT de juger l'actionnabilité (HARD, #11900).** Un body est daté de sa rédaction, jamais de sa lecture — et le picker pondère le **délaissement**, donc plus une EPIC est ancienne, plus son body a de chances d'être faux : **plus le picker fait son travail, plus il remonte d'impasses apparentes.** Le paragraphe bloquant y a souvent survécu à sa propre résolution (mesuré sur #2874 et #7357, dont les deux conditions étaient levées depuis 68 j et 31 j). Sans ce réflexe, le régime produit des rerolls en boucle et la lane conclut à tort que le pool est saturé — l'inverse exact de l'intention de la règle 5.

Expand Down Expand Up @@ -69,7 +69,7 @@ python scripts/ci/check_umbrella_freshness.py <N> [<N>...] # exit 1 = SATURATE
- **Portée écrite** : une PR sans tag `Grain:` lisible n'est imputable à aucune lane et reste invisible à ce garde. Son tag manquant est lui-même le défaut à corriger — c'est au coordinateur de les reprendre.

6. **Variété obligatoire — le tarissement est structurellement interdit (HARD).** Les règles 1-5 interdisent l'idle ; celle-ci interdit la **monotonie**, et pose l'auto-alimentation comme **principe**, pas comme rattrapage du coordinateur. Une lane ne PEUT PAS se tarir : le worker pioche **de lui-même**, **varié**, même si le coordinateur est absent plusieurs cycles.
- **Substance en plat principal** : chaque cycle, viser un grain d'EPIC de fond (preuve Lean, backtest/training, série notebook, moteur SOTA, sécu/infra).
- **Substance en plat principal** : chaque cycle, viser un grain de fond (preuve Lean, backtest/training, série notebook, moteur SOTA, sécu/infra).
- **Nettoyage/doc = à-côté plafonné** (budget G-VAR-2, cf [variation-protocol.md](variation-protocol.md)) : nécessaire, jamais le plat unique. Une journée entière sur un seul registre monotone = sous-régime à corriger **de soi-même**.
- **Rotation genres ET familles** : alterner Lean / .NET / Python / QC / GenAI / docs ; le pool global rend la variété toujours accessible ; ne jamais tunneliser un mono-thème.

Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/coordinate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Les phases ci-dessous s'executent sous le budget defini par la section `## Budge
**Pourquoi enumerer, et pas nommer** : une skill qui sait d'avance quoi lire est **structurellement aveugle** a une cle qu'elle n'anticipe pas. Le 2026-09-21, `workspace-CoursIA (2)` — cle forkee par collision de noms Google Drive — portait **23 messages vivants** de po-2026 et po-2027, dont deux PRs debloquees en attente du merge-gate, pendant plusieurs jours sans qu'aucun cycle ne la voie. Une cle a suffixe ` (N)` dont le `workspace` declare **ne porte pas** ce suffixe est une moitie de la meme lane, pas une lane voisine : la lire, et escalader la reparation (`action:"merge"`, cf dashboard `global`).

2. **Inbox DM — drainer et EXTRAIRE, jamais survoler** : `roosync_messages(action:"inbox", status:"unread", deep:true)` — **sans `deep:true` le compte de non-lus est un faux zero**. Deux gestes, dans cet ordre. **(a) Purger les classes qui doublonnent une surface deja lue** — `bulk_mark_read(subject_contains:"Worker Report")` et `bulk_mark_read(subject_contains:"[MENTION] Dashboard")` : sans ca l'arriere se reconstruit a ~8 DM/h et noie le signal utile, qui pese moins de 10 % du volume. **(b) Extraire la liste nommee des PRs deja pre-machees** — marqueurs `[ADJOINT PREFLIGHT]`, `[ADJOINT VERIFIED]`, `[ADJOINT DECISION PACK]`, `preflight exact-head`. Cette liste est une **entree obligatoire de la Phase 3.3** : le pre-machage est produit qu'on le lise ou non ; non consomme, il est paye deux fois.
3. **GitHub** : `gh pr list --state open` (a merger) + le pool **tire, jamais scanne** — `python scripts/pick_idle_grain.py --lane myia-ai-01:CoursIA` (un `gh issue list` nu plafonne a 30, tries par recence : il ne montre que ce que je viens de creer, et c'est ce biais que le steering doit eviter de reproduire).
3. **GitHub** : `gh pr list --state open` (a merger) + le pool **tire, jamais scanne** — `python scripts/pick_idle_grain.py --belt --lane myia-ai-01:CoursIA` (un `gh issue list` nu plafonne a 30, tries par recence : il ne montre que ce que je viens de creer, et c'est ce biais que le steering doit eviter de reproduire).
4. **Cron** : `CronList` — si le job coordinateur a disparu (session-only), le re-armer a la cadence **courante**. Cette cadence est un **etat**, pas une regle : elle ne s'ecrit pas ici (decision user 2026-09-22 — l'etat courant se met a jour dans les memoires et les dashboards, jamais sous git). L'expression a re-armer se lit dans la memoire du coordinateur (`coordinator-handover`, section cron) et sur le dashboard. Ce qui reste durable : une minute off-`:00` (le jitter evite de frapper l'API a la meme seconde que le reste de la flotte), et une cadence unique, PAS de 2e cron ni ScheduleWakeup en plus — un cycle plus long que sa cadence annule deja ses propres declenchements, en empiler un second ne fait qu'ajouter de la conso.

### Phase 3 - Dispatchs, relances, memoire (LES 30 PREMIERES MINUTES)
Expand All @@ -58,7 +58,7 @@ Les phases ci-dessous s'executent sous le budget defini par la section `## Budge
5. **Trancher les design-gates en attente** dans le cycle -- ne pas deferer une option deja investiguee. Regles : [coordinator-discipline.md](../../rules/coordinator-discipline.md) (R3 lanes independantes, R4 jamais sanctionner l'idle, R5 steer qui ATTEINT/VRAI/DECIDE).
6. **Grounder chaque grain firsthand** (`gh issue view N` / `gh pr view N`) AVANT de dispatcher -- jamais depuis un status condense.
7. **Double canal obligatoire** : DM `roosync_messages(action:"send", to:"<machine>:<workspace>", ...)` (le worker lit l'inbox en premier, le DM survit a la condensation) **+** pointeur `[DISPATCH->inbox]` sur le dashboard de la lane (sonnette persistante).
8. **Une lane sans grain = echec coordinateur** : deep-queue, fallback perenne par famille, ou pool global -- jamais un statut terminal-idle. Chaque worker draine **tous** ses nits et reserves reparables sur **toutes** ses PRs, puis enchaine plusieurs grains DEEP/MED ; une seule PR livree ne clot pas sa session.
8. **Une lane sans grain = echec coordinateur** : la tete du tapis (`pick_idle_grain.py --belt`), jamais un statut terminal-idle. Chaque worker draine **tous** ses nits et reserves reparables sur **toutes** ses PRs, puis enchaine plusieurs grains DEEP/MED ; une seule PR livree ne clot pas sa session.
9. **MAJ memoire maintenant, pas en fin de cycle** : `coordinator-durable-state.md` si l'etat durable a bouge. Repoussee a la fin, elle saute quand le cycle deborde -- et le cycle suivant re-derive ce qu'il savait deja.

**Budget** : ces neuf points sont **clos avant** d'ouvrir la Phase 4. S'ils ne le sont pas a la fin des 30 minutes, ce sont eux qu'on termine -- pas le merge qu'on commence.
Expand Down
14 changes: 12 additions & 2 deletions scripts/pick_idle_grain.py
Original file line number Diff line number Diff line change
Expand Up @@ -3900,18 +3900,28 @@ def belt_sort_key(it: dict) -> tuple:
def belt_filter(
admitted: list[dict],
args,
urns: set[str] | None = None,
) -> list[dict]:
"""Filtre le pool admissible pour le mode --belt.

Meme jeu de filtres que la voie ponderee (exclusions, labels, bornes,
urnes), sauf l'admissibilite par DWELL/zone -- le tapis ne refuse
JAMAIS, par contrat (cf issue #18832). Les bornes du tapis sont
uniquement celles que le caller passe en CLI.

``urns`` : urnes EFFECTIVES, deja passees par
``apply_delivered_urn_gate`` (#15069). Le caller ``main`` les fournit
toujours ; relire ``args.urns`` brut rendrait l'urne ``delivered``
(presente par defaut) a une lane worker, qui ne doit jamais la recevoir.
``None`` garde la lecture de ``args.urns`` pour les appels sans lane.
"""
excluded_issues_set = {int(v) for v in _csv_values(args.exclude_issue)}
required_labels_set = set(_csv_values(args.require_label))
excluded_labels_set = set(_csv_values(args.exclude_label))
selected_urns_set = {v.casefold() for v in _csv_values([args.urns])}
if urns is None:
selected_urns_set = {v.casefold() for v in _csv_values([args.urns])}
else:
selected_urns_set = {v.casefold() for v in urns}

def _keep(item: dict) -> bool:
n = item["number"]
Expand Down Expand Up @@ -5265,7 +5275,7 @@ def main(argv: list[str] | None = None) -> int:
metrics = belt_report_metrics(pool, closed_7d=None)
print_belt_report(metrics)
return 0
belt_pool = belt_filter(admitted, args)
belt_pool = belt_filter(admitted, args, urns=selected_urns)
belt_pool.sort(key=belt_sort_key)
# Verification des claims tenes par une autre lane : on regarde
# plus large que `args.grains` pour tolerer un remplacement si
Expand Down
Loading
Loading