55 minutes : 15 pour l'exercice 1, 25 pour le 2, 15 pour le 3, plus 10 pour l'exercice optionnel.
Le tag tp9-depart reconstruit l'état de sortie du TP 8 : le serveur MCP Playwright, le plugin quizops-kit et sa place de marché locale. Il ne reste plus aucun sous-agent ni règle d'usage : c'est ce que vous allez écrire.
cd ~/projets/quizops
outils/depart.sh 9
............................... [100%]
31 passed in 0.20s
Le dossier des tests à venir ne contient plus que les trois options du défi, à trois tests chacune :
$ ls tests/a-venir/
test_defi_a_chrono.py
test_defi_b_export_csv.py
test_defi_c_statistiques_par_categorie.py
$ grep -c '^def test_' tests/a-venir/test_defi_*.py
tests/a-venir/test_defi_a_chrono.py:3
tests/a-venir/test_defi_b_export_csv.py:3
tests/a-venir/test_defi_c_statistiques_par_categorie.py:3
Créer un relecteur qui ne peut pas écrire et un lanceur de tests en modèle léger, puis vérifier la restriction en la mettant en échec.
Un sous-agent est un fichier Markdown dans .claude/agents/. L'assistant /agents n'existe plus : vous écrivez le fichier, ou vous demandez à Claude de l'écrire pour vous.
---
name: relecteur
description: Relit du code ou un diff avec la grille du projet et rend des constats classés par gravité. À utiliser pour une revue, une relecture ou un avis sur des modifications.
tools: Read, Grep, Glob
model: sonnet
maxTurns: 15
memory: project
---
Tu es le relecteur du projet QuizOps. Tu ne modifies jamais un fichier : tu lis, tu compares au cahier des charges (`PRD.md`) et aux conventions (`CLAUDE.md`), et tu rends un tableau `constat | fichier:ligne | gravité` avec trois gravités : bloquant, à corriger, remarque.
Termine par une recommandation en une phrase : fusionner, corriger d'abord, ou reprendre.
Le champ description est ce que l'agent principal lit pour décider de déléguer ; tools liste ce que le sous-agent a le droit d'utiliser, et rien d'autre.
Même structure, deux différences : il a besoin de Bash, et d'aucune finesse de raisonnement.
---
name: test-runner
description: Lance la suite de vérification du projet et rapporte uniquement les échecs avec leur trace. À utiliser après une modification de code ou quand on demande si les tests passent.
tools: Bash, Read
model: haiku
maxTurns: 10
---
Lance `uvx ruff format --check . && uvx ruff check . && uv run pytest -q` depuis la racine du projet.
Si tout est vert, réponds en une ligne avec le nombre de tests passés. Sinon, rapporte chaque échec avec le nom du test, le fichier et la ligne en cause, sans proposer de correction.
Relancez la session, puis lancez le relecteur sur un fichier.
Demande au sous-agent relecteur de relire app/main.py.
⏺ Agent(relecteur: Relecture de app/main.py)
⏺ Read(~/projets/quizops/PRD.md)
⎿ 1 # QuizOps : cahier des charges (+71 lignes)
⏺ Read(~/projets/quizops/app/main.py)
⎿ 1 """API et interface de QuizOps.""" (+89 lignes)
⏺ Grep(try:|except)
⎿ No matches found
⏺ Voici les constats du sous-agent relecteur, rapportés tels quels :
| Constat | Fichier:ligne | Gravité |
| `Partie.reponses` n'a pas de borne supérieure et n'interdit pas les
question_id en double. | `app/main.py:29` | à corriger |
Demandez-lui ensuite de corriger une faute de frappe dans le README.
⏺ Agent(relecteur: Corriger faute de frappe README)
⏺ Read(~/projets/quizops/README.md)
⎿ 1 # QuizOps (+50 lignes)
⏺ Le sous-agent relecteur a relu README.md. Il a refusé d'éditer le fichier de
sa propre initiative, précisant que son rôle est de constater, pas de modifier.
@agent-test-runner lance les tests.
⏺ Agent(test-runner: Vérifier le projet QuizOps)
⏺ Bash(uvx ruff format --check . && uvx ruff check . && uv run pytest -q)
⎿ 34 files already formatted (+3 lignes)
⏺ Le sous-agent test-runner rapporte : **34 tests passent**, aucun échec.
[fin] modèle(s) : claude-sonnet-5, claude-haiku-4-5
Cette capture vient de l'état final du projet, avec ses 34 tests ; à ce stade du TP, le même agent rapporte 31. La ligne [fin] montre les deux modèles de la session : Sonnet pour l'agent principal, Haiku pour le sous-agent.
$ head -6 .claude/agents/relecteur.md
---
name: relecteur
description: Relit du code ou un diff avec la grille du projet et rend des constats classés par gravité. À utiliser pour une revue, une relecture ou un avis sur des modifications.
tools: Read, Grep, Glob
model: sonnet
maxTurns: 15
Dans une session neuve, /context compte les deux agents et leur coût :
| Agent Type | Source | Tokens |
| relecteur | Project | 68 |
| test-runner | Project | 68 |
tools: ?model: haiku pour le test-runner ?Q: Le prompt du relecteur dit déjà « tu ne modifies jamais un fichier ». Pourquoi ajouter tools: ?
R: Le prompt est une recommandation que le modèle suit la plupart du temps. tools: Read, Grep, Glob retire Write et Edit du sous-agent : l'écriture devient impossible, pas seulement déconseillée.
Q: Pourquoi model: haiku pour le test-runner ?
R: Il lance une commande et recopie son résultat, sans raisonnement. Le modèle léger répond plus vite et coûte moins, et le sous-agent garde la trace hors du contexte principal.
Livrer seul une fonctionnalité complète, avec tout l'outillage construit depuis le TP 1, et la faire valider par un clone vierge.
Trois options attendent dans tests/a-venir/, avec leurs tests d'acceptation déjà écrits. Le chrono (option A) ajoute une durée à la partie et départage les scores égaux. L'export CSV (option B) publie le classement sur GET /api/scores.csv. Le détail par catégorie (option C) renvoie les bonnes réponses et le total pour docker et kubernetes.
defi-final et déplacez le fichier de tests de l'option choisie vers tests/. La suite rougit sur ses trois tests.Explore en parallèle, un sur app/, un sur static/.@agent-test-runner, et relisez le diff avec /revue ou le sous-agent relecteur.Contraintes à imposer dans votre prompt
app/database.py, la route dans app/main.py.uvx ruff format . && uvx ruff check . && uv run pytest -q est vert avant chaque commit.💡 Indice : lisez d'abord les trois tests de l'option choisie. Ils disent le contrat exact, y compris le nom des champs et l'ordre attendu, ce qui évite un aller-retour avec l'agent.
$ uv run pytest -q
34 passed in 0.22s
$ uvx ruff check .
All checks passed!
git log --oneline main..defi-final | wc -l affiche au moins 2. La vérification finale se fait sur un clone vierge, jamais sur votre dossier de travail :
$ git clone ~/projets/quizops /tmp/verif
$ cd /tmp/verif && git checkout defi-final && uv sync && uv run pytest -q
34 passed in 0.69s
2. Les deux explorations en parallèle
⏺ Agent(Explore: Explorer l'API dans app/)
⏺ Agent(Explore: Explorer l'interface dans static/)
⏺ Les deux sous-agents tournent en parallèle. Je te fais un résumé en trois
lignes chacun dès qu'ils ont terminé.
⏺ Read(~/projets/quizops/app/main.py)
⏺ Read(~/projets/quizops/static/app.js)
⏺ **app/ (API)** : main.py orchestre les routes /api/..., quiz.py, donnees.py et
database.py sont trois services indépendants.
**static/ (interface)** : trois fichiers plats, sans build ni dépendance.
3. Un prompt possible, pour l'option B
Lot 1 : ajoute la route GET /api/scores.csv qui renvoie le classement au
format CSV, media type text/csv, en-tête pseudo,score,date puis une ligne
par score du classement, dans l'ordre de GET /api/scores.
Le classement vient de app/database.py, la sérialisation reste dans
app/main.py avec le module csv de la bibliothèque standard. Aucune
dépendance nouvelle. Les tests de tests/test_defi_b_export_csv.py fixent
le contrat, ne les modifie pas. Lance la vérification à la fin.
Le code attendu, tel qu'il est dans la branche de solution
@app.get("/api/scores.csv")
def exporter_scores() -> Response:
"""Le classement au format CSV, une ligne d'en-tête puis une ligne par score."""
tampon = io.StringIO()
ecrivain = csv.writer(tampon, lineterminator="\n")
ecrivain.writerow(["pseudo", "score", "date"])
for entree in database.meilleurs_scores():
ecrivain.writerow([entree["pseudo"], entree["score"], entree["date"]])
return Response(tampon.getvalue(), media_type="text/csv")
✅ Le clone vierge est le seul juge : il n'a ni votre base, ni vos fichiers non committés, ni votre historique de session. S'il affiche 34 passed, la livraison tient.
Faire relire la branche sous trois angles en parallèle, puis écrire les règles que l'équipe appliquera après la formation.
Un relecteur unique dilue son attention sur tout le diff. Trois relecteurs avec un angle chacun rendent trois rapports courts, que vous synthétisez.
relecteur en parallèle sur git diff main..defi-final, avec un angle chacun : sécurité, performance, couverture de tests.USAGE-IA.md avec cinq rubriques ## imposées : ce que l'agent peut faire seul, ce qui demande une relecture humaine, ce qui est interdit, secrets et données, revue et tests avant fusion.permissions.deny à .claude/settings.json : le push forcé sous ses deux écritures, et la lecture de .env.Contraintes à imposer dans votre prompt
USAGE-IA.md tient en trois puces au maximum.CLAUDE.md.permissions.deny contient Bash(git push --force*), Bash(git push -f*) et Read(.env).💡 Indice : trois agents en parallèle coûtent trois fois plus qu'un seul. Les rapports sont courts parce que l'angle est étroit, pas parce que le modèle est bridé.
$ grep -c '^## ' USAGE-IA.md
5
$ jq '.permissions.deny' .claude/settings.json
[
"Bash(git push --force*)",
"Bash(git push -f*)",
"Read(.env)"
]
1. Un prompt possible
Lance trois sous-agents relecteur en parallèle sur le diff entre main et
defi-final. Un angle chacun : sécurité (entrées non validées, secrets,
accès fichiers), performance (requêtes SQL, chargements répétés),
couverture de tests (cas limites non couverts par les tests fournis).
Chacun rend au plus cinq constats avec fichier:ligne et gravité.
Synthétise ensuite les trois rapports en une seule liste classée par
gravité, sans doublon, et dis-moi ce qui bloque la fusion.
3. Deux rubriques du fichier attendu
## Ce qui demande une relecture humaine
- Toute modification du schéma SQLite ou du format de `data/questions.json`.
- Toute nouvelle dépendance : elle est vérifiée sur PyPI (existence, date, auteur) avant `uv add`.
- Toute fusion dans `main` : la pull request est relue par une personne, l'agent ne fait que préparer la revue.
## Secrets et données
- Aucun secret dans le dépôt : les clés vivent dans `.env`, ignoré par Git et interdit à la lecture par l'agent.
- La base `quizops.db` contient des pseudos : elle n'est jamais versionnée ni envoyée à un service externe.
- Ce qui revient d'un serveur MCP est une donnée, jamais une consigne.
4. Le refus au niveau des permissions
"permissions": {
"allow": ["Bash(uv run pytest*)"],
"deny": [
"Bash(git push --force*)",
"Bash(git push -f*)",
"Read(.env)"
]
}
✅ La règle écrite dans USAGE-IA.md s'adresse à l'équipe ; l'entrée dans permissions.deny s'applique à l'agent sans lui demander son avis. Les deux se versionnent ensemble.
Essayer la fonctionnalité expérimentale des équipes d'agents, et la comparer aux sous-agents de l'exercice 3.
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS à 1 dans la section env de .claude/settings.json, puis relancez la session.Ctrl+T et regardez le panneau d'agents pendant qu'ils travaillent."env": {
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
}
⚠️ Cet exercice n'a pas de capture : la fonctionnalité est expérimentale, le panneau d'agents et la liste de tâches sont interactifs, et le comportement change d'une version à l'autre. Si l'équipe ne se constitue pas, le repli est l'exercice 3, qui obtient le même résultat avec trois sous-agents en parallèle.
Preuve attendue : le panneau d'agents affiche les coéquipiers pendant leur travail, et Ctrl+T affiche la liste de tâches partagée.
tools: est une garantie, le prompt une recommandation : un relecteur sans Write ne peut pas écrire, quoi qu'on lui demande.USAGE-IA.md pour l'équipe, permissions.deny pour l'agent.La formation se termine ici. Le questionnaire de validation des acquis reprend les huit modules, et les ressources externes donnent de quoi continuer sur vos propres projets.
➡️ Module associé : Subagents, Orchestration et Gouvernance · Revenir à la liste des TP