Plus tôt cette année, Andrej Karpathy (@karpathy) a pointé un agent vers son propre code d’entraînement et l’a laissé tourner pendant deux jours. Il a lancé 700 expériences, gardé les 20 qui battaient le benchmark, et a accéléré l’entraînement du modèle de 11 %. Puis il a dit quelque chose de très intéressant : toute métrique que l’on peut évaluer à faible coût peut être confiée à un essaim d’agents.
Le taux de réponse est une métrique que l’on peut évaluer à faible coût. Depuis, j’ai passé du temps à réfléchir à ce à quoi ressemble cette boucle appliquée à l’outbound.
Mon montage :
Codex lit les résultats de la semaine précédente, modifie les fichiers de scoring et de plays que le système outbound utilise, lance un test, et ouvre une pull request. Il propose une modification du playbook avec la preuve et le score associé, puis attend l’approbation humaine. L’envoi et la fusion restent en dehors de la boucle.
J’ai déjà construit la première boucle plusieurs fois : détecter le marché, noter le compte, écrire à partir du signal, vérifier le message, enregistrer le résultat, apprendre de la réponse. Cet article parle de la deuxième boucle, celle qui modifie la première.
Voilà le montage : le GTM comme un code versionné qui s’améliore grâce au marché.

Le dépôt
Commencez par le dossier. La structure compte, car Codex ne peut améliorer que ce qu’il peut lire et modifier.
1codex-self-improving-outbound/2 AGENTS.md3 README.md4 config/5 scoring.yaml6 plays.yaml7 prompts/8 improve_scoring.md9 improve_prompt.md10 pr_summary.md11 memory/12 outcomes.jsonl13 evals/14 fixtures.yaml15 score.py16 scripts/17 append_outcome.py18 run_codex_step.sh19 propose_improvement.py20 open_pr.sh21 weekly_tune.sh22 examples/23 outcomes.sample.jsonl24 weekly-pr.md
Le dépôt est volontairement simple. config/scoring.yaml contient les règles qui décident quels signaux comptent. prompts/ contient les plays qui rédigent les messages. memory/outcomes.jsonl contient ce que le marché a fait. evals/score.py est la porte qui dit si une modification proposée a aidé. AGENTS.md est la loi que Codex lit avant de toucher à quoi que ce soit.
Exécutez la première version hors ligne. Pas de CRM, pas d’enrichissement, pas de système de livraison. La boucle d’amélioration doit se prouver sur des fichiers locaux avant d’approcher une vraie machine outbound.
Étape 1. Écrire la loi en premier
Avant le fichier de scoring, avant les fichiers de prompt, écrivez AGENTS.md. C’est le fichier qui garde l’agent utile et contenu.
1# Règles d’amélioration de l’outbound auto-apprenant23Vous améliorez un système outbound à partir des résultats.45Règles strictes :6- N’envoyez jamais de messages.7- Ne scrapez ni n’enrichissez jamais de vraies personnes.8- Ne fusionnez jamais par vous-même.9- Modifiez uniquement les fichiers de ce dépôt.10- Changez un concept à la fois.11- Citez les résultats de `memory/outcomes.jsonl` pour chaque modification proposée.12- Améliorez `evals/score.py` avant qu’une modification ne devienne une PR.13- Si l’évaluation ne s’améliore pas, annulez votre modification et arrêtez.1415Modifications autorisées :16- `config/scoring.yaml`17- `config/plays.yaml`18- `prompts/*.md`1920Résultat requis :21- fichiers modifiés22- raison de chaque modification23- score avant24- score après25- résumé de la pull request
La loi a un seul objectif : réduire le périmètre. Sans elle, Codex essaiera d’aider en élargissant le champ. Il ajoutera plus de données, touchera plus de fichiers, appellera plus d’outils, ou automatisera une étape qui devrait rester sous contrôle humain. Ici, le travail est plus petit : lire les résultats, proposer une modification de fichier, prouver qu’elle a aidé, puis attendre.
Ce à quoi ressemble un bon résultat. Vous pouvez lire la loi avant d’approuver une PR et savoir exactement ce que Codex était autorisé à faire.
Là où ça casse. La loi devient un document de conformité. Si AGENTS.md a besoin d’une table des matières, c’est qu’il est déjà trop long. Gardez-le opérationnel.
Étape 2. Mettre le jugement dans la configuration
La plupart du jugement outbound réside dans la tête de quelqu’un. Puis l’équipe achète un logiciel et s’attend à ce que le logiciel améliore une décision qu’il ne peut pas voir.
Mettez le jugement dans un fichier.
1signals:2 competitor_comparison:3 weight: 84 reason: "l'acheteur compare les alternatives"5 implementation_page_visit:6 weight: 67 reason: "l'acheteur vérifie si cela peut être installé"8 job_repost:9 weight: 510 reason: "le poste est toujours ouvert et urgent"11 funding_event:12 weight: 513 reason: "le budget ou le mandat a peut-être changé"14 generic_download:15 weight: 116 reason: "intérêt pour le contenu, faible intention d'achat"1718thresholds:19 draft: 620 human_review: 102122negative_signals:23 student_research: -824 vendor_pitch: -625 competitor: -10
Ce fichier commence comme une hypothèse visible. Si un téléchargement générique ne devrait compter pour rien, l’équipe peut pointer la ligne exacte et la modifier. Si une visite de page d’implémentation est un signal plus fort que prévu, Codex peut proposer le diff et montrer les lignes de résultats qui le justifient.
N’enterrez pas cette logique dans une fonction Python. Si la règle est visible, l’équipe peut la réviser, la discuter et l’améliorer sans transformer un jugement commercial en refactoring d’ingénierie.
Ce à quoi ressemble un bon résultat. Le fichier est assez petit pour être discuté. Cinq signaux, c’est une bonne première version.
Là où ça casse. Le fichier de scoring devient un tiroir à bazar. Vingt signaux, six seuils et des règles d’exception pour chaque cas particulier feront surajuster l’amélioreur. Commencez modestement et laissez les résultats vous dire où placer le prochain bouton.
Étape 3. Écrire les résultats comme une mémoire
Le fichier le plus important est memory/outcomes.jsonl.
Une ligne par contact, écrite lorsque le résultat est connu :
1{"date":"2026-07-01","account":"Northwind Finance","signal":"competitor_comparison","play":"migration_note","score":8,"outcome":"reply","reason":"a demandé des notes de migration"}2{"date":"2026-07-01","account":"Bluepeak Studio","signal":"generic_download","play":"resource_followup","score":1,"outcome":"no_reply","reason":"intention uniquement de contenu"}3{"date":"2026-07-02","account":"KiteOps","signal":"implementation_page_visit","play":"implementation_angle","score":6,"outcome":"reply","reason":"a posé des questions sur le calendrier d'implémentation"}4{"date":"2026-07-02","account":"Atlas Recruiting","signal":"job_repost","play":"hiring_angle","score":5,"outcome":"bad_fit","reason":"demande de recherche étudiante"}
Le champ reason est tout l’intérêt. no_reply ne vous apprend presque rien. intention uniquement de contenu indique à la prochaine exécution que ce signal ne mérite peut-être pas un brouillon. bad_fit n’est utile que lorsque la raison explique pourquoi. a posé des questions sur le calendrier d'implémentation est le genre de détail qui peut changer un poids.
Construisez le validateur avant de construire l’amélioreur :
1Construisez scripts/append_outcome.py.23Il accepte :4- date5- account6- signal7- play8- score9- outcome : reply | meeting | no_reply | bad_fit | bounced10- reason1112Il rejette :13- les champs manquants14- les outcomes inconnus15- une raison vide16- les dates dans le futur1718Ajoute les lignes valides à memory/outcomes.jsonl.19Affiche la ligne ajoutée.
C’est là que la capitalisation commence. Un tableau de bord peut vous dire qu’une campagne est en baisse. Un journal de résultats propre peut dire à Codex quel signal, play ou phrase doit changer avant la prochaine exécution.
Ce à quoi ressemble un bon résultat. Après une semaine, un étranger peut lire le fichier et dire quels signaux ont généré des réponses, quels plays ont créé des conversations inadaptées, et quel favori interne le marché a ignoré.
Là où ça casse. L’équipe remplit les résultats le vendredi de mémoire. Les victoires survivent, les raisons de mauvais ajustement s’estompent, et le système apprend à partir de fiction. Écrivez la ligne quand le résultat atterrit.
Étape 4. Construire la porte d’évaluation
Avant que Codex ne modifie quoi que ce soit, il a besoin d’un test qu’il ne peut pas contourner.
Créez evals/fixtures.yaml :
1cases:2 - account: Northwind Finance3 signals: [competitor_comparison, implementation_page_visit]4 expected: human_review5 note: "deux signaux forts sur un seul compte"67 - account: Bluepeak Studio8 signals: [generic_download]9 expected: ignore10 note: "intention uniquement de contenu"1112 - account: KiteOps13 signals: [implementation_page_visit]14 expected: draft15 note: "l'intention d'implémentation devrait franchir le seuil de brouillon"1617 - account: Atlas Recruiting18 signals: [job_repost, student_research]19 expected: ignore20 note: "le marqueur de mauvais ajustement annule le signal"
Ensuite, créez evals/score.py :
1Construisez evals/score.py.23Lisez config/scoring.yaml et evals/fixtures.yaml.45Pour chaque cas :61. Additionnez les poids de chaque signal.72. Ajoutez les pénalités des signaux négatifs.83. Acheminez le compte :9 - score >= thresholds.human_review => human_review10 - score >= thresholds.draft => draft11 - sinon => ignore124. Comparez l'acheminement à l'attendu.1314Affichez chaque prédiction.15Affichez la précision finale sous forme score=0.00 à score=1.00.16Sortie 0 uniquement lorsque la précision est 1.00.
La première porte doit être assez petite pour être comprise et assez tranchante pour détecter une vraie erreur. Lors de mon premier essai, la base a échoué sur un cas :
1Northwind Finance: predicted=human_review expected=human_review2Bluepeak Studio: predicted=ignore expected=ignore3KiteOps: predicted=ignore expected=draft4Atlas Recruiting: predicted=ignore expected=ignore5score=0.75
C’était bien. Le système avait l’intention d’implémentation en dessous du seuil de brouillon, donc il ignorait un compte que le fixture disait mériter un message. Mieux vaut le détecter dans un test qu’après un mois de comptes manqués.
Ce à quoi ressemble un bon résultat. Une commande donne un chiffre, et chaque cas échoué est facile à inspecter.
Là où ça casse. Le fixture ne contient que des victoires évidentes. Alors tout changement imprudent passe. Mettez des cas laids dans la porte : intention faible, mauvais ajustement, pas de réponse, signaux obsolètes, et les comptes que vous auriez aimé que le système saute.
Étape 5. Laisser Codex proposer un changement de scoring
Maintenant Codex peut modifier.
Créez prompts/improve_scoring.md :
1Vous améliorez le système de scoring outbound.23Lisez :4- AGENTS.md5- config/scoring.yaml6- memory/outcomes.jsonl7- evals/fixtures.yaml89Votre travail :101. Trouvez une règle de scoring qui devrait changer.112. La raison doit citer memory/outcomes.jsonl.123. Modifiez uniquement config/scoring.yaml.134. Exécutez python3 evals/score.py.145. Si le score s'améliore, gardez la modification.156. Si le score reste identique ou baisse, annulez votre modification et arrêtez.1617Sortie :18- la ligne exacte modifiée19- les lignes de résultats qui l'ont provoquée20- score avant21- score après22- si la modification doit devenir une PR2324Ne modifiez pas les prompts.25N'ajoutez pas de nouveaux signaux.26Ne touchez pas à la livraison.
Exécutez-le via le wrapper du dépôt :
1scripts/run_codex_step.sh improve_scoring
La première version de mon amélioreur a fait une erreur utile. Il a cherché le signal de réponse le plus propre. competitor_comparison avait le meilleur taux de réponse dans le petit journal de résultats, donc l’amélioreur voulait augmenter ce poids. L’évaluation est restée à 0.75, donc la modification a été rejetée.
C’est exactement pourquoi la porte existe. Un système plus faible aurait accepté l’histoire parce qu’elle semblait raisonnable. Celui-ci a posé une meilleure question : est-ce que la modification corrige l’erreur connue ?
Le deuxième passage a trouvé la plus petite modification qui aidait :
1- implementation_page_visit: 42+ implementation_page_visit: 6
L’évaluation a passé :
1Northwind Finance: predicted=human_review expected=human_review2Bluepeak Studio: predicted=ignore expected=ignore3KiteOps: predicted=draft expected=draft4Atlas Recruiting: predicted=ignore expected=ignore5score=1.00
C’est le moment où la boucle devient utile. Elle a changé une règle, pour une raison, et prouvé la modification par rapport à un fixture.

