tour 0001 (claude, implementer) : fondation — README, PROTOCOL v0, config, backlog B-1..B-13

Disput-Turn: 0001
Disput-Participant: claude
Disput-Role: implementer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 12:29:24 +02:00
co-authored by Claude Opus 5.5
commit 59e046ad99
8 changed files with 538 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
# Backlog — disput
Une tâche par ligne : `- [ ] B-<n> : …`. On coche à la fin du tour qui
la termine, en citant ce tour. `[USER]` = décision humaine, jamais prise
par un implémenteur. Numéros uniques, jamais réutilisés.
## v0 — amorçage (le relais remplace la navette manuelle)
- [ ] B-1 : squelette du paquet Python (`pyproject.toml`, `src/disput/`,
point d'entrée `disput`, `argparse` + `tomllib`, aucune dépendance
externe en v0), `tests/` en `unittest`
- [ ] B-2 : parseur de fichiers de tour (en-tête `+++` TOML, sections
Réponses/Items, identifiants, destinataires) + `disput lint`
- [ ] B-3 : déduction de l'état des items à partir des tours (§5) +
`disput status`
- [ ] B-4 : ordonnanceur (§7) en **fonction pure** :
(config, tours, diff du dernier tour) → (participant, rôle) | USER
| DONE. Tests exhaustifs, y compris l'anti ping-pong
- [ ] B-5 : vérification des invariants après un tour (§8), git
uniquement
- [ ] B-6 : adaptateurs `claude` et `vibe` (lancement non interactif,
env forcé, budget/max-turns, log local) + adaptateur `stub` pour
les tests
- [ ] B-7 : `disput run` (verrou, boucle, arrêt, `.disput/last-run.md`,
notification) + scénarios stub « tricheurs » : pas de commit,
réécriture, modification d'un tour existant, réviseur qui touche
au code, `status=USER`
- [ ] B-8 : `disput decide "…"` (écrit et commite le tour `user`)
- [ ] B-9 : garde-fous appliqués par adaptateur, à partir de `[guards]`
(deny et hook Claude, agent et hook `pre_tool` Vibe), avec preuve
qu'un `git push` est refusé dans chaque adaptateur
- [ ] B-10 : `disput init` (crée `.disput/` dans un projet existant)
- [ ] B-11 : premier `disput run --max-turns 2` sur disput lui-même,
sous surveillance de l'humain
- [ ] B-12 : adoption par qbt-manager (`disput init`, `disput.toml`
avec les checks preflight/tests et les garde-fous dry-run, rôle
`security` sur ses chemins sensibles)
## Décisions humaines
- [ ] B-13 [USER] : licence, et dépôt public ou privé (remote)
## Plus tard (seulement sur besoin observé)
- ACP comme transport unifié des adaptateurs, avec des garde-fous
centralisés
- reviewers en parallèle (worktrees)
- interface en terminal (affichage des tours en direct, décisions à
l'écran)
- adaptateurs supplémentaires (codex, gemini, opencode…)
+53
View File
@@ -0,0 +1,53 @@
# Configuration disput pour le développement de disput lui-même.
[project]
name = "disput"
instructions = "PROTOCOL.md"
[[participants]]
name = "claude"
adapter = "claude"
roles = ["implementer", "reviewer", "security"]
[[participants]]
name = "vibe"
adapter = "vibe"
roles = ["implementer", "reviewer", "security"]
[[participants]]
name = "user"
adapter = "human"
roles = ["decider"]
[[roles]]
name = "implementer"
writes_code = true
triggers = ["backlog"]
brief = "Prendre la tâche ouverte la plus haute du backlog, l'implémenter, la tester réellement, commiter."
[[roles]]
name = "reviewer"
writes_code = false
triggers = ["code_changed"]
brief = "Review sévère du diff depuis la base du tour : bugs, sécurité, hypothèses non validées, écart entre code et tours. Ré-exécuter, ne pas croire."
[[roles]]
name = "security"
writes_code = false
triggers = ["paths:src/disput/guards*,src/disput/adapters/**,.disput/disput.toml"]
brief = "Audit des garde-fous : un agent peut-il contourner un deny, échapper à l'env forcé, pousser, réécrire l'historique ?"
[[roles]]
name = "decider"
writes_code = false
triggers = []
brief = "L'humain. Tranche les décisions [USER]."
[[checks]]
name = "tests"
command = "python -m unittest discover -s tests"
policy = "no_regression" # passera à must_pass dès qu'il y aura des tests
[guards]
env = {}
deny = ["git push", "git filter-branch", "git filter-repo", "git rebase", "git reset --hard", "git commit --amend", "git update-ref", "git reflog expire"]
+98
View File
@@ -0,0 +1,98 @@
+++
turn = 1
participant = "claude"
role = "implementer"
base = "" # premier tour, pas de HEAD antérieur
status = "CONTINUE"
+++
# Tour 1 — claude, implementer : fondation du dépôt
## Réponses
Aucune : premier tour.
## Travail
Décision `user` à l'origine du dépôt, prise hors tour et consignée ici
(règle 5) :
- créer un outil d'orchestration multi-agents, nommé **disput**, dans
son propre dépôt ;
- ouvrir dès le départ le protocole à **N participants et à des rôles
configurables**, pas seulement à un dialogue à deux ;
- développer l'outil **en parallèle de son expérimentation**, avec son
propre protocole (dogfooding) ;
- objectif premier : supprimer la navette manuelle (« à toi ») de
l'humain.
Ce tour pose :
- `README.md` : raison d'être, principes, usage visé ;
- `PROTOCOL.md` : spec normative v0 (concepts, fichiers de tour
immuables, cycle de vie des items, rôles et déclencheurs,
ordonnancement, invariants, garde-fous, place de l'humain, pratique
manuelle) ;
- `.disput/disput.toml` : configuration du projet lui-même (claude et
vibe éligibles aux trois rôles, user décideur) ;
- `.disput/backlog.md` : B-1 à B-13 ;
- `AGENTS.md` / `CLAUDE.md` minces, qui renvoient à `PROTOCOL.md`.
Origine : ce protocole généralise celui pratiqué sur 16 tours dans un
autre projet du même utilisateur (dialogue Claude/Vibe en
implémenteur/reviewer alternés via un DEVLOG unique). Ce qui est repris :
- la réponse point par point ;
- l'interdiction de modifier les entrées de l'autre ;
- l'obligation de vérifier soi-même.
Ce qui change :
- un fichier par tour plutôt qu'un DEVLOG unique ;
- l'état des items déduit des tours ;
- un ordonnanceur à règles au lieu d'une simple alternance ;
- des invariants vérifiables par machine.
## Vérifications
Aucun code ce tour, donc rien à exécuter. Relecture de cohérence :
l'en-tête de ce fichier suit le §4, et les identifiants d'items le §4
également.
## Items
- [I-0001-1] → role:reviewer : en-tête de tour en **TOML entre `+++`**
plutôt qu'en YAML `---`, pour rester sans dépendance (`tomllib` est
dans la stdlib depuis 3.11). C'est moins répandu que YAML chez les
agents. Risque-t-il de mal formater des en-têtes ? Le coût d'une
dépendance YAML est-il préférable ?
- [I-0001-2] → role:reviewer : l'état des items est **déduit** des
tours, sans fichier d'état. C'est robuste à la falsification, mais
dépendant d'un parsing strict des lignes `- [I-…]` dans du markdown
libre. Faut-il un bloc structuré (TOML) pour les réponses et les items
plutôt que des puces ?
- [I-0001-3] → role:reviewer : **trou dans le §7** que je signale
moi-même. Un item adressé à `@participant`, sans rôle, ne dit pas dans
quel rôle ce participant doit jouer. Ma proposition : il joue dans le
rôle qu'il tenait au tour visé par l'item, et à défaut `implementer`.
D'accord, ou faut-il interdire l'adressage `@participant` nu ?
- [I-0001-4] → role:reviewer : « un tour = un rôle ». Si `reviewer` et
`security` sont déclenchés par le même diff et qu'un seul participant
est éligible (≠ auteur), il joue deux tours d'affilée. Est-ce
acceptable, ou faut-il autoriser un tour multi-rôles (un fichier, deux
sections) ?
- [I-0001-5] → role:security : le §9 promet trop. Des `deny` par motif
de chaîne se contournent facilement : `git -c … push`, des alias, un
`sh -c`, un `subprocess` en Python… L'invariant §8.3 (refs distantes)
ne détecte le push qu'**après coup**. Piste de défense en amont :
forcer dans `[guards].env` des variables qui rendent le transport
inopérant pendant un tour, par exemple une commande SSH qui échoue
toujours, ou un `pushurl` invalide via la config git d'environnement.
Est-ce suffisant, contournable, ou à combiner avec autre chose ?
- [I-0001-6] → role:reviewer : v0 **sans aucune dépendance externe**
(`argparse`, `unittest`, `tomllib`). Est-ce tenable jusqu'à B-7 ? Ou
vaut-il mieux assumer tout de suite une CLI (Typer/Click) et pytest ?
**Prochain tour, d'après le §7 :** ce tour modifie des fichiers hors de
`.disput/` (`code_changed`), donc c'est au **reviewer**, tenu par
**vibe** (≠ auteur). `.disput/disput.toml` correspond aussi au
déclencheur `paths:` de `security`, donc vibe joue ensuite **security**,
sauf si I-0001-4 est tranché autrement entre-temps.
+9
View File
@@ -0,0 +1,9 @@
.disput/logs/
.disput/run.lock
.disput/last-run.md
.venv/
__pycache__/
*.pyc
*.egg-info/
dist/
build/
+21
View File
@@ -0,0 +1,21 @@
# AGENTS.md — instructions pour les agents (Vibe et autres)
Ce dépôt se développe **avec son propre protocole**. Tu es un
participant ; ton nom est celui déclaré dans `.disput/disput.toml`
(Vibe = `vibe`).
1. Lis **[`PROTOCOL.md`](PROTOCOL.md) en entier** avant toute action :
c'est la seule source des règles.
2. Sur « à toi » : applique le §11 (pratique manuelle) du protocole.
3. Signe ton fichier de tour `NNNN-vibe-<role>.md`, et commite avec ce
format de message :
```
tour NNNN (vibe, <role>) : <résumé>
Disput-Turn: NNNN
Disput-Participant: vibe
Disput-Role: <role>
```
4. Jamais de push, de réécriture d'historique ni de modification d'un
fichier de tour existant (§3 et §8 du protocole).
+10
View File
@@ -0,0 +1,10 @@
# CLAUDE.md
@PROTOCOL.md
Tu es le participant `claude` (voir `.disput/disput.toml`). Sur « à toi » :
applique le §11 du protocole ci-dessus. Signe tes tours
`NNNN-claude-<role>.md`, et commite avec les trailers
`Disput-Turn` / `Disput-Participant: claude` / `Disput-Role`, sur le modèle
d'`AGENTS.md`. Jamais de push, de réécriture d'historique ni de
modification d'un fichier de tour existant.
+228
View File
@@ -0,0 +1,228 @@
# Protocole disput — v0 (brouillon)
Ce document est **normatif**. Il s'applique aux agents comme à l'outil.
On peut le pratiquer sans l'outil : chaque agent lit ce fichier, puis
l'état de `.disput/`, et joue son tour.
Les mots en MAJUSCULES (DOIT, NE DOIT PAS, PEUT) ont valeur de règle.
---
## 1. Concepts
| Concept | Définition |
|---|---|
| **Participant** | Un client IA (adaptateur + modèle) ou l'humain. Identifié par un nom court : `claude`, `vibe`, `user`. |
| **Rôle** | Une fonction dans le dialogue, définie dans `disput.toml` : responsabilités, droits, déclencheurs. Un participant PEUT tenir plusieurs rôles. |
| **Tour** | Une intervention d'un participant dans un rôle. Elle produit **un fichier de tour** et **au moins un commit**. |
| **Item** | Une objection, une question ou une proposition, adressée à un participant ou à un rôle. Elle a un cycle de vie (§5). |
| **Backlog** | La liste ordonnée des tâches du projet (`.disput/backlog.md`). |
| **Décision** | Un choix réservé à l'humain. Le dialogue s'arrête jusqu'à ce qu'il tranche. |
## 2. Arborescence dans un projet
```
.disput/
disput.toml # participants, rôles, déclencheurs, contrôles, garde-fous
backlog.md # backlog unique du projet
turns/
0001-claude-implementer.md
0002-vibe-reviewer.md
...
archive/ # chantiers clos, déplacés verbatim (optionnel)
```
Fichiers locaux, **jamais commités** : `.disput/logs/`, `.disput/run.lock`,
`.disput/last-run.md`.
## 3. Règles générales
1. **Un tour = un fichier de tour + au moins un commit.** Pas de fichier
sans commit, pas de code sans tour qui l'explique.
2. **Les fichiers de tour sont immuables.** Une fois commité, un fichier
de tour NE DOIT PLUS JAMAIS être modifié, ni par son auteur ni par un
autre. Pour corriger, on écrit un nouveau tour.
3. **Réponse point par point.** Chaque item adressé à un participant DOIT
recevoir une réponse explicite de sa part, à son prochain tour, avant
tout autre travail. Les réponses groupées sont interdites.
4. **Vérifier, pas croire.** Un reviewer DOIT ré-exécuter ce qu'il peut
ré-exécuter (tests, scripts, commandes citées) au lieu de faire
confiance au récit de l'auteur.
5. **Toute décision est consignée** dans un tour, avec sa justification.
Le code doit refléter les tours.
6. **Pas de réécriture d'historique**, pas de force-push, pas de push
pendant un tour, sauf décision `user` consignée qui l'autorise
explicitement.
7. **Diversité des regards.** Le reviewer d'un tour DOIT être un autre
participant que l'auteur de ce tour. On DEVRAIT choisir un autre
fournisseur de modèle.
8. Les règles propres au projet (prod, secrets, dépôt public…) vivent
dans `disput.toml` (`[guards]`, `[[checks]]`) et dans le fichier
d'instructions du projet. Elles s'ajoutent à celles-ci et n'en
retirent aucune.
## 4. Fichier de tour
Nom : `NNNN-<participant>-<role>.md`. `NNNN` est le numéro du tour, sur
4 chiffres et strictement croissant.
En-tête TOML entre `+++` (sans dépendance : `tomllib`) :
```markdown
+++
turn = 17
participant = "vibe"
role = "implementer"
base = "a1b2c3d" # HEAD au début du tour
status = "CONTINUE" # CONTINUE | USER | DONE
+++
# Tour 17 — vibe, implementer : <titre court>
## Réponses
- [I-0016-1] ACCEPTÉ : <ce qui est fait, ou va l'être>
- [I-0016-2] REJETÉ : <raison technique>
## Travail
<ce qui a été fait, et pourquoi>
## Vérifications
<commandes exécutées et résultats, sans sortie brute sensible>
## Items
- [I-0017-1] → @claude : <question ou objection>
- [I-0017-2] → role:security : <demande d'audit>
## Décision requise (seulement si status = "USER")
<question fermée, options numérotées, recommandation>
```
- Sections **Réponses** et **Items** : une ligne par item, avec
l'identifiant entre crochets en tête. Ce format sert au parsing et
DOIT être respecté à la lettre.
- Identifiant d'item : `I-<tour sur 4 chiffres>-<n>`, attribué par son
auteur.
- Destinataire : `@<participant>` ou `role:<rôle>`.
## 5. Cycle de vie d'un item
```
┌─ ACCEPTÉ ── CORRIGÉ ─┬─ CLOS
OUVERT ─────┤ (destinataire) └─ ROUVERT ─→ (retour à OUVERT)
(auteur) └─ REJETÉ ─────────────┬─ CLOS
(destinataire) └─ ESCALADÉ ─→ role:arbiter ou @user
(auteur)
```
- `ACCEPTÉ`, `REJETÉ` et `CORRIGÉ (<sha>)` sont posés par le
**destinataire**.
- `CLOS`, `ROUVERT` et `ESCALADÉ` sont posés par l'**auteur** de l'item,
qui vérifie lui-même la correction (règle 4).
- L'état d'un item n'est stocké nulle part : il se **déduit** de la
suite des fichiers de tour. C'est pour ça qu'ils sont immuables.
- Le parseur DEVRAIT accepter les mots-clés avec ou sans accent.
## 6. Rôles et déclencheurs
Un rôle se déclare dans `disput.toml` :
```toml
[[roles]]
name = "reviewer"
writes_code = false # ne peut modifier que .disput/
triggers = ["code_changed"]
brief = "Review sévère du diff : bugs, sécurité, hypothèses non validées, écart code/tours."
```
Déclencheurs v0 :
| Déclencheur | Le rôle doit jouer quand… |
|---|---|
| `items_pending` | des items lui sont adressés et attendent une réponse (implicite pour tous les rôles) |
| `code_changed` | le dernier tour a modifié des fichiers hors de `.disput/` |
| `paths:<glob>,<glob>` | le dernier tour a touché un de ces chemins |
| `backlog` | aucun item en attente, et le backlog a une tâche ouverte, non `[USER]` |
Une **affectation** associe un rôle aux participants qui peuvent le
tenir :
```toml
[[participants]]
name = "claude"
adapter = "claude"
roles = ["implementer", "reviewer", "security"]
```
## 7. Ordonnancement : qui joue ensuite
Après chaque tour, on choisit le prochain **(participant, rôle)** dans
l'ordre de priorité suivant :
1. Si le dernier tour a `status = "USER"` : **arrêt**, on attend
`@user`.
2. **Items en attente.** S'il en existe, le plus ancien item non résolu
donne la main à celui qui doit agir : le destinataire pour répondre
ou corriger, l'auteur pour clore, rouvrir ou escalader.
3. **Déclencheurs.** Sinon, si le dernier tour a changé du code, chaque
rôle déclenché (`code_changed`, `paths:`) qui n'a pas encore joué sur
ce diff passe, dans l'ordre de `disput.toml`, tenu par un
participant ≠ l'auteur du code (règle 7).
4. **Backlog.** Sinon, le rôle `implementer` prend la tâche ouverte la
plus haute, non `[USER]`. S'il y a plusieurs participants éligibles,
on tourne entre eux (celui qui a implémenté le moins récemment).
5. Sinon : **DONE**.
Pour un même rôle, on choisit le participant éligible qui a joué le
moins récemment. **Anti ping-pong** : si deux tours de review successifs
sur le même diff n'ouvrent aucun item et ne posent aucun `REJETÉ`, le
dialogue sur ce diff est clos.
## 8. Invariants vérifiés après chaque tour
Si un invariant échoue, l'outil s'arrête et le signale à `@user`. Sans
l'outil, le reviewer suivant DOIT les vérifier lui-même.
1. Au moins un nouveau commit, et un arbre de travail propre.
2. L'ancien HEAD est un ancêtre du nouveau : pas de réécriture.
3. Aucune ref distante n'a bougé : pas de push.
4. Un seul fichier de tour ajouté, avec le bon numéro et un en-tête
valide. Aucun fichier de tour existant n'a été modifié.
5. Un rôle `writes_code = false` n'a modifié que `.disput/`.
6. Chaque item adressé à ce participant et en attente a reçu une réponse
dans ce tour (règle 3).
7. Les contrôles du projet (`[[checks]]`) respectent leur politique :
- `must_pass` : le contrôle doit réussir ;
- `no_regression` : s'il réussissait avant le tour, il doit encore
réussir après.
## 9. Garde-fous (`[guards]`)
- `env` : variables imposées à l'agent pendant son tour, par exemple
`DRY_RUN=true`.
- `deny` : motifs de commandes interdites (push, réécriture, passage en
prod…). L'outil les applique **dans chaque adaptateur** : les deny et
hooks de Claude Code, l'agent et le hook `pre_tool` de Vibe. Plus tard,
ils seront appliqués de façon centralisée via ACP.
## 10. L'humain
`user` est un participant comme les autres, avec le rôle `decider`. Il
répond à un tour `status = "USER"` par son propre tour
(`NNNN-user-decider.md`). Sans l'outil, il écrit ce fichier à la main.
Avec l'outil, il passe par `disput decide "…"`, qui l'écrit et le
commite. Ses décisions PEUVENT lever une règle du §3, explicitement et
de façon bornée.
## 11. Pratique manuelle (avant que l'outil existe)
Quand l'agent reçoit « à toi » :
1. lire ce fichier, `disput.toml`, `backlog.md` et les fichiers de tour
depuis le dernier tour de ce participant (ou tous, s'il n'en a
aucun) ;
2. appliquer le §7 pour vérifier que c'est bien son tour, et dans quel
rôle. Si ce n'est pas son tour, le dire et s'arrêter ;
3. vérifier les invariants du §8 sur le tour précédent ;
4. jouer le tour, écrire le fichier de tour, commiter ;
5. résumer à l'humain : ce qui est fait, qui joue ensuite, et la
décision attendue s'il y en a une.
+69
View File
@@ -0,0 +1,69 @@
# disput
> *disput*, de *disputatio* : la dispute scolastique, où chaque objection
> reçoit une réponse.
`disput` orchestre **plusieurs clients IA en ligne de commande** (Claude
Code, Mistral Vibe, puis d'autres) qui travaillent ensemble sur un même
dépôt git. Chaque participant y tient un **rôle** : implémenteur,
reviewer, auditeur sécurité… Le travail se fait **par tours**, sous un
protocole strict, et tout l'état du dialogue vit dans le dépôt.
`disput` ne remplace pas le harness de chaque agent : il **les fait
jouer à tour de rôle**, vérifie après chaque tour que les règles ont
été respectées, et ne rend la main à l'humain que lorsqu'une décision
lui revient.
## Pourquoi
Le protocole est né sur un projet réel, où Claude et Vibe alternaient
implémentation et review sévère. Sur 16 tours, la review croisée entre
modèles a attrapé de vrais bugs, que l'implémenteur avait pourtant
déclarés corrigés. Deux limites sont apparues :
- l'humain servait de **bus de messages** : il tapait « à toi » dans un
agent, puis dans l'autre ;
- les garde-fous (pas de prod, pas de push, pas de réécriture
d'historique) n'étaient que **déclaratifs**.
`disput` automatise le relais, impose les garde-fous et ouvre le
protocole à **N participants et à des rôles configurables**.
## Principes
1. **Le protocole d'abord, l'outil ensuite.** [`PROTOCOL.md`](PROTOCOL.md)
se suffit à lui-même : on peut le pratiquer à la main, en tapant
« à toi » à chaque agent. L'outil ne doit jamais devenir indispensable
pour avancer.
2. **Tout l'état est dans git.** Tours, objections, backlog, décisions :
des fichiers versionnés, lisibles par les humains comme par les
machines.
3. **Les garde-fous sont techniques.** Une règle que l'outil ne sait pas
vérifier ou bloquer n'est qu'un vœu.
4. **Pas de fonctionnalité sans besoin observé.** Une fonctionnalité
n'entre que si un projet réel en a eu besoin, et ce besoin est
consigné dans un tour.
5. **disput se développe avec disput.** Ce dépôt est son propre premier
projet client (voir `.disput/`).
## État
**Phase d'amorçage** : la spec existe, pas encore le code. Le
développement suit le protocole **à la main** jusqu'à ce que la v0 de
`disput run` sache prendre le relais. Voir
[`.disput/backlog.md`](.disput/backlog.md) et
[`.disput/turns/`](.disput/turns/).
## Utilisation visée (v0)
```bash
disput init # crée .disput/ dans un projet (config, backlog, tours)
disput status # qui joue ensuite, objections ouvertes, décisions en attente
disput run # enchaîne les tours jusqu'à DONE, USER ou un invariant violé
disput decide "texte" # enregistre la décision de l'humain et relance
disput lint # valide les fichiers de tour et la config
```
## Licence
À décider (voir le backlog, item `[USER]`).