Fondations Chapitre 1 / 42

Le levier sur le prompt & l'équation Ingénieurs + Code + Agents

Ce qu'une usine logicielle vous apporte — des résultats d'agents reproductibles, mesurables et moins chers — et la première pièce que vous posez : le plan.

Vendredi, vous confiez une fonctionnalité à un agent de code. Vingt minutes plus tard, l’implémentation est propre et les tests passent. Lundi, la même demande produit une autre architecture, deux tests rouges et un transcript de quarante pages à relire.

Ce livre transforme le vendredi en régime normal. L’outil s’appelle une usine logicielle : un petit système en Python et en YAML qui entoure vos agents et vous donne quatre choses qu’aucun prompt ne produit seul. Des résultats reproductibles. Un « fini » vérifiable par une commande. Une trace lisible de chaque exécution. Un coût connu, réductible phase par phase. Vous la construisez pièce par pièce, un chapitre par jour, dans un repo compagnon nommé plume-factory. Aujourd’hui vous posez la première pièce : le plan.

Le levier sur le prompt

L’idée en une phrase

Une usine logicielle donne du levier sur votre prompt : la même demande, formulée pareil, produit un résultat meilleur, reproductible et mesurable, parce que l’installation autour du prompt fait le travail que vous refaisiez à la main à chaque session. La première pièce de cette installation, PLAN.md, vit du côté déterministe de l’usine. C’est un document que vous possédez, pas une sortie d’agent.

Points clés

  • Tout le monde obtient un bon résultat une fois. Presque personne ne l’obtient deux fois. C’est le constat qui fonde ce livre. Le problème n’est pas la qualité du modèle, c’est que personne ne possède la boucle.
  • Le remède : sortir le pilotage du prompt et le mettre dans du Python. Le séquencement, les reprises et l’acceptation appartiennent au code. L’agent travaille à l’intérieur d’une phase nommée, avec une entrée et une sortie bornées.
  • Le levier se mesure : nombre de phases nommées, taux de runs acceptés, coût et durée par run. « Ça marche mieux » n’est pas une mesure.
  • Le levier est cumulatif. Une pièce posée sert tous les runs suivants. Un prompt mieux écrit sert un run, puis disparaît avec sa session.

Exemple concret

Prenez une demande que vous ferez réellement plus loin dans ce livre : ajouter une fonctionnalité à Plume, la petite application d’écriture qui naîtra au chapitre 2 et servira de banc d’essai à l’usine.

  • Sans usine : une session, un agent, aucune coupure. Le contexte est renvoyé à chaque tour et gonfle sans limite. Sur un travail de cette taille, comptez de l’ordre du million de tokens cumulés en entrée. « Fini » veut dire « l’agent s’est tu ». Trois exécutions donnent trois implémentations différentes, et la seule trace est un transcript à relire en entier.
  • Avec l’usine : quatre phases nommées, plan → build → test → review, chacune avec son entrée et sa sortie bornées. Le volume de tokens baisse d’environ un tiers à une moitié, la durée tombe à quelques minutes, et le verdict devient binaire. La suite de tests passe, ou l’échec repart vers l’agent pour correction.
  • Le coût dépend surtout du modèle choisi. Le même run coûte quelques dizaines de centimes sur un modèle intermédiaire et plusieurs dollars sur un modèle frontier, soit un facteur 10 environ. Choisir le modèle phase par phase est le sujet du module 4. Retenez ici que l’usine rend ce choix possible, parce qu’elle sait de quelle phase on parle.

Les étages du levier

ÉtageCe que vous investissezCe que vous obtenezReproductible ?
Prompt nudeux minutes d’écritureun résultat, parfois bonnon
Deux agents chaînésun script shellun enchaînement, aucun verdictnon
Phases + enveloppes~200 lignes de Pythonune trace lisible, un « fini » nomméen partie
Phases + gates + rosterl’usine de ce livreun verdict binaire, un coût connuoui

Commande — le même travail, deux étages du levier

Les deux harnais au programme de ce livre, pi et Claude Code, acceptent un tour non interactif. C’est l’étage 0 : déjà utile, mais sans mémoire d’un run à l’autre.

# Étage 0 — le prompt nu, lancé à la main. Le résultat dépend entièrement
# de cette session-là ; demain, il faudra tout redemander.
pi -p "ajoute l'export Markdown a Plume et fais passer les tests"
claude -p "ajoute l'export Markdown a Plume et fais passer les tests"