Ce à quoi ressemble un bon résultat. Le diff proposé est ennuyeux et traçable : une ligne modifiée, une raison basée sur les résultats attachée, une évaluation améliorée.
Là où ça casse. Codex change trois poids et deux prompts à la fois. Maintenant personne ne peut dire quel changement a aidé. Gardez la loi stricte : un concept par proposition.
Étape 6. Améliorer les fichiers de prompt séparément
Le scoring n’est que la moitié du système. Les modèles de message se dégradent aussi.
Une ligne qui fonctionnait le mois dernier commence à sonner familier. Une question qui génère des réponses dans un segment est ignorée dans un autre. Une phrase qui semble percutante en interne est punie par le marché. Traitez l’amélioration des prompts comme une voie séparée pour que Codex ne mélange pas scoring et copy dans la même PR.
Créez config/plays.yaml :
1plays:2 migration_note:3 prompt_file: prompts/plays/migration_note.md4 use_when:5 - competitor_comparison6 banned_lines:7 - "j'ai pensé que cela pourrait être pertinent"8 - "question rapide"910 implementation_angle:11 prompt_file: prompts/plays/implementation_angle.md12 use_when:13 - implementation_page_visit14 banned_lines:15 - "vous jetez un œil à notre solution"16 - "j'aimerais échanger un peu"
Ensuite, créez prompts/improve_prompt.md :
1Vous améliorez un play outbound.23Lisez :4- AGENTS.md5- config/plays.yaml6- memory/outcomes.jsonl7- le fichier de prompt du play choisi89Choisissez un play avec au moins 10 résultats.1011Trouvez :12- les lignes ou structures qui apparaissent dans les résultats positifs13- les lignes ou structures qui apparaissent dans les résultats no_reply ou bad_fit14- toute phrase qui devrait être bannie1516Faites une petite modification dans le prompt de ce play.1718Règles :19- Ne modifiez pas le scoring.20- Ne créez pas un nouveau play.21- N'ajoutez pas un nouveau canal.22- Citez les lignes de résultats.23- Écrivez l'instruction avant et après.2425Exécutez ensuite l'évaluation de copy si elle existe.26S'il n'y a pas d'évaluation de copy, ouvrez la PR en review_required.
Certaines améliorations peuvent être notées automatiquement. D’autres ont encore besoin de goût. S’il n’y a pas d’évaluation de copy, Codex peut proposer la modification du prompt, mais il doit marquer la PR pour révision au lieu de prétendre que la modification est prouvée.
Ce à quoi ressemble un bon résultat. Codex dit : « Cette phrase est apparue dans sept résultats sans réponse, donc je l’ai ajoutée aux banned_lines », ou « les réponses positives citaient le détail d’implémentation dans la première phrase, donc j’ai resserré le play pour exiger cela. »
Là où ça casse. L’amélioreur réécrit toute la voix parce qu’un message a reçu une réponse. Les modifications de prompt devraient être plus petites que votre instinct.
Étape 7. Livrer les modifications sous forme de pull requests
C’est la couche de contrôle. Codex modifie les fichiers, exécute l’évaluation et rédige le résumé de la PR. Un humain révise et fusionne.

