La boucle de correction & le SDLC complet
L'échec de gate cesse d'arrêter le run : il repart vers le builder en enveloppe, dans sa session vivante — et adw_sdlc.py déroule plan → build → test → review → document en une seule commande.
Hier, vous avez peut-être vu un run d’adw_build mourir en rouge sur un échec de gate. Regardez
ce que l’usine avait entre les mains à cet instant : un motif d’échec déjà rédigé, preuve à
l’appui, et la session du builder encore vivante : l’agent qui venait d’écrire le code presque
bon attendait, tout son contexte en tête. Et le run s’arrêtait quand même. Aujourd’hui, vous
fermez cette boucle : l’échec de gate repart vers le builder, en enveloppe, par la même porte
qu’un rapport d’agent, la symétrie que le chapitre 9 vous avait promise. Comme la chaîne sait
désormais se corriger, elle peut s’allonger : à la fin du chapitre, une seule commande déroulera
le SDLC complet (plan → build → test → review → document) et votre usine atteindra le premier
grand jalon du livre, un cycle de développement entier, vert de bout en bout, sur Plume. Deux
pièces se posent : adws/adw_sdlc.py, la chaîne maîtresse, et la version chapitre 13 du roster,
qui accueille son cinquième agent, le documenter.
La boucle de correction : l’échec revient en enveloppe
L’idée en une phrase
La boucle de correction transforme un échec de gate en travail à refaire : le code met le verdict des gates en forme d’enveloppe et le renvoie au builder dans sa session vivante, par la même porte qu’un rapport d’agent. La boucle elle-même vit côté code déterministe : le runner retente, borné, et les gates re-tranchent à chaque tour.
Points clés
- La symétrie du chapitre 9, enfin exploitée. Un résultat produit par du code peut être mis
en forme d’enveloppe et tendu à un agent par la même porte qu’un rapport d’agent : le verdict
des gates devient une
Envelope(status: fail, motif dansnotes_for_next_agent), injectée parhandoff(). Le builder ne fait pas la différence, et c’est voulu. - La session vivante fait toute l’économie. Le builder qui reçoit le verdict a encore la spec, le code et ses choix en contexte : la reprise coûte quelques milliers de tokens, contre un redémarrage à froid qui rejouerait tout le build.
- La boucle est bornée par le code.
--fix-loopsfixe le nombre de reprises (3 par défaut), ce n’est jamais « jusqu’à ce que ça passe ». Une boucle non bornée est une facture non bornée. - Les gates re-tranchent à chaque tour. Elles tournent dans la phase build, après chaque tentative : une phase build verte signifie gates vertes sur le dernier état du code, pas de feu vert périmé.
- Trois échecs, trois portes, et la boucle n’en ouvre qu’une de plus. Le contrat repart via
correction()(chapitre 9), la gate repart en enveloppe viahandoff()(aujourd’hui), la brèche de permission ne repart jamais : l’écriture est annulée et la phase meurt (chapitre 10).
Exemple concret
Le builder implémente la spec, déclare ses fichiers, mais bun test rend exit 1 : une assertion
casse. Hier : run rouge, ~10 à 20 centimes dépensés pour rien, et c’est vous qui relisiez le
motif avant de relancer un run entier. Aujourd’hui, le verdict (gate nommée, exit 1, la queue
de la sortie en preuve) repart dans la session vivante du builder. Il voit le test qui casse,
corrige les quelques lignes, relance la suite, redéclare ses fichiers : ~2 à 5 centimes, une à
deux minutes de plus, et les gates re-tranchent. Le run entier reste vert sans que vous ayez
levé les yeux. La reprise coûte un facteur 5 environ de moins que le re-run, et surtout elle ne
coûte plus votre attention.
Trois échecs, trois portes
| Échec | Qui le détecte | Ce qui repart vers l’agent | Session |
|---|---|---|---|
| contrat — enveloppe invalide | parse() + check(), en code | le motif exact, via correction() | la même, vivante |
| gate — déclaration fausse, tests rouges | les gates, en code | le verdict, mis en enveloppe et injecté par handoff() | la même, vivante |
| brèche de permission | l’état des lieux, en code | rien — l’écriture est annulée, la phase meurt | le run s’arrête |
Script — la boucle, au cœur de la phase build
Cette pièce ne touche pas le harnais : le roster décide qui tourne, le port traduit, une seule
version suffit donc. Le cœur d’adw_sdlc.py (fichier complet dans les travaux pratiques) : les
gates tournent dans la phase, et leur échec prépare la relance au lieu de tuer le run :
# Les gates, DANS la phase build : leur echec doit revenir au builder
# dans sa session vivante — pas arreter le run comme dans adw_build.
failed = gates.motif(reports)
if failed:
verdict = Envelope(status="fail", summary="gates en echec sur ton build",
notes_for_next_agent=failed)
# Le verdict du code part par la meme porte qu'un rapport d'agent :
# une enveloppe, injectee par handoff() — la symetrie du chapitre 9.
next_ask = (REPAIR_ASK + "\n\n" + envelopes.handoff(verdict) + "\n\n"
+ envelopes.contract(BuildEnvelope))
raise PhaseFailure(f"gates en echec :\n{failed}")
Piège courant : « la boucle de correction, c’est laisser l’agent réessayer jusqu’à ce que ça passe » décrit une autre machine, une machine sans frein. Ici, chaque tour est cadré trois fois par le code : le verdict qui repart est déterministe (les gates, pas une impression), le nombre de reprises est borné (
--fix-loops), et c’est le même juge qui re-tranche à chaque tour. L’agent ne négocie pas la sortie de la boucle, il la mérite.
Review, Document : le SDLC de bout en bout
L’idée en une phrase
adws/adw_sdlc.py enchaîne le SDLC complet (plan → build → test → review → document) en
dix-huit phases dont quatre seulement sont des phases agent : le reviewer répond à la
seconde question (« est-ce ce qui était demandé ? », que les gates ne savent pas poser), le
documenter écrit la mémoire du changement sous app_docs/, et tout le séquencement reste
côté code déterministe.
Points clés
- Deux questions, deux juges. Les gates tranchent « est-ce que ça tourne ? », mécaniquement et pour zéro token. Le reviewer juge « est-ce ce qui était demandé ? » contre la spec, son unique contrat, exigence par exigence. Aucun des deux ne sait répondre à la question de l’autre.
statusn’est pas le verdict. DansReviewEnvelope,status: successdit que la revue a abouti. Le verdict, c’estapproved, et un refus doit nommer ses findings,check()l’exige.- Le reviewer juge les mains liées :
writes: []au roster depuis le chapitre 10, état des lieux après sa phase. L’exemple du chapitre 10, le reviewer serviable qui « corrige au passage », est désormais un cas que votre usine détecte et annule en vrai. - Un refus de review arrête le run en rouge, délibérément. Un échec de gate est mécanique et la machine le renvoie au builder. Un refus de review met en cause la conformité à la spec, et cet arbitrage vous revient : la corriger coûte zéro token (chapitre 11), et personne ne laisse deux agents renégocier la spec entre eux.
- Le documenter écrit pour l’ingénieur qui arrive après : ce qui a changé, où ça vit, comment le vérifier, traçable aux fichiers du run, jamais un fichier que le run n’a pas touché. Il tourne sur le modèle léger par défaut du roster : rédiger d’après un diff est un travail de rapport, pas de décision. Un compte rendu ne s’écrase jamais.
Exemple concret
Lancez adw_sdlc.py sur « ajoute un bouton pour effacer le texte de l’éditeur de Plume ». Le
scout lit Plume sur le léger : moins d’un centime. Le planner pose la spec sur le frontier :
~15 à 25 centimes. Le builder implémente sur le workhorse : ~10 à 20 centimes, et si une
gate recale le build, la reprise ajoute ~2 à 5 centimes. Le reviewer confronte le code à la
spec sur l’intermédiaire : ~2 à 5 centimes. Le documenter rédige sur le léger : ~1
centime. Total : ~40 à 60 centimes, 10 à 15 minutes, quatre agents, un seul geste de votre
part, et à l’arrivée un code retour plus deux artefacts durables : la spec dans specs/, le
compte rendu dans app_docs/. La variante éco tient en une ligne : passez le model: du planner
sur le workhorse, et le même SDLC tombe à ~20 à 30 centimes.
Le SDLC en une commande
| Étape | Qui | Côté couture | Ce qui traverse |
|---|---|---|---|
| plan | scout léger, puis planner frontier | agent | la demande → PlanEnvelope (la spec) |
| build | builder workhorse | agent | la spec → BuildEnvelope |
| test | les gates | code | le verdict — en enveloppe s’il repart |
| review | reviewer intermédiaire, writes: [] | agent | ReviewEnvelope : approved + findings |
| document | documenter léger | agent | DocumentEnvelope : le compte rendu sous app_docs/ |
Config — le cinquième agent du roster
Le documenter rejoint la feuille de distribution, seule évolution du roster
aujourd’hui, et le fichier complet est dans les travaux pratiques. Il hérite du modèle léger et du
thinking des défauts : il rapporte, il ne décide pas.
# Le documenter : rediger la memoire du changement, rien d'autre.
# Modele et thinking herites des defauts (leger, medium) — rediger d'apres
# un diff est un travail de rapport, pas de decision.
- name: documenter
purpose: Rediger apres coup ce qui a change et pourquoi ; n'ecrire que sous app_docs/.
writes:
- app_docs/ # la memoire de l'usine — et rien d'autre
tools: [read, bash, grep, find, ls, write]
Piège courant : « un refus de review devrait repartir vers le builder, comme un échec de gate » semble cohérent, c’est pourtant confondre deux natures d’échec. La gate est un juge déterministe : son verdict est un fait, la machine peut le renvoyer sans arbitrage. Un refus de review dit que le code et la spec divergent, et la bonne correction est parfois dans la spec, pas dans le code. Renvoyer le refus en boucle, c’est laisser deux agents converger entre eux vers autre chose que ce que vous avez demandé. Le run rouge vous rend l’arbitrage : relire les findings, corriger la spec à coût nul, relancer.
Fil rouge — la pièce posée aujourd’hui
Deux pièces closent la zone « squelette ADW » du plan : adws/adw_sdlc.py, la chaîne maîtresse
qui s’articule sur tout ce que le module a posé (runner, enveloppes, roster, permissions, scout,
plan, build, gates) et la version chapitre 13 de factory.config.yaml, qui distribue le
cinquième rôle. La loi du livre tourne désormais en circuit fermé : l’agent propose, le code
dispose, et quand le code refuse, son verdict repart vers l’agent en enveloppe, par la même
porte qu’un rapport, dans la session qui a produit le presque-bon. Quatorze phases code, quatre
phases agent, et le contexte ne traverse qu’en enveloppes typées : ScoutEnvelope,
PlanEnvelope, BuildEnvelope, ReviewEnvelope, DocumentEnvelope, plus celle, fabriquée par
le code, qui transporte les verdicts de gates. À l’usage : ~40 à 60 centimes et 10 à
15 minutes le cycle complet (~20 à 30 en variante éco). Ce que la pièce économise, c’est le
re-run entier à chaque accroc (la reprise en session vivante coûte un facteur 5 de moins) et
l’après-midi d’ingénieur qu’aurait coûté le même cycle mené à la main. Le jalon du module est
atteint : votre usine déroule un SDLC entier, vert, en une commande. Le module 4 s’attaque à ses
moteurs : quel modèle pour quelle phase, et comment le mesurer.
Travaux pratiques — la pièce du jour
Une pièce complète à poser dans le repo compagnon plume-factory, qui devient, chapitre après
chapitre, votre usine logicielle agentique. Aujourd’hui, deux fichiers : le roster version
chapitre 13 (le documenter rejoint la distribution) et la chaîne maîtresse adw_sdlc.py. Les ADW
des chapitres précédents tournent tels quels : adw_build.py garde son comportement d’hier
(l’échec de gate y arrête le run), la boucle de correction étant le propre de la chaîne complète.
Pièce — adws/adw_config/factory.config.yaml
Cette version remplace celle du chapitre 10. Une seule évolution : l’entrée documenter en
fin de roster, rien d’autre ne bouge. Identifiants et prix relevés sur openrouter.ai/models le
23 août 2026, des exemples datés à vérifier le jour où vous poserez la pièce.
# factory.config.yaml — le roster de l'usine : un agent, un role, un modele.
# Les ADW nomment des agents, jamais des modeles : changer de moteur, c'est
# changer UNE ligne ici, aucun script. Identifiants et prix releves sur
# openrouter.ai/models le 2026-08-23 — des exemples dates, pas des verites.
defaults:
harness: pi # pi | claude — les deux adaptateurs du port (ch. 7)
model: deepseek/deepseek-v4-flash-0731 # leger : ~0,07 $/M entree, ~0,18 $/M sortie
thinking: medium # off | minimal | low | medium | high | xhigh | max
tools: [read, bash, grep, find, ls] # lecture + execution ; ecrire se merite
# Interdit a tout agent qui ne le nomme pas lui-meme dans son `writes` :
# un agent ne doit pas pouvoir editer la machinerie qui juge son travail.
protected_files:
- adws/adw_modules/
- adws/adw_config/
- adws/adw_*.py
- PLAN.md
data_dir: adws/adw_data # le runtime : TOUJOURS ouvert, jamais commite
agents:
- name: scout
purpose: Reperer ou vivent les choses dans le repo ; ne rien changer.
thinking: low # il rapporte, il ne decide pas
writes: [] # lecture seule vis-a-vis du repo — pas muet :
# son rapport atterrit sous data_dir
# write : indispensable pour poser ce rapport — l'allowlist regle
# l'interface, c'est writes: [] (verifie apres coup) qui protege le repo.
tools: [read, bash, grep, find, ls, write]
- name: planner
purpose: Transformer une demande en plan que le builder implemente sans questions.
model: anthropic/claude-opus-5 # frontier : ~5 $/M entree, ~25 $/M sortie
thinking: high # le plan porte tout le run — c'est ici qu'on paie
writes:
- specs/ # le plan est la seule trace qu'il laisse au repo
tools: [read, bash, grep, find, ls, write]
- name: builder
purpose: Implementer le plan, exactement ; declarer chaque fichier modifie.
model: z-ai/glm-5.3 # workhorse : ~1,40 $/M entree, ~4,40 $/M sortie
thinking: high
# pas de cle writes : libre dans le repo — mais protected_files tient toujours.
tools: [read, bash, grep, find, ls, edit, write]
- name: reviewer
purpose: Confirmer que ce qui est construit est ce qui etait demande ; ne rien changer.
model: google/gemini-3.7-flash # intermediaire rapide : ~0,38 $/M entree
thinking: high # juger demande de reflechir, pas d'ecrire
writes: [] # un reviewer qui ne peut pas corriger ne peut pas
# corriger en douce — regle, plus promesse
- name: documenter
purpose: Rediger apres coup ce qui a change et pourquoi ; n'ecrire que sous app_docs/.
# modele et thinking herites des defauts (leger, medium) : rediger
# d'apres un diff est un travail de rapport, pas de decision.
writes:
- app_docs/ # la memoire de l'usine — et rien d'autre
tools: [read, bash, grep, find, ls, write]
Pièce — adws/adw_sdlc.py
La chaîne maîtresse. Elle importe la mécanique des chapitres 11 et 12 (agent_action,
constat, perimetre, BuildEnvelope, les gates) et n’ajoute que ce qui lui est propre : la
boucle de correction dans la phase build, les deux nouveaux sous-types d’enveloppe, et les briefs
du reviewer et du documenter. Rien de posé n’est modifié.
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["pyyaml"]
# ///
"""adw_sdlc — le cycle complet : plan -> build -> test -> review -> document.
Usage :
uv run adws/adw_sdlc.py "Votre demande" [--config ...] [--retries 2] [--fix-loops 3]
La chaine maitresse de l'usine. Dix-huit phases, quatre agent. La nouveaute
du chapitre : un echec de gate ne tue plus le run — son verdict repart vers
le builder, en enveloppe, dans sa session vivante, borne par --fix-loops.
Un refus de review, lui, arrete le run en rouge : cet arbitrage vous revient.
"""
import argparse
import json
import sys
import uuid
from dataclasses import asdict, dataclass, field
from pathlib import Path
from adw_modules import envelopes, gates, harness, roster
from adw_modules.envelopes import Envelope, EnvelopeError
from adw_modules.runner import PhaseFailure, PhaseSpec, Run
from adw_scout import ScoutEnvelope, agent_action, constat, perimetre, scout_ask
from adw_plan import PlanEnvelope, plan_ask
from adw_build import BuildEnvelope, TRUTH_COMMANDS, build_ask
REQUIRED_AGENTS = ("scout", "planner", "builder", "reviewer", "documenter")
REPAIR_ASK = """Les gates ont rejete ton build — le verdict est dans l'enveloppe ci-dessous,
champ 'notes_for_next_agent'. Corrige ce qu'elle nomme, rien d'autre, relance
la suite de tests, et declare a nouveau CHAQUE fichier modifie."""
def builder_action(builder):
"""La phase build du SDLC : la boucle de correction vit DANS la phase.
Tentative 1 : la spec. Ensuite : ce que la derniere verification a
rejete — contrat OU gates — repart dans la MEME session. Le verdict des
gates voyage en enveloppe, par la meme porte qu'un rapport d'agent :
le builder ne fait pas la difference, et c'est voulu (chapitre 9).
"""
next_ask = envelopes.correction("enveloppe invalide", BuildEnvelope)
def action(run: Run, attempt: int) -> BuildEnvelope:
nonlocal next_ask
ask = build_ask(run.results["plan"].spec_path)(run) if attempt == 0 else next_ask
request = harness.HarnessRequest(
prompt=ask, session_id=run.sessions.get("build"),
model=builder.model, thinking=builder.thinking, tools=builder.tools)
try:
result = harness.run(builder.harness, request)
except harness.HarnessError as error:
raise PhaseFailure(str(error)) from None
# Memoriser la session AVANT de valider : un retry doit la poursuivre.
run.sessions["build"] = result.session_id
run.cost_usd += result.cost_usd
try:
envelope = envelopes.parse(result.text, BuildEnvelope)
except EnvelopeError as error:
next_ask = envelopes.correction(str(error), BuildEnvelope)
raise PhaseFailure(f"enveloppe invalide : {error}") from None
# Les gates, DANS la phase : re-trancher apres CHAQUE tentative —
# une phase build verte signifie gates vertes sur le dernier etat.
reports = [gates.changed_files_exist(envelope), gates.artifacts_exist(envelope)]
reports += [gates.command(cmd, cwd)(envelope) for cmd, cwd in TRUTH_COMMANDS]
failed = gates.motif(reports)
if failed:
verdict = Envelope(status="fail", summary="gates en echec sur ton build",
notes_for_next_agent=failed)
next_ask = (REPAIR_ASK + "\n\n" + envelopes.handoff(verdict) + "\n\n"
+ envelopes.contract(BuildEnvelope))
raise PhaseFailure(f"gates en echec :\n{failed}")
return envelope
return action
@dataclass(frozen=True)
class ReviewEnvelope(Envelope):
"""Ce que le reviewer doit au code : un verdict motive, jamais une retouche."""
approved: bool = field(default=False, metadata={
"ask": "true si le build correspond a la spec, false sinon"})
findings: list = field(default_factory=list, metadata={
"ask": "liste d'objets {'requirement': l'exigence, 'met': true/false, "
"'evidence': la preuve ou le manque}, [] si rien a signaler"})
def check(self) -> None:
super().check()
for finding in self.findings:
if not isinstance(finding, dict) or not finding.get("requirement"):
raise EnvelopeError(
"chaque finding doit etre un objet avec au moins 'requirement'")
if self.status == "success" and not self.approved and not self.findings:
raise EnvelopeError(
"un refus sans findings n'aide personne : nommer chaque manque")
REVIEWER_BRIEF = """Tu es le reviewer de l'usine : confirme que ce qui est construit est ce
qui etait demande. Ce n'est PAS une seance de tests — les gates s'en chargent.
- La spec est ton unique contrat : lis-la en entier, decoupe-la en exigences.
- Juge le code sur le disque, jamais le resume du builder : pars de
'changed_files', lis les fichiers, statue sur chaque exigence.
- 'status' dit si TA revue a abouti ; le verdict, c'est 'approved' — true
seulement si CHAQUE exigence est satisfaite.
- Ne change RIEN : tes findings repartent vers l'operateur, c'est l'unique
voie de reparation."""
def review_ask(run: Run) -> str:
"""La mission du reviewer : le brief, la spec, le build a juger, le contrat."""
plan_env: PlanEnvelope = run.results["plan"]
return (REVIEWER_BRIEF
+ f"\n\n### spec\n\nLa spec a confronter au code : {plan_env.spec_path}\n\n"
+ envelopes.handoff(run.results["build"]) + "\n\n"
+ envelopes.contract(ReviewEnvelope))
@dataclass(frozen=True)
class DocumentEnvelope(Envelope):
"""Ce que le documenter doit au code : ou vit le compte rendu."""
doc_path: str = field(default="", metadata={
"ask": "chemin du compte rendu ecrit sous app_docs/, '' si status=fail"})
def check(self) -> None:
super().check()
if self.status == "success" and not self.doc_path.startswith("app_docs/"):
raise EnvelopeError("doc_path doit pointer un fichier sous app_docs/")
DOCUMENTER_BRIEF = """Tu es le documenter de l'usine : redige, pour l'ingenieur qui arrive
apres, ce que ce run a change.
- Lis la spec et les fichiers de 'changed_files' : tout ce que tu ecris doit
etre tracable au code — ne nomme JAMAIS un fichier que le run n'a pas touche.
- Ecris le compte rendu dans app_docs/{adw_id}-<deux-a-quatre-mots-kebab>.md :
ce qui a change, ou ca vit, comment le verifier. Si le nom existe deja,
suffixe -v2 : un compte rendu ne s'ecrase JAMAIS.
- Court : un lecteur doit comprendre le changement en moins de deux minutes.
- N'ecris que de la documentation — jamais dans le code."""
def document_ask(run: Run) -> str:
"""La mission du documenter : le brief, la spec, le build a decrire, le contrat."""
plan_env: PlanEnvelope = run.results["plan"]
return (DOCUMENTER_BRIEF.format(adw_id=run.adw_id)
+ f"\n\n### spec\n\nLa demande d'origine, planifiee : {plan_env.spec_path}\n\n"
+ envelopes.handoff(run.results["build"]) + "\n\n"
+ envelopes.contract(DocumentEnvelope))
def verite_plan(run: Run, attempt: int) -> PlanEnvelope:
"""Phase code : la spec declaree existe — sinon inutile de payer le build."""
envelope: PlanEnvelope = run.results["plan"]
if envelope.status != "success":
raise PhaseFailure(f"le planner declare lui-meme un echec : {envelope.summary!r}")
spec = Path(envelope.spec_path)
if not spec.is_file() or spec.stat().st_size == 0:
raise PhaseFailure(f"spec declaree mais introuvable ou vide : {envelope.spec_path}")
print(f"spec posee : {envelope.spec_path}", file=sys.stderr)
return envelope
def verdict_review(run: Run, attempt: int) -> ReviewEnvelope:
"""Phase code : appliquer le verdict — un refus arrete le run, findings nommes.
Deliberement PAS une boucle : un refus de review met en cause la
conformite a la spec, et cet arbitrage revient a l'operateur — la spec
se corrige a zero token, c'est tout le levier du chapitre 11.
"""
review: ReviewEnvelope = run.results["review"]
if review.status != "success":
raise PhaseFailure(f"le reviewer declare lui-meme un echec : {review.summary!r}")
if not review.approved:
unmet = [f for f in review.findings if not f.get("met", False)]
detail = "\n".join(f" - {f.get('requirement')} : {f.get('evidence', 'non satisfait')}"
for f in unmet) or f" - {review.summary}"
raise PhaseFailure(
"review refusee — le build ne correspond pas a la spec :\n" + detail)
return review
def dispose(run: Run, attempt: int) -> DocumentEnvelope:
"""Phase code : la verite du compte rendu, puis le bilan du run."""
envelope: DocumentEnvelope = run.results["document"]
if envelope.status != "success":
raise PhaseFailure(f"le documenter declare lui-meme un echec : {envelope.summary!r}")
doc = Path(envelope.doc_path)
if not doc.is_file() or doc.stat().st_size == 0:
raise PhaseFailure(
f"compte rendu declare mais introuvable ou vide : {envelope.doc_path}")
print(json.dumps(asdict(envelope), indent=2, ensure_ascii=False))
print(f"SDLC complet : spec={run.results['plan'].spec_path} "
f"doc={envelope.doc_path}", file=sys.stderr)
return envelope
def main() -> int:
parser = argparse.ArgumentParser(
description="Le SDLC complet : plan -> build -> test -> review -> document.")
parser.add_argument("prompt", help="la demande, en langage naturel")
parser.add_argument("--config", default=str(roster.DEFAULT_PATH))
parser.add_argument("--retries", type=int, default=2,
help="reprises de contrat des phases agent, en session vivante")
parser.add_argument("--fix-loops", type=int, default=3,
help="reprises du builder quand les gates echouent")
args = parser.parse_args()
factory = roster.load(args.config) # zero token : tout echec est gratuit
missing = [name for name in REQUIRED_AGENTS if name not in factory.agents]
if missing:
print(f"roster incomplet : agents manquants {missing} — "
"posez la version chapitre 13 de factory.config.yaml", file=sys.stderr)
return 1
scout, planner = factory.agents["scout"], factory.agents["planner"]
builder = factory.agents["builder"]
reviewer, documenter = factory.agents["reviewer"], factory.agents["documenter"]
run = Run(adw_id=uuid.uuid4().hex[:8])
return run.execute([
PhaseSpec(name="constat_scout", kind="code", action=constat("scout", factory)),
PhaseSpec(name="scout", kind="agent",
action=agent_action("scout", scout, scout_ask(args.prompt, factory),
ScoutEnvelope),
retries=args.retries),
PhaseSpec(name="perimetre_scout", kind="code",
action=perimetre("scout", scout, factory)),
PhaseSpec(name="constat_plan", kind="code", action=constat("plan", factory)),
PhaseSpec(name="plan", kind="agent",
action=agent_action("plan", planner, plan_ask(args.prompt, factory),
PlanEnvelope),
retries=args.retries),
PhaseSpec(name="perimetre_plan", kind="code",
action=perimetre("plan", planner, factory)),
PhaseSpec(name="verite_plan", kind="code", action=verite_plan),
PhaseSpec(name="constat_build", kind="code", action=constat("build", factory)),
PhaseSpec(name="build", kind="agent", action=builder_action(builder),
retries=args.fix_loops),
PhaseSpec(name="perimetre_build", kind="code",
action=perimetre("build", builder, factory)),
PhaseSpec(name="constat_review", kind="code", action=constat("review", factory)),
PhaseSpec(name="review", kind="agent",
action=agent_action("review", reviewer, review_ask, ReviewEnvelope),
retries=args.retries),
PhaseSpec(name="perimetre_review", kind="code",
action=perimetre("review", reviewer, factory)),
PhaseSpec(name="verdict_review", kind="code", action=verdict_review),
PhaseSpec(name="constat_document", kind="code", action=constat("document", factory)),
PhaseSpec(name="document", kind="agent",
action=agent_action("document", documenter, document_ask,
DocumentEnvelope),
retries=args.retries),
PhaseSpec(name="perimetre_document", kind="code",
action=perimetre("document", documenter, factory)),
PhaseSpec(name="dispose", kind="code", action=dispose),
])
if __name__ == "__main__":
sys.exit(main())
La gate du TP
# 1) le roster v2 se charge et se valide — cinq agents, zero token
uv run --with pyyaml python -m adws.adw_modules.roster
# 2) le jalon du module : le premier SDLC complet de l'usine, en une commande
uv run adws/adw_sdlc.py "Ajoute un bouton pour effacer le texte de l'editeur de Plume"
Attendu : la première commande liste désormais cinq agents, documenter compris. La seconde
déroule dix-huit phases : spec posee, le build (avec, le cas échéant, une reprise gates en echec suivie d’une tentative verte), la review approuvée, puis l’enveloppe du documenter et
SDLC complet : ~40 à 60 centimes, 10 à 15 minutes, ou ~20 à 30 centimes en variante éco
(planner sur le workhorse). Vérifiez les deux artefacts (ls specs/ app_docs/), puis
commitez : votre usine vient de dérouler son premier cycle de développement entier, verte de bout
en bout.