# Étage 3 — le même travail confié à l'usine. Le harnais n'est plus invoqué
# à la main : un script Python le lance phase par phase, puis juge le résultat.
uv run adws/adw_plan_build.py "ajoute l'export Markdown a Plume"

Sous le capot, l’usine construit un appel bien plus riche : sortie machine, session reprenable, outils autorisés. Vous écrirez cet appel vous-même au chapitre 7, pour les deux harnais.

Piège courant : « il suffit d’un meilleur prompt » est inexact. Un prompt mieux écrit améliore un run et meurt avec lui. Il ne crée aucune phase nommée, aucun critère d’acceptation, aucune trace interrogeable. La bonne question n’est pas « comment mieux demander ? » mais « qu’est-ce qui, dans ce run, mérite d’être écrit une fois pour tous les runs suivants ? ».


L’équation Ingénieurs + Code + Agents

L’idée en une phrase

Trois acteurs fabriquent le logiciel — vous, le code et les agents — et l’usine les tient séparés à dessein. Chacun devient un couloir de la trace, avec son coût et sa compétence propres. Le levier vient de solliciter le bon acteur au bon moment, pas du nombre d’agents. La frontière entre code et agent passe exactement là où le jugement commence.

Points clés

  • Trois sortes de phases, trois couloirs : engineer (vous), agent (un appel borné, prompt en entrée et réponse structurée en sortie) et code (un pas déterministe qui tient seul).
  • Le code ne coûte rien. Il s’exécute instantanément, se modifie en une seconde, et vous le possédez, ce qui n’est vrai d’aucun modèle loué au token.
  • La règle de partage : si l’invocation est déjà connue, elle s’écrit en code. bun test n’est pas un jugement, ruff check non plus. Ce qui demande de lire et de décider reste à l’agent.
  • Un agent qui redécouvre votre lanceur de tests brûle une fenêtre de contexte pour apprendre ce qu’un subprocess sait déjà, et vous facture ce privilège à chaque run.
  • Une phase code n’est jamais enfouie dans une phase agent. La trace doit montrer exactement quand le code a tourné et quand un agent travaillait.

Exemple concret

Regardez la phase de test aux deux extrêmes.

  • En phase agent : l’agent ouvre package.json, cherche le lanceur, l’exécute, lit des centaines de lignes de sortie et rédige un verdict en prose. Comptez quelques dizaines de milliers de tokens et une bonne demi-minute par run, pour une opinion qu’il vous faudra encore interpréter.
  • En phase code : un subprocess.run sur bun test. Zéro token, zéro dollar, quelques secondes, et un code retour qui ne se discute pas.
  • Sur mille runs, l’écart se chiffre en dizaines de dollars et en heures. Le vrai gain est ailleurs : vous avez remplacé une opinion par un verdict binaire, donc reproductible.

Qui répond à quelle question

Question poséeActeurPourquoi luiCoût marginal
Que fabrique-t-on ?vousvous seul portez l’intentionvotre temps
Ce plan est-il implémentable ?agentil faut lire et déciderdes tokens
La suite passe-t-elle ?codel’invocation est connuezéro
Ce code répond-il à la demande ?agentc’est un jugementdes tokens
Faut-il rejouer la phase ?coderègle écrite, pas arbitragezéro
Le commit part-il ?codel’agent propose le message, le code écritzéro

Script — la frontière, en cinq lignes

Un extrait du workflow vers lequel tout le module 3 converge. La phase test_1 est du code. La phase fix_1 est un agent qui reçoit l’échec par la même porte qu’un rapport d’agent. Vous écrirez ce mécanisme vous-même aux chapitres 8 et 9.

# La suite est une commande connue : la lancer ne demande aucun jugement,
# donc c'est une phase code — zéro token, un code retour.
with run.phase(PhaseParams(name="test_1", kind="code", owner="quality",
                           description="Run the suite")) as ph:
    test = quality.run_tests(run)

# L'échec repart vers le builder sous forme structurée : la session reste
# vivante, rien ne redémarre à froid, la boucle de correction est bornée.
with run.phase(PhaseParams(name="fix_1", kind="agent", owner="builder", retries=1,
                           description="Repair what the suite reported")) as ph:
    previous = ph.call(AgentCall(output_type=BuildOutput, prompt=prompt,
                                 previous=quality.as_envelope(test, "tests")))

Un détail contre-intuitif mérite d’être noté dès maintenant. Une suite rouge ne fait pas échouer sa phase : le runner a fait son travail, c’est le code du projet qui a échoué. La suite rouge fait échouer le run, et seulement à la fin, une fois la boucle de correction épuisée.