Créez prompts/pr_summary.md :
1Rédigez un résumé de pull request pour cette amélioration outbound.23Incluez :41. Ce qui a changé.52. Pourquoi cela a changé, en citant les lignes de résultats.63. Score avant.74. Score après.85. Fichiers modifiés.96. Risque.107. Ce que le relecteur humain doit vérifier.1112Gardez-le court.13N'affirmez pas que la modification est en ligne.
Créez scripts/open_pr.sh :
1#!/usr/bin/env bash2set -euo pipefail34branch="codex/weekly-tune-$(date +%Y-%m-%d)"56git checkout -b "$branch"7git add config prompts evals memory8git commit -m "Codex weekly outbound tune"910body="$(cat outputs/pr-summary.md)"1112python3 scripts/create_pr.py \13 "$branch" \14 "Codex weekly outbound tune" \15 "$body"
La PR devrait se lire comme si un coéquipier l’avait écrite :
1Modification :2- Augmentation de implementation_page_visit de 4 à 6.34Pourquoi :5- KiteOps avait une intention de page d'implémentation et a répondu avec le calendrier d'implémentation.6- Le score précédent acheminait ce compte vers ignore.78Avant :9- score d'évaluation 0.751011Après :12- score d'évaluation 1.001314Vérification du relecteur :15- Assurez-vous que l'intention d'implémentation est suffisamment spécifique.16- Gardez les téléchargements génériques bas.17- Fusionnez uniquement si cela correspond au jugement commercial réel.
Voilà le système de sécurité. Codex fait le travail fastidieux. L’opérateur maintient le standard.
Ce à quoi ressemble un bon résultat. Une PR par semaine, petit diff, raison claire, évaluation réussie.
Là où ça casse. Quelqu’un donne à Codex la permission de fusionner parce que la révision semble être une friction. Cette minute sépare un système qui s’améliore d’un système qui dérive.
Étape 8. Mettre en cadence
Ne faites pas tourner cela après chaque réponse. C’est ainsi qu’un système surajuste à un seul compte bruyant.
Laissez la semaine se dérouler, laissez les résultats s’accumuler, puis ajustez.

