Automatiser le développement Laravel avec des agents IA : créer, tester, corriger, répéter
Utilisez Laravel Boost et l’OpenCode CLI pour exécuter un cycle de compilation, de test et de correction grâce à des tâches en file d’attente, un orchestrateur, des limites de tentative et une révision humaine avant que quoi que ce soit ne soit déployé.
Sources: Build → Test → Fix → Repeat: Automating Laravel Development with AI Agents
La course infinie du copier-coller
Comptez combien de fois une journée typique avec un assistant de codage IA se déroule ainsi : on écrit une demande, on copie le résultat dans l’éditeur, on exécute php artisan test, on constate une erreur. On copie le texte d’erreur, on le colle à nouveau dans l’IA en demandant « veuillez corriger ceci », puis on recommence depuis l’étape de copie.
Certains développeurs répètent ce cycle une douzaine de fois par jour. On qualifie cela de développement assisté par l’IA, mais celui qui travaille le plus dans ce cycle n’est pas l’IA : c’est la personne qui transmet les messages entre le modèle et le terminal, en décidant quand exécuter les tests, en lisant les erreurs, en jugeant s’une autre correction est nécessaire, et en copiant tout d’un bout à l’autre. L’IA reste inactive jusqu’à ce que la prochaine instruction arrive.
Une meilleure organisation élimine le messager humain. Un petit système peut gérer seul le cycle de création, de test, de correction et de répétition jusqu’à ce que le travail soit véritablement terminé, et une personne n’intervient que pour la révision finale. Les outils utilisés ici sont Laravel Boost et OpenCode CLI, deux outils adaptés à l’écosystème Laravel actuel. Les explications sont volontairement lentes afin que toute personne n’ayant jamais utilisé aucun de ces outils puisse suivre.
Vocabulaire
Quelques termes reviennent fréquemment :
- Agent : une intelligence artificielle à qui ont été confiées une tâche définie ainsi qu’un ensemble d’outils définis, comparable à un employé ayant un rôle clairement défini par écrit.
- MCP (Model Context Protocol) : un connecteur permettant à l’intelligence artificielle de poser des questions directement à votre système, que ce soit la base de données ou les journaux, ce qui l’empêche de deviner en se basant sur ce que vous avez tapé par hasard.
La question a évolué au-delà de « l’IA peut-elle écrire du code ? »
Tout ceux qui ont déjà utilisé Claude Code, Cursor ou un outil similaire avec Laravel savent que l’IA peut générer des migrations, des modèles, des politiques et des tests. Le problème restant concerne le flux de travail manuel associé :
Developer → AI → Developer → Test → Developer → AI → Developer → Test → ...
Chaque flèche représente une personne qui transfère manuellement le contexte d’une étape à l’autre, décidant qu’il est temps de tester ou qu’une erreur doit être renvoyée à l’IA.
Comparez avec une version automatisée :
Developer → Orchestrator → Build → Test → (failed? → Fix → Test again) → Review → Done
L’écart semble petit, mais il entraîne de nombreux changements. Il existe désormais un orchestrateur, une couche qui connaît l’ordre des tâches, le moment de réessayer et celui d’arrêter, sans qu’il soit nécessaire que quelqu’un surveille constamment la situation tous les trente secondes.
Laravel évolue également dans cette direction. Laravel Boost a été conçu pour que les agents de codage basés sur l’IA reçoivent un contexte réel concernant une application Laravel, au lieu de deviner en se basant sur des modèles génériques.
Les deux outils principaux
Laravel Boost : un intermédiaire entre l’IA et l’application
Même si une intelligence artificielle est forte en général pour Laravel, elle peut rester complètement incapable de gérer votre projet spécifique. Elle ne sait pas quelle version de Laravel vous utilisez, comment votre base de données est réellement structurée, si vous employez Livewire ou Inertia, ni quelles conventions de nommage votre équipe a adoptées.
Boost est un paquet Composer qui permet à l’IA de prendre connaissance de la situation réelle du projet. Il fonctionne via MCP, ce qui permet à l’IA de consulter directement l’application : examiner le schéma de la base de données, visualiser les routes existantes, voir l’erreur la plus récente, exécuter des commandes Artisan et rechercher des informations dans les documents Laravel correspondant aux versions réellement installées du paquet.
L’installation nécessite deux commandes :
composer require laravel/boost --dev
php artisan boost:install
Le deuxième est interactif. Il demande quel IDE ou outil d’IA vous utilisez et installe les ressources correspondantes.
Il convient de préciser clairement que Boost traduit le contexte sans prendre de décisions. Il ne dira pas « exécutez les tests maintenant » ni « lancez l’agent de correction en cas d’échec ». Sa seule fonction est d’empêcher les agents d’ignorer l’état réel de l’application.
OpenCode : où l’agent effectue le travail
Si Boost traduit le contexte, OpenCode constitue l’atelier : une interface en ligne de commande conçue spécifiquement pour le codage par intelligence artificielle. Contrairement à un chatbot ordinaire, il vous permet de définir plusieurs agents ayant des responsabilités différentes, par exemple un qui écrit du code et un autre qui lit les résultats des tests, chacun disposant de ses propres instructions et limites.
OpenCode peut également se connecter à des serveurs MCP, y compris Boost. Une fois connecté, tout ce que Boost propose devient disponible pour l’agent qui s’exécute à l’intérieur d’OpenCode.
Pratique : connecter Boost à OpenCode
Commencez par essayer les choses manuellement dans le terminal, avant d’automatiser quoi que ce soit.
1. Diriger OpenCode vers Boost
Avec Boost installé, OpenCode doit savoir où se trouve son serveur MCP. Ces informations sont stockées dans un fichier opencode.json situé à la racine du projet :
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"laravel-boost": {
"type": "local",
"command": ["php", "artisan", "boost:mcp"],
"enabled": true
}
}
}
La commande exacte peut varier en fonction de la version de Boost, il faut donc suivre celle générée par php artisan boost:install plutôt que de la copier aveuglément. C’est l’un des éléments les plus susceptibles de changer entre les versions.
Vérifiez que OpenCode peut voir le serveur :
opencode mcp list
Si laravel-boost est indiqué comme connecté, les agents ont accès à tout ce que Boost met à disposition.
2. Créer des agents avec des responsabilités distinctes
Au lieu d’un seul agent pour toutes les tâches, divisez les fonctions. Créez un fichier .opencode/agent/builder.md dans le projet :
---
description: Build Agent for new features
mode: primary
tools:
write: true
edit: true
---
You are the Build Agent.
Use Laravel Boost tools to understand the application structure
before writing any code. Never assume the database structure -
always check the schema first. Write tests for every new behavior.
At the end of your work, return ONLY JSON in this format:
{"status": "success", "files_changed": [...], "summary": "..."}
La ligne tools.write: true permet à cet agent de modifier des fichiers. Un agent ultérieur recevra write: false, ce qui le renda uniquement en lecture et incapable de modifier quoi que ce soit, et la raison importante sera expliquée bientôt.
Énumérez les agents enregistrés :
opencode agent list
3. Exécutez-le manuellement en premier
opencode run --agent builder "Add a task assignment feature to TaskFlow according to the requirements in TASK.md"
Quelques notes sur cette commande :
--agentpermet de choisir quel agent s’exécute, que ce soit le constructeur, le testeur ou tout autre agent défini.- L’ajout de
--format jsongénère des données brutes au lieu d’un affichage destiné aux utilisateurs. C’est ce que l’appel PHP utilisera par la suite. --sessionet--continuepermettent à l’agent de se souvenir des conversations précédentes au lieu de recommencer à zéro à chaque fois.
Exécutez-la manuellement plusieurs fois pour vous familiariser avec son fonctionnement avant de l’automatiser.
Un cas concret : l’affectation de tâches dans TaskFlow
Un exemple concret aide à bien comprendre le concept. Imaginez une application simple de gestion de tâches appelée TaskFlow qui a besoin de la fonctionnalité suivante : les utilisateurs peuvent assigner une tâche à un autre utilisateur, mais uniquement au sein d’un projet auquel ils ont tous deux accès. Décomposé en exigences :
1. A task has an assignee column (belongs to a User).
2. A user can only assign tasks within a project they're a member of.
3. A user must not assign or access tasks from other projects.
4. All of the above must be covered by automated tests.
C’est une exigence courante, ce qui en fait une bonne démonstration. Des cas comme celui-ci nécessitent souvent deux ou trois tentatives avant que la question de savoir qui peut accéder à quoi, techniquement appelée autorisation, ne soit résolue de manière définitive. C’est précisément là que le boucle automatisée trouve son utilité.
Pourquoi le processus a besoin d’un journal
Lorsque le travail s’exécute en arrière-plan, personne ne peut compter sur sa mémoire pour se rappeler « quel était déjà cet erreur ? ». Tout doit être enregistré, et cela signifie ici l’utilisation d’une table de base de données servant de journal des progrès :
Schema::create('ai_tasks', function (Blueprint $table) {
$table->id();
$table->string('type');
$table->string('status')->default('pending');
$table->text('prompt');
$table->json('result')->nullable();
$table->unsignedTinyInteger('iteration')->default(0);
$table->timestamps();
});
Le modèle :
class AiTask extends Model
{
protected $fillable = [
'type', 'status', 'prompt', 'result', 'iteration',
];
protected function casts(): array
{
return ['result' => 'array'];
}
}
La colonne status a une importance capitale. Elle passe par ces étapes :
pending → building → testing → (fixing → testing)* → reviewing → completed / failed
Pourquoi pas simplement un drapeau done ? Parce que l’orchestrateur doit savoir à quelle étape se trouve le travail, et non seulement s’il est terminé. Le suivi des étapes permet également de reprendre une exécution en cas de panne d’un travailleur au milieu du processus, et rend visible l’avancement en temps réel.
Intégrer OpenCode dans PHP
Beaucoup de gens sautent cette étape : il ne faut pas disperser les commandes OpenCode dans de nombreux fichiers. Si l’outil est remplacé plus tard, des dizaines d’endroits ne devraient pas nécessiter de modifications. Créez un point d’entrée unique, une interface :
interface AgentRunner
{
public function run(string $agent, string $prompt): array;
}
Puis la mise en œuvre réelle, qui appelle OpenCode depuis PHP :
class OpenCodeAgentRunner implements AgentRunner
{
public function run(string $agent, string $prompt): array
{
$result = Process::timeout(600)->run([
'opencode', 'run',
'--agent', $agent,
'--format', 'json',
$prompt,
]);
if (! $result->successful()) {
return [
'status' => 'error',
'error' => $result->errorOutput(),
];
}
return json_decode($result->output(), true) ?? [
'status' => 'error',
'error' => 'Agent output was not valid JSON',
];
}
}
Lier cela à un fournisseur de services :
$this->app->bind(AgentRunner::class, OpenCodeAgentRunner::class);
Avec ce modèle, chaque tâche ne communique qu’avec AgentRunner et se moque du fait que ce soit OpenCode ou un autre élément qui se trouve derrière.
Une tâche par étape
Pour ceux qui découvrent Laravel Jobs : une tâche est une opération qui s’exécute en arrière-plan via une file d’attente, de sorte que personne n’a à attendre qu’elle soit terminée.
La tâche de compilation
class BuildFeature implements ShouldQueue
{
public function __construct(protected AiTask $task) {}
public function handle(AgentRunner $agent): void
{
$result = $agent->run(
agent: 'builder',
prompt: $this->task->prompt,
);
$this->task->update([
'status' => 'testing',
'result' => $result,
]);
RunTests::dispatch($this->task);
}
}
Ici, l’agent de compilation s’exécute, son résultat est enregistré, et le flux passe directement au job suivant (RunTests) sans aucune action manuelle.
Le job de test
Ce job est délibérément conçu de manière simple. Il n’a pas besoin de comprendre le code. Il exécute la suite de tests et lit les résultats.
class RunTests implements ShouldQueue
{
public function __construct(protected AiTask $task) {}
public function handle(): void
{
$result = Process::timeout(300)->run('php artisan test --compact');
if ($result->successful()) {
$this->task->update(['status' => 'reviewing']);
ReviewFeature::dispatch($this->task);
return;
}
$this->task->update(['status' => 'failed']);
AnalyzeFailure::dispatch($this->task, $result->output());
}
}
Le processus est simple :
Test
├── passed → move to Review
└── failed → move to Analyze
Ne transmettez pas d’erreurs brutes à l’agent
Cette étape est la plus facile à sauter, mais elle a l’impact le plus important sur la qualité. Indiquer simplement à l’agent qu’un test a échoué et lui demander de le corriger ne suffit pas. Il doit tout reprendre à zéro, relire chaque fichier et deviner ce qui a mal tourné.
Structurez d’abord l’échec :
{
"status": "failed",
"failed_tests": [
{
"name": "user_cannot_assign_task_from_other_project",
"error": "Expected response status code [403] but received 200."
}
]
}
Puis transmettez-le à un agent dont la seule fonction est le diagnostic, appelons-le l’Agent de débogage, en lui donnant des instructions qui restreignent strictement ce qu’il peut faire :
You are the Debug Agent.
Analyze the failed tests below. DO NOT modify any files.
Inspect: relevant models, policies, migrations, and tests.
Return JSON with:
- root_cause
- affected_files
- recommended_fix
- risk_of_regression
Pourquoi séparer le diagnostic de la correction ? Pour créer un écart entre la compréhension du problème et l’exécution de la réparation. Si l’agent se voit simplement demander de corriger les choses, la trace montrant pourquoi un fichier donné a changé disparaît, et l’examen final devient beaucoup plus difficile à vérifier.
Pour TaskFlow, la sortie de l’agent de débogage ressemblerait probablement à ceci :
{
"status": "analyzed",
"root_cause": "TaskPolicy does not verify project membership before allowing assignment",
"affected_files": ["app/Policies/TaskPolicy.php"],
"recommended_fix": "Add a project membership check inside the assign() method",
"next_action": "fix"
}
Ce serait une découverte crédible, car il s’agit de l’un des bugs d’autorisation les plus courants : une politique qui ne vérifie que si l’utilisateur est connecté, et non s’il fait réellement partie du projet.
Résolvez le problème, puis testez à nouveau ; ne vous contentez jamais de résoudre sans tester.
class FixFeature implements ShouldQueue
{
public function __construct(protected AiTask $task, protected array $analysis) {}
public function handle(AgentRunner $agent): void
{
$result = $agent->run(
agent: 'fixer',
prompt: json_encode($this->analysis),
);
$this->task->increment('iteration');
$this->task->update([
'status' => 'testing',
'result' => $result,
]);
RunTests::dispatch($this->task);
}
}
Remarquez que, après la correction, l’état revient à testing au lieu de passer directement à completed. Le principe est clair : un agent n’a pas le droit de se déclarer terminé simplement parce qu’un fichier a changé. La seule preuve valable de correction est un ensemble de tests qui a réellement été exécuté à nouveau, et non l’affirmation de l’agent lui-même.
L’orchestrateur en tant que contrôleur de trafic
Au début, il semble tentant d’enchaîner les tâches en ligne droite :
Bus::chain([
new BuildFeature($task),
new RunTests($task),
new ReviewFeature($task),
])->dispatch();
Mais ce n’est qu’une ligne. Dès qu’apparaît une branche, comme le retour pour corriger un échec de test, une chaîne linéaire s’avère insuffisante. Ce dont on a besoin ressemble à un contrôleur de trafic qui, chaque fois qu’il est sollicité, vérifie l’état actuel et choisit la prochaine action en conséquence :
class AgentOrchestrator
{
public function next(AiTask $task): void
{
match ($task->status) {
'pending' => BuildFeature::dispatch($task),
'testing' => RunTests::dispatch($task),
'failed' => AnalyzeFailure::dispatch($task),
'fixing' => RunTests::dispatch($task),
'reviewing' => ReviewFeature::dispatch($task),
'completed', 'stopped' => null,
default => throw new LogicException("Unknown status: {$task->status}"),
};
}
}
Le changement de mentalité est significatif. L’orchestrateur ne se demande plus quel est le prochain travail à effectuer. Il se demande quelle transition est valide compte tenu de l’état actuel. Cela facilite le débogage, et une nouvelle étape, comme une revue de sécurité, peut être ajoutée sans perturber le flux global.
Limitez les tentatives de réessai
En l’absence de limite, un scénario comme celui-ci peut se produire :
Fix → Test fails → Fix → Test fails → Fix → Test fails → ...
Cela gaspille du temps, et chaque appel à l’IA a également un coût. La solution réside dans une limite maximale :
class RunTests implements ShouldQueue
{
protected const MAX_ITERATIONS = 5;
public function handle(): void
{
if ($this->task->iteration >= self::MAX_ITERATIONS) {
$this->task->update(['status' => 'needs_human_review']);
return;
}
// ... run tests as usual
}
}
Si cinq tentatives de correction ne suffisent pas, le système s’arrête et marque la tâche pour être traitée par un humain, au lieu de continuer indéfiniment. Cela ne signifie pas nécessairement que l’IA est mauvaise dans sa tâche. Parfois, les exigences sont ambiguës, ou la décision nécessite réellement une intervention humaine.
Les tests réussis ne signifient pas pour autant que la fonctionnalité est terminée
Il est facile d’oublier cela une fois que le système qui fonctionne seul semble impressionnant. Supposons que l’agent écrive quelque chose comme :
public function assign(User $user, Task $task): bool
{
return $user->isAdmin();
}
Tous les tests pourraient passer, car ils ne couvrent que les scénarios imaginés à l’époque. La exigence réelle stipulait que l’utilisateur devait être membre du projet, et non qu’il devait être administrateur. La logique est erronée même si tous les tests restent positifs.
C’est pourquoi ce flux est dangereux :
Build → Tests pass → Deploy immediately
Celle qui est plus sûre ressemble à ceci :
Build → Test → (if failed: Debug → Fix → Test again) → Human review → Deploy
La phase d’examen doit toujours impliquer une personne, au minimum pour tout ce qui concerne les droits d’accès. Pour des modifications sensibles sur le plan de la sécurité comme celle-ci, il est judicieux d’exiger un examen humain avant fusionnement, aussi performant que puisse paraître le processus automatisé.
Agents parallèles et conflits de fichiers
L’idée d’exécuter simultanément un agent de sécurité et un agent de recherche présente un piège. Si deux agents peuvent écrire dans le même fichier en même temps, une condition de course apparaît, ce qui signifie que les deux processus écrasent le travail de l’autre.
Agent A → writes Task.php
Agent B → reads Task.php (nearly at the same time)
Agent A → writes Task.php again
La règle de base consiste à répartir les responsabilités en fonction de ceux qui peuvent écrire et de ceux qui ne peuvent que lire :
Research Agent → read-only
Review Agent → read-only
Security Agent → read-only
Build Agent → write access
Fix Agent → write access
Cela explique également la ligne tools.write: true dans le fichier builder.md précédent. Elle détermine quels agents peuvent modifier des fichiers et quels agents ne peuvent que les consulter.
Résistez au super-agent
La plus grande tentation une fois que cela fonctionne est d’avoir un seul agent chargé de planifier, de construire, de tester, de déboguer, de corriger, de réviser et de déployer. Cela a tendance à avoir des effets contraires aux escomptés. Plus un agent assume de responsabilités, plus d’informations il doit traiter, plus d’outils il possède, et plus il est susceptible de prendre une mauvaise décision à un moment donné. Diviser les tâches en fonction des responsabilités est plus efficace :
Planner → designs the approach
Builder → writes the new feature's code
Tester → runs the test suite
Debugger → diagnoses failures (read-only)
Fixer → executes the fix
Reviewer → final quality check (read-only)
C’est le même principe que celui qui consiste à éviter une seule classe géante gérant tout dans une application. Évitez également de créer un seul agent qui fait tout.
Une liste de contrôle minimale avant utilisation réelle
Pour des expérimentations dans un projet réel, ces éléments doivent être en place dès le premier jour et non ajoutés ultérieurement :
- un plafond strict sur les tentatives de réessai, afin que la boucle de correction ne puisse pas fonctionner indéfiniment
- un délai limité pour chaque commande exécutée par l’agent
- des restrictions d’accès aux fichiers adaptées au rôle de l’agent, lecture seule ou écriture
- des tests qui doivent exister et réussir avant que le statut ne puisse passer en phase d’examen
- une approbation humaine avant que quoi que ce soit ne soit mis en production
- un enregistrement de chaque décision prise par l’agent, et non seulement du résultat final
- une protection pour les fichiers sensibles tels que
.env, afin qu’un agent ne les lise jamais par accident
L’élément final est souvent sous-estimé. Une fois qu’un agent peut exécuter des commandes sur le système, grâce aux appels Process::run() mentionnés ci-dessus, il peut en fait effectuer presque n’importe quelle action sur ce serveur. Les restrictions d’accès sont une exigence dès le départ, et non une option.
Rôle réel de Boost
Voici le tableau complet :
Developer (us)
│
▼
Orchestrator (traffic controller, running on Laravel Queue)
│
├── Build Agent
├── Test Agent
├── Debug Agent
└── Fix Agent
│
▼
OpenCode (where the agent does its work)
│
▼
Laravel Boost (context translator, via MCP)
│
▼
Our Laravel application
Boost ne décide jamais de lancer les tests ni d’activer l’agent de correction en cas d’échec. Il fait une seule chose, et c’est essentielle : s’assurer que chaque agent du projet comprend bien l’état réel de l’application et ne se base pas sur des schémas généraux. Les questions concernant le fait de savoir qui agit, à quel moment, et quand tout s’arrête restent du ressort de l’orchestrateur basé sur Laravel Queue.
La question à se poser maintenant
Il y a quelques années, la question intéressante était de savoir si l’IA pouvait écrire du code Laravel correct. Elle le peut clairement, et ses capacités s’améliorent constamment. La question plus pertinente aujourd’hui est de savoir si l’IA peut mettre en place son propre cycle de feedback comprenant la compilation, les tests, les échecs, l’analyse des raisons de ces échecs, la correction et de nouveaux tests, jusqu’à ce que le travail soit véritablement terminé, sans avoir besoin d’un humain pour intervenir manuellement à chaque étape.
Le scénario TaskFlow suggère que la réponse est oui, à condition que trois éléments ne soient pas oubliés. Il faut un enregistrement clair des progrès, une limite stricte aux tentatives de réessai, et les tests réussis doivent être considérés comme un signal, et non comme la ligne d’arrivée.
Boost aide les agents à comprendre le contexte de l’application. OpenCode leur fournit un endroit où travailler. Laravel Queue devient l’élément central qui organise tout. De plus, les développeurs peuvent monter en niveau, passant d’opérateurs manuels à chaque étape à des personnes chargées de vérifier le résultat final.
Vibe coding accélère l’écriture du code. Une telle orchestration permet au processus de codage de se dérouler automatiquement, et c’est là que les expériences de codage par IA deviennent intéressantes.