Piège courant : croire que « plus d’agents = plus de levier ». Chaîner cinq agents là où trois phases code suffisaient multiplie le coût, la latence et la variance sans rien ajouter au verdict. Le bon réflexe est l’inverse. Chaque fois que vous envisagez une phase agent, cherchez d’abord la commande qui répondrait à la même question. Si elle existe, elle gagne : elle est gratuite, instantanée, et rend toujours le même verdict.


Fil rouge — la pièce posée aujourd’hui

La pièce du jour est le plan lui-même : PLAN.md et son garde-fou .gitignore, déposés dans un dépôt plume-factory fraîchement initialisé. Aucune machine n’existe encore. Ce qui existe, c’est le contrat que les vingt-six pièces suivantes vont honorer : l’arbre cible, les zones de l’usine, les jalons. La couture passe ici au plus net. PLAN.md est un document que vous possédez et qu’aucun agent n’écrira à votre place, parce que l’intention ne se délègue pas. La gate du jour est la première de la série et la seule à ne rien consommer : zéro token, zéro dollar, moins d’une seconde. Ce que cette pièce vous fait économiser ne se mesure pas en dollars mais en dérive. Sans plan écrit, chaque chapitre réinventerait un chemin, et vingt-sept pièces qui ne s’emboîtent pas ne font pas une usine.


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.

Mise en place

# à côté de vos autres dépôts — une seule fois
mkdir plume-factory && cd plume-factory
git init -b main

Pièce — PLAN.md

L’état cible de l’usine, en un seul fichier que vous possédez. C’est la référence que vous rouvrirez à chaque chapitre pour savoir quelle pièce arrive et sur quoi elle s’appuie. Toute déviation ultérieure, outil disparu ou chemin devenu invalide, se règle en modifiant ce fichier, jamais en laissant deux chapitres diverger en silence.

# plume-factory — le plan de l'usine

Ce dépôt contient une **usine logicielle agentique** et le payload qu'elle travaille.

Loi fondamentale : **l'agent propose, le code dispose.** Le code déterministe possède le
séquencement, les reprises et l'acceptation. Un agent n'est qu'un nœud borné à l'intérieur d'une
phase nommée. Le contexte ne traverse une frontière que dans une enveloppe JSON typée. Les gates
définissent « fini » ; un échec de gate revient à l'agent par la même porte qu'un rapport d'agent.

## Arbre cible

plume-factory/
├── PLAN.md                          # ce fichier — ch. 1
├── justfile                         # recettes racine — ch. 5
├── just/
│   ├── adws.just                    # lancer les workflows
│   ├── obs.just                     # observer les runs
│   └── sandbox/                     # lifecycle, keys, orch — module 6
├── apps/plume/                      # le payload : app d'écriture Bun + TS — ch. 2
├── specs/                           # les specs : la vôtre (ch. 2), puis celles d'adw_plan (ch. 11+)
├── adws/
│   ├── hello_factory.py             # l'embryon du runner — ch. 3
│   ├── adw_prompt.py                # une phase agent, une enveloppe — ch. 8
│   ├── adw_scout.py / adw_plan.py   # reconnaissance et plan — ch. 11
│   ├── adw_build.py                 # implémentation + gates — ch. 12
│   ├── adw_sdlc.py                  # la chaîne complète — ch. 13
│   ├── adw_bench.py                 # mini-benchs par roster — ch. 17
│   ├── adw_modules/
│   │   ├── harness.py               # LE port harnais + adaptateurs pi / claude_code — ch. 7
│   │   ├── runner.py                # phases, séquencement, reprises — ch. 8
│   │   ├── envelopes.py             # enveloppes JSON typées — ch. 9
│   │   ├── roster.py                # chargement de factory.config.yaml — ch. 10
│   │   ├── permissions.py           # tools, writes, fichiers protégés — ch. 10
│   │   ├── gates.py                 # lint / tests / typecheck — ch. 12-13
│   │   └── tracer.py                # événements de phase vers SQLite — ch. 18-20
│   ├── adw_config/                  # factory, frontier, open-weights, eco — ch. 10, 15-17
│   ├── prompts/                     # system.md + user.md par agent
│   └── adw_data/                    # runtime : sessions, enveloppes, factory.db — jamais commité
├── app_docs/                        # sorties du documenter — ch. 13
├── .claude/skills/factory/          # l'usine empaquetée en skill — ch. 26
├── .env.sample                      # OPENROUTER_API_KEY ; provisioning côté hôte seulement
└── .gitignore                       # posé ch. 1

## Zones de l'usine