Créez scripts/weekly_tune.sh :
1#!/usr/bin/env bash2set -euo pipefail34cd "$(dirname "$0")/.."56python3 evals/score.py || true7scripts/run_codex_step.sh improve_scoring8python3 evals/score.py9scripts/run_codex_step.sh pr_summary > outputs/pr-summary.md10scripts/open_pr.sh
Ensuite, cron :
10 8 * * MON cd ~/codex-self-improving-outbound && scripts/weekly_tune.sh
Si vous utilisez GitHub Actions, gardez la même forme :
1name: weekly-outbound-tune23on:4 schedule:5 - cron: "0 8 * * 1"6 workflow_dispatch:78jobs:9 tune:10 runs-on: ubuntu-latest11 steps:12 - uses: actions/checkout@v413 - uses: actions/setup-python@v514 with:15 python-version: "3.11"16 - run: pip install -r requirements.txt17 - run: python3 evals/score.py || true18 - run: scripts/weekly_tune.sh
Exécutez les deux premiers ajustements à la main. Lisez chaque diff. Observez ce que Codex essaie de changer lorsque l’échantillon est mince. Une fois que les propositions sont ennuyeuses, mettez-les en planning.
Ce à quoi ressemble un bon résultat. Une PR hebdomadaire apparaît avec la preuve, le diff et le résultat de l’évaluation. Vous la fusionnez, modifiez ou fermez.
Là où ça casse. Le job tourne, personne ne révise, et les PR s’accumulent. Un système auto-apprenant a encore une habitude humaine : lire le diff.
La version clone-et-exécute
Le dépôt devrait livrer avec quatre commandes :
1git clone <repo>2cd codex-self-improving-outbound3python3 -m venv .venv4source .venv/bin/activate5pip install -r requirements.txt6python3 evals/score.py7python3 scripts/propose_improvement.py
Première exécution attendue :
1score=0.752changed config/scoring.yaml3implementation_page_visit: 4 -> 64score=1.005open PR for human review
La démo hors ligne prouve les contrats de fichiers. L’exécution Codex prouve la boucle d’édition. Après cela, remplacez les exemples de résultats par les vôtres, renommez les signaux, ajoutez vos plays, et construisez un fixture qui reflète les comptes que vous auriez aimé que le système achemine différemment.
Ne commencez pas par câbler la livraison. Commencez par prouver la boucle d’amélioration.
La version complète : max
Ce dépôt est la couche manuelle. Il fonctionne à partir de fichiers, de signaux publics et de votre plan Codex. Il enseigne la forme parce que chaque règle est exposée.
yourmax.ai est le même système avec les coutures cachées.
Au lieu d’un dépôt que vous bricolez vous-même, max est l’agent que vous utilisez directement. Il détecte les mouvements du marché, décide qui mérite d’être contacté et pourquoi maintenant, rédige le démarchage par email et LinkedIn pour votre approbation, et continue de s’améliorer à partir des résultats.
Le dépôt montre la couche d’auto-ajustement que la plupart des équipes ne construisent jamais : les résultats deviennent des propositions de changement de règles, les propositions passent par une porte, et la fusion humaine décide ce qui devient actif. max prend cette même logique opérationnelle et l’exécute comme un système géré.
Si vous voulez le dépôt complet, vous pouvez me le dire et je vous l’enverrai.





