Automatizando el desarrollo de Laravel con agentes de IA: crear, probar, corregir y repetir
Utilice Laravel Boost y la OpenCode CLI para ejecutar un ciclo de compilación, pruebas y correcciones con tareas en cola, un orquestador, límites de intentos y revisión humana antes de que cualquier cosa sea lanzada.
Fuentes: Build → Test → Fix → Repeat: Automating Laravel Development with AI Agents
La cinta de correr del copiar y pegar
Cuenta cuántas veces se desarrolla así un día típico con un asistente de programación basado en IA. Escribe una instrucción. Copia el resultado al editor. Ejecuta php artisan test. Se produce un fallo. Copia el texto del error, pégalo de nuevo en la IA, di “por favor, arregla esto” y vuelve a empezar desde el paso de copiado.
Algunos desarrolladores repiten ese ciclo una docena de veces al día. Se le llama desarrollo asistido por IA, pero quien más trabaja en ese ciclo no es la IA. Es la persona que transfiere mensajes entre el modelo y la terminal: decide cuándo deben ejecutarse las pruebas, lee los errores, juzga si se necesita otra corrección y pega todo de un lado a otro. La IA permanece inactiva hasta que llega la siguiente instrucción.
Un arreglo mejor elimina al mensajero humano. Un sistema pequeño puede llevar a cabo por sí solo el ciclo de creación, prueba, corrección y repetición hasta que el trabajo esté realmente terminado, y una persona solo interviene para la revisión final. Las herramientas utilizadas aquí son Laravel Boost y OpenCode CLI, dos herramientas adecuadas para el ecosistema de Laravel actual. Las explicaciones se dan de forma lenta a propósito, para que quien nunca haya utilizado ninguna de ellas pueda seguir el proceso.
Vocabulario
Algunos términos aparecen repetidamente:
- Agente: una IA a la que se le ha asignado una tarea definida y un conjunto definido de herramientas, similar a un empleado con un rol establecido por escrito.
- MCP (Model Context Protocol): un conector a través del cual la IA puede hacer preguntas directamente a su sistema, ya sea a la base de datos o a los registros, evitando así adivinar a partir de lo que casualmente se haya escrito.
La pregunta ya no es “¿puede la IA escribir código?”
Cualquiera que haya utilizado Claude Code, Cursor o una herramienta similar con Laravel ya sabe que la IA puede generar migraciones, modelos, políticas y pruebas. El problema restante es el flujo de trabajo manual asociado a ello:
Developer → AI → Developer → Test → Developer → AI → Developer → Test → ...
Cada flecha representa a una persona que transfiere manualmente el contexto de un paso al siguiente, decidiendo que ha llegado el momento de probar o que se necesita devolver un error a la IA.
Compare con una versión automatizada:
Developer → Orchestrator → Build → Test → (failed? → Fix → Test again) → Review → Done
La brecha parece pequeña, pero marca una gran diferencia. Ahora existe un orquestador, una capa que conoce el orden de las tareas, cuándo repetirlas y cuándo detenerse, sin necesidad de que alguien supervise cada treinta segundos.
Laravel también ha ido por este camino. Laravel Boost fue creado para que los agentes de programación basados en IA reciban un contexto real sobre una aplicación Laravel en lugar de tener que adivinar a partir de patrones genéricos.
Las dos herramientas principales
Laravel Boost: un traductor entre la IA y la aplicación
Una inteligencia artificial puede ser muy competente en Laravel en general y, aun así, quedarse completamente sin información sobre tu proyecto específico. No sabe qué versión de Laravel utilizas, cómo está realmente estructurada tu base de datos, si usas Livewire o Inertia, ni qué convenciones de nomenclatura ha acordado tu equipo.
Boost es un paquete de Composer que permite a la IA conocer el estado real del proyecto. Funciona a través de MCP, por lo que la IA puede consultar directamente la aplicación: inspeccionar el esquema de la base de datos, ver las rutas existentes, observar el error más reciente, ejecutar comandos de Artisan y buscar información en las documentaciones de Laravel que corresponden a las versiones del paquete realmente instaladas.
La instalación requiere dos comandos:
composer require laravel/boost --dev
php artisan boost:install
El segundo es interactivo. Pregunta qué IDE o herramienta de IA utiliza y instala los recursos correspondientes.
Vale la pena aclarar que Boost traduce el contexto, pero no toma decisiones. No dirá “ejecuta las pruebas ahora” ni “inicia el agente de corrección en caso de error”. Su única función es evitar que los agentes ignoren el estado real de la aplicación.
OpenCode: donde realiza su trabajo el agente
Mientras que Boost traduce el contexto, OpenCode funciona como un taller: una CLI diseñada específicamente para la programación con IA. A diferencia de un chatbot común, permite definir varios agentes con responsabilidades distintas; por ejemplo, uno que escribe código y otro que lee los resultados de las pruebas, cada uno con sus propias instrucciones y límites.
OpenCode también puede conectarse a servidores MCP, incluido Boost. Una vez conectado, todo lo que ofrece Boost queda disponible para el agente que se ejecute dentro de OpenCode.
En práctica: conectar Boost con OpenCode
Comience probando las cosas directamente en la terminal antes de automatizar nada.
1. Dirigir OpenCode hacia Boost
Con Boost instalado, OpenCode debe saber dónde se encuentra su servidor MCP. Esa información se guarda en un archivo opencode.json en la raíz del proyecto:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"laravel-boost": {
"type": "local",
"command": ["php", "artisan", "boost:mcp"],
"enabled": true
}
}
}
La orden exacta puede variar según la versión de Boost, por lo que debe seguir lo que genere php artisan boost:install en lugar de copiarlo ciegamente. Es una de las partes que con mayor probabilidad cambian entre las distintas versiones.
Confirme que OpenCode puede ver el servidor:
opencode mcp list
Si laravel-boost aparece como conectado, los agentes tienen acceso a todo lo que ofrece Boost.
2. Crear agentes con responsabilidades distintas
En lugar de tener un solo agente para todas las tareas, divida las funciones. Cree un archivo .opencode/agent/builder.md en el proyecto:
---
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 línea tools.write: true permite que este agente modifique archivos. Un agente posterior tendrá write: false, lo que lo hará de solo lectura e incapaz de tocar nada; la razón importante se explica en breve.
Enumere los agentes registrados:
opencode agent list
3. Ejútelo manualmente primero
opencode run --agent builder "Add a task assignment feature to TaskFlow according to the requirements in TASK.md"
Algunas notas sobre esa orden:
--agentselecciona qué agente se ejecutará, ya sea el generador, el probador o cualquier otro definido.- Al agregar
--format json, la salida será datos en bruto en lugar de información destinada a ser mostrada a personas. Eso es lo que se utilizará posteriormente en una llamada a PHP. --sessiony--continuepermiten que el agente recuerde la conversación anterior en lugar de comenzar desde cero cada vez.
Ejecútela manualmente unas cuantas veces para familiarizarte antes de automatizarla.
Un caso concreto: asignación de tareas en TaskFlow
Un ejemplo práctico ayuda a mantener las cosas claras. Imagina una aplicación sencilla de gestión de tareas llamada TaskFlow que necesita la siguiente funcionalidad: los usuarios pueden asignar una tarea a otro usuario, pero exclusivamente dentro de un proyecto al que ambos tengan acceso. Desglosado en requisitos:
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.
Es un requisito común, lo que lo convierte en una buena demostración. Los casos como este suelen requerir dos o tres intentos antes de que la cuestión de quién puede acceder a qué, técnicamente llamada autorización, quede resuelta de manera definitiva. Es precisamente allí donde un bucle automatizado cobra sentido.
Por qué el proceso necesita un registro
Una vez que el trabajo se ejecuta en segundo plano, nadie puede confiar en su memoria para recordar “¿cuál era ese error otra vez?”. Todo debe ser registrado, y en este caso eso significa una tabla de base de datos que funcione como un registro de progreso:
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();
});
El modelo:
class AiTask extends Model
{
protected $fillable = [
'type', 'status', 'prompt', 'result', 'iteration',
];
protected function casts(): array
{
return ['result' => 'array'];
}
}
La columna status es la que determina el peso. Pasa por estas etapas:
pending → building → testing → (fixing → testing)* → reviewing → completed / failed
¿Por qué no usar simplemente una bandera done? Porque el orquestador debe saber en qué etapa se encuentra el trabajo, no solo si está terminado. El seguimiento de etapas también permite reanudar una ejecución después de que un trabajador falle durante su ejecución y hace visible el progreso en tiempo real.
Vinculando OpenCode con PHP
La gente a menudo omite este paso: no distribuya las órdenes de OpenCode en muchos archivos. Si más adelante se cambia la herramienta, no debería ser necesario editar docenas de lugares. Cree un único punto de entrada, una interfaz:
interface AgentRunner
{
public function run(string $agent, string $prompt): array;
}
Luego está la implementación real, que llama a OpenCode desde 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',
];
}
}
Átelo en un proveedor de servicios:
$this->app->bind(AgentRunner::class, OpenCodeAgentRunner::class);
Con este patrón, cada tarea solo se comunica con AgentRunner y no le importa si detrás de él está OpenCode o algo else.
Una tarea por etapa
Para quienes son nuevos en las tareas de Laravel: una tarea es una operación que se ejecuta en segundo plano a través de una cola, por lo que nadie tiene que esperar a que termine.
La tarea de compilación
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);
}
}
Aquí se ejecuta el Build Agent, su resultado se guarda y el flujo pasa directamente al siguiente trabajo (RunTests) sin necesidad de hacer clic manualmente.
El trabajo de pruebas
Este trabajo se diseña intencionadamente de forma sencilla. No necesita comprender el código; simplemente ejecuta el conjunto de pruebas y lee los resultados.
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());
}
}
El proceso es sencillo:
Test
├── passed → move to Review
└── failed → move to Analyze
No transmita errores sin procesar al agente
Esta lección es la más fácil de omitir, pero tiene el mayor impacto en la calidad. Decirle al agente únicamente que una prueba falló y pedirle que la arregle no es suficiente. El agente debe comenzar desde cero, leer cada archivo nuevamente e intentar adivinar qué salió mal.
Primero, estructure la información sobre el fallo:
{
"status": "failed",
"failed_tests": [
{
"name": "user_cannot_assign_task_from_other_project",
"error": "Expected response status code [403] but received 200."
}
]
}
Luego entréguelo a un agente cuya única función es la diagnosis; llámelo Agente de Depuración, y dígale instrucciones que limiten estrictamente lo que puede hacer:
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
¿Por qué separar el diagnóstico de la solución? Para crear una brecha entre comprender el problema y ejecutar la reparación. Si simplemente se le dice al agente que arregle las cosas, desaparece el rastro que muestra por qué cambió un archivo determinado, y resulta mucho más difícil verificar la revisión final.
En TaskFlow, la salida del Agente de Depuración probablemente se asemejaría a:
{
"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"
}
Eso sería un hallazgo creíble, ya que se trata de uno de los errores de autorización más comunes: una política que solo verifica si el usuario está conectado, pero no si realmente pertenece al proyecto.
Arregla y luego vuelve a probar; nunca basta con arreglarlo y listo.
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);
}
}
Tenga en cuenta que, tras la corrección, el estado vuelve a testing en lugar de pasar a completed. El principio es firme: un agente no tiene autoridad para declararse terminado solo porque un archivo cambió. La única prueba válida de corrección es un conjunto de pruebas que se haya ejecutado realmente de nuevo, no la afirmación del propio agente.
El orquestador como controlador de tráfico
Al principio, enlazar las tareas en línea recta parece tentador:
Bus::chain([
new BuildFeature($task),
new RunTests($task),
new ReviewFeature($task),
])->dispatch();
Pero eso es solo una línea. Tan pronto como aparece un ramal, como el de regresar para corregir algo cuando una prueba falla, una cadena lineal resulta insuficiente. Lo que se necesita es algo similar a un controlador de tráfico que, cada vez que se le solicita, verifica el estado actual y elige el siguiente paso a seguir:
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}"),
};
}
}
El cambio en la forma de pensar es significativo. El orquestador ya no pregunta cuál es el siguiente trabajo; en su lugar, pregunta qué transición es válida teniendo en cuenta el estado actual. Eso facilita la depuración, y se puede añadir una nueva etapa como una revisión de seguridad sin interrumpir el flujo.
Límite para las reintentos
Sin un límite, puede darse un escenario como este:
Fix → Test fails → Fix → Test fails → Fix → Test fails → ...
Eso desperdicia tiempo, y cada llamada a la IA también conlleva un costo. La solución es establecer un tope máximo:
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 cinco intentos de corrección no son suficientes, el sistema se detiene y marca la tarea para que la resuelva un humano, en lugar de seguir intentándolo indefinidamente. Eso no significa necesariamente que la IA sea deficiente en su tarea; a veces los requisitos son ambiguos, o realmente se necesita la intervención de una persona para tomar la decisión.
Las pruebas positivas no equivalen a una función terminada
Es fácil olvidarlo una vez que el sistema que funciona por sí solo parece emocionante. Supongamos que el agente escribe algo como:
public function assign(User $user, Task $task): bool
{
return $user->isAdmin();
}
Podría ser que todas las pruebas den resultado positivo, ya que estas solo abarcan los escenarios que alguien imaginó en ese momento. El requisito real indicaba que el usuario debía ser miembro del proyecto, no que tuviera que ser administrador. La lógica es incorrecta aunque todas las pruebas sigan mostrando resultado positivo.
Por eso este flujo es inseguro:
Build → Tests pass → Deploy immediately
La versión más segura se ve así:
Build → Test → (if failed: Debug → Fix → Test again) → Human review → Deploy
La etapa de revisión aún debe involucrar a una persona, al menos para todo lo relacionado con quién puede acceder a qué. Para cambios sensibles desde el punto de vista de la seguridad como este, es razonable requerir una revisión humana antes de fusionarlos, por más limpio que parezca el proceso automatizado.
Agentes paralelos y conflictos de archivos
La idea de ejecutar un Agente de Seguridad y un Agente de Investigación simultáneamente conlleva un problema. Si dos agentes pueden escribir en el mismo archivo más o menos al mismo tiempo, surge una condición de carrera, lo que significa que los dos procesos sobrescriben el trabajo del otro.
Agent A → writes Task.php
Agent B → reads Task.php (nearly at the same time)
Agent A → writes Task.php again
La regla rectora es dividir las responsabilidades según quién puede escribir y quién solo puede leer:
Research Agent → read-only
Review Agent → read-only
Security Agent → read-only
Build Agent → write access
Fix Agent → write access
Eso también explica la línea tools.write: true del archivo builder.md anterior. Controla qué agentes pueden modificar archivos y cuáles solo pueden consultarlos.
Resista al superagente
La mayor tentación una vez que esto funcione es contar con un único agente encargado de planificar, crear, probar, depurar, corregir, revisar e implementar. Esto tiende a tener efectos negativos. Cuanta más responsabilidad asuma un agente, más información debe procesar, más herramientas maneja y mayor es la probabilidad de tomar una decisión errónea en algún momento. Es mejor dividir las tareas según las responsabilidades:
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)
Es el mismo principio que evitar tener una clase gigantesca que se encargue de todo en una aplicación. Tampoco se debe crear un agente que haga todo.
Una lista de verificación mínima antes del uso real
Para experimentos en un proyecto real, estos elementos deben estar presentes desde el primer día y no añadirse posteriormente:
- un límite estricto en las reintentos, para que el bucle de corrección no funcione indefinidamente
- un límite de tiempo para cada comando que ejecuta un agente
- límites de acceso a archivos acordes al rol del agente, solo lectura o escritura
- pruebas que deben existir y superarse antes de que el estado pueda pasar a revisión
- aprobación humana antes de que nada llegue a producción
- registro de cada decisión que toma el agente, no solo del resultado final
- protección para archivos confidenciales como
.env, para que un agente nunca los lea por error
El último elemento suele ser subestimado. Una vez que un agente puede ejecutar comandos en el sistema, a través de las llamadas Process::run() mencionadas anteriormente, puede ejecutar prácticamente cualquier cosa en ese servidor. Las restricciones de acceso son un requisito desde el primer día, no una opción.
El papel real de Boost
Aquí está la imagen completa:
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 nunca decide ejecutar las pruebas ni activar el agente corrector en caso de fallo. Solo hace una cosa, algo importante: asegurarse de que cada agente del proyecto comprenda el estado real de la aplicación y no suponga basándose en patrones genéricos. Las cuestiones sobre quién actúa, en qué momento y cuándo todo se detiene quedan en manos del orquestador desarrollado con Laravel Queue.
La pregunta que debemos hacernos ahora
Hace unos años, la pregunta interesante era si la IA podía escribir código correcto en Laravel. Claramente puede hacerlo, y sigue mejorando. La pregunta más relevante hoy es si la IA puede gestionar su propio ciclo de retroalimentación de compilación, pruebas, fallos, análisis de las causas, correcciones y nuevas pruebas hasta completar realmente la tarea, sin necesidad de que un humano actúe como intermediario en cada paso.
El escenario de TaskFlow sugiere que la respuesta es sí, siempre y cuando no se olviden tres cosas: el progreso necesita un registro claro, las reintentos deben tener un límite estricto, y las pruebas exitosas deberían considerarse como una señal, no como la meta final.
Boost ayuda a los agentes a comprender el contexto de la aplicación. OpenCode les proporciona un lugar donde trabajar. Laravel Queue se convierte en el eje central que ordena todo. Y los desarrolladores pueden ascender de nivel, pasando de ser operadores manuales en cada paso a revisores del resultado final.
Vibe coding acelera la escritura de código. Una orquestación como esta permite que el proceso de programación funcione por sí mismo, y ahí es donde los experimentos con codificación por IA se vuelven interesantes.
Lecturas relacionadas
- Mapeando el ecosistema de IA de Laravel: Prism, LarAgent, Neuron AI y Boost — De simples llamadas a LLM a agentes completos en Laravel: qué hacen Prism, LarAgent, Neuron AI, el SDK de IA y Boost, con un ejemplo de RAG para preguntas sobre políticas de recursos humanos.
- Desarrollo Dirigido por Especificaciones con Agentes de Codificación AI: Una Guía Práctica — Cómo mantener a los agentes de codificación AI alineados con las especificaciones: el flujo de trabajo en cuatro fases, los archivos AGENTS.md y SPEC.md, los roles de múltiples agentes, los niveles de madurez y la deriva de intenciones.