| Module | Zone | Pièces principales |
|---|---|---|
| 1 — Fondations | le plan et la baseline | PLAN.md, apps/plume, hello_factory.py |
| 2 — Poste de pilotage | l'outillage | uv, justfile, herdr, harness.py |
| 3 — Le squelette ADW | le coeur | runner, envelopes, roster, gates, adw_sdlc |
| 4 — Model stack | les moteurs | 4 rosters, adw_bench |
| 5 — Observabilité | la salle de contrôle | tracer.py, obs.just, export OTel |
| 6 — Sandboxes & scale | le hors-site | just/sandbox, clés provisionnées, best-of-N |
| 7 — Distribution | l'emballage | skill factory, /install, capstone |

## Jalons

- [x] ch. 1 — dépôt initialisé, PLAN.md et .gitignore posés
- [ ] ch. 2 — Plume générée en un shot, `bun test` vert : la baseline « sans usine »
- [ ] ch. 3 — hello_factory.py : un subprocess, une sortie JSON validée
- [ ] ch. 7 — plus aucun appel direct au harnais hors adw_modules/harness.py
- [ ] ch. 13 — premier adw_sdlc.py vert de bout en bout sur Plume
- [ ] ch. 17 — quatre rosters interchangeables, adw_bench les compare
- [ ] ch. 20 — chaque run laisse une trace SQLite exploitable
- [ ] ch. 25 — un best-of-N de 3 à 5 rosters lancé, moissonné, comparé
- [ ] ch. 27 — l'usine s'installe dans un repo vierge via son skill

## Règles de continuité

1. Une pièce posée ne se casse pas. Toute évolution redonne le fichier entier et l'annonce.
2. `adws/adw_data/` et `.env` ne sont jamais commités.
3. Chaque pièce a sa gate : la commande qui prouve qu'elle fonctionne, coût et durée à l'appui.
4. Le payload Plume reste petit (5 à 8 fichiers) : les runs restent lisibles et bon marché.
5. Les ADW nomment des agents, jamais des modèles. Le roster décide du modèle.

Pièce — .gitignore

Le garde-fou, posé avant la première ligne de code de l’usine. Le runtime et les secrets ne doivent jamais pouvoir partir dans un commit, y compris par accident lors d’un git add -A déclenché par un agent. C’est un pas déterministe qui protège toutes les pièces à venir.

# secrets — jamais commités
.env
.env.*
!.env.sample

# runtime de l'usine : sessions, enveloppes, traces, base SQLite
adws/adw_data/

# dépendances et artefacts du payload
node_modules/
dist/
build/

# Python
__pycache__/
*.pyc
.venv/

# OS et éditeurs
.DS_Store
Thumbs.db
.idea/
.vscode/

La gate du TP

Le bloc ci-dessous est du shell : collez-le tel quel dans votre terminal, à la racine de plume-factory. Après le commit, il enchaîne trois vérifications. La boucle for interroge git (git check-ignore) sur trois chemins sensibles pour prouver que le .gitignore les couvre, avant même qu’ils existent. test -s vérifie que PLAN.md existe et n’est pas vide. Enfin git status --porcelain liste une ligne par fichier non commité, wc -l les compte, et zéro signifie que l’arbre est propre.

git add PLAN.md .gitignore
git commit -m "chore: le plan de l'usine et ses garde-fous"

# 1. rien de sensible ni de runtime ne doit pouvoir partir dans un commit
for p in .env adws/adw_data/ node_modules/; do
  git check-ignore -q "$p" && echo "ignore : $p" || echo "EXPOSE : $p"
done

# 2. le plan existe et n'est pas vide
test -s PLAN.md && echo "plan   : PLAN.md pose, $(wc -l < PLAN.md) lignes"

# 3. l'arbre est propre : tout ce qui devait être commité l'est
echo "sale   : $(git status --porcelain | wc -l) fichier(s)"

Attendu : trois lignes ignore, une ligne EXPOSE signalerait un chemin que le .gitignore ne couvre pas, puis le décompte de lignes de PLAN.md, et sale : 0. Coût : zéro token, 0,00 $, moins d’une seconde. C’est la seule gate du livre qui ne consomme rien, et exactement le genre de vérification qu’on ne confie jamais à un agent.


Quiz — teste tes connaissances
Fondations 7 questions Objectif : 5/7 minimum
0/7
bonnes reponses
Objectif non atteint (minimum 5/7 requis).
Remonte relire la fiche memo en pretant attention aux points manques, puis cliquer sur « Recommencer » pour retenter.