Volver al blog
Construyendo un Meta-Agente: De Prompts Zero-Shot a Programas DSPy Auto-Optimizados

Construyendo un Meta-Agente: De Prompts Zero-Shot a Programas DSPy Auto-Optimizados

Most agent systems today are manually designed — you decide how many agents, what type (ReAct, RLM, ChainOfThought), what prompts they use, and how they route tasks. Every change requires editing code. The Lab 11 Meta-Agent experiment in the lab-experiments repo takes a different approach: what if the system could analyze a task, generate the right agents on the fly, optimize them through DSPy’s full Generative Feedback Loop, and consolidate what it learned — all without human intervention? This tutorial walks through the architecture, code, and CLI commands that make this possible.

La mayoría de los sistemas de agentes hoy son diseñados manualmente — tú decides cuántos agentes, qué tipo (ReAct, RLM, ChainOfThought), qué prompts usan y cómo enrutan las tareas. Cada cambio requiere editar código. El experimento Lab 11 Meta-Agent en el repositorio lab-experiments toma un enfoque diferente: ¿qué pasaría si el sistema pudiera analizar una tarea, generar los agentes correctos sobre la marcha, optimizarlos mediante el Generative Feedback Loop completo de DSPy y consolidar lo aprendido — todo sin intervención humana? Este tutorial recorre la arquitectura, el código y los comandos CLI que lo hacen posible.

Architecture: The Four-Step Pipeline

Arquitectura: El Pipeline de Cuatro Pasos

The meta-agent processes a user task through four sequential stages:

El meta-agente procesa una tarea de usuario a través de cuatro etapas secuenciales:

  1. Analyze — The task is analyzed using BestOfN with a ChainOfThought signature. Three candidate analyses are generated, and the one producing the most agent definitions is selected.
  2. Generate — Specialized DSPy agents are created dynamically: RLM (code + tools), ReAct (tools only), CodeAct (code only), or ChainOfThought (default). Each module is smoke-tested before acceptance.
  3. Execute — The agent stack runs with UCB-based frontier exploration, MultiChainComparison selection, and Refine-based prompt adaptation when quality drops below 0.7.
  4. Consolidate — Execution trajectories are mined for patterns via Trace2Skill consolidation, and rules are extracted via InferRules. LSE tracks quality improvement across iterations.
  1. Analizar — La tarea se analiza usando BestOfN con una firma ChainOfThought. Se generan tres análisis candidatos y se selecciona el que produce más definiciones de agentes.
  2. Generar — Se crean agentes DSPy especializados dinámicamente: RLM (código + herramientas), ReAct (solo herramientas), CodeAct (solo código) o ChainOfThought (predeterminado). Cada módulo se prueba antes de aceptarse.
  3. Ejecutar — El stack de agentes se ejecuta con exploración de frente basada en UCB, selección MultiChainComparison y adaptación de prompts mediante Refine cuando la calidad baja de 0.7.
  4. Consolidar — Las trayectorias de ejecución se minan en busca de patrones mediante consolidación Trace2Skill y se extraen reglas mediante InferRules. LSE rastrea la mejora de calidad entre iteraciones.

1. Task Analysis with BestOfN

1. Análisis de Tareas con BestOfN

The entry point is the AgentGenerator.analyze() method, which uses dspy.BestOfN to sample three candidate task decompositions and picks the most comprehensive one:

El punto de entrada es el método AgentGenerator.analyze(), que usa dspy.BestOfN para muestrear tres descomposiciones candidatas de la tarea y selecciona la más completa:

class AnalyzeTask(dspy.Signature):
    """Analyze a user task and determine what sub-agents are needed."""
    task: str = dspy.InputField()
    num_agents: int = dspy.OutputField(desc="How many distinct sub-agents needed")
    agent_definitions: str = dspy.OutputField(
        desc="JSON list: [{\"name\", \"role\", \"goal\", \"tools\", \"use_code\"}]"
    )

self._analyzer = dspy.BestOfN(
    dspy.ChainOfThought(AnalyzeTask),
    N=3,
    reward_fn=lambda ex, pred: (
        getattr(pred, "num_agents", 0)
        if hasattr(pred, "agent_definitions") and pred.agent_definitions
        else 0
    ),
)

The reward function maximizes num_agents, so the analysis that proposes the most sub-agents wins. This is intentional — better to over-generate and filter than to miss necessary capabilities. If parsing fails entirely, the system falls back to three default agents: a web researcher, a content analyst, and a research synthesizer.

La función de recompensa maximiza num_agents, por lo que gana el análisis que propone más sub-agentes. Esto es intencional — mejor sobre-generar y filtrar que perder capacidades necesarias. Si el parsing falla por completo, el sistema usa tres agentes predeterminados: un investigador web, un analista de contenido y un sintetizador de investigación.

2. Dynamic Agent Generation

2. Generación Dinámica de Agentes

Based on the agent definition from the analysis, generate_module() selects the appropriate DSPy module type using a decision hierarchy:

Basado en la definición del agente del análisis, generate_module() selecciona el tipo de módulo DSPy apropiado usando una jerarquía de decisión:

def generate_module(self, entry: AgentEntry) -> dspy.Module | None:
    tools = _build_tools(entry.tools, self._bridge)

    if entry.use_code and tools:
        module = dspy.RLM("task: str -> result: str", tools=tools)
    elif tools:
        module = dspy.ReAct("task: str -> result: str", tools=tools, max_iters=10)
    elif entry.use_code:
        module = dspy.CodeAct("task: str -> result: str")
    else:
        sig_cls = type(entry.name, (dspy.Signature,), {
            "__doc__": prompt,
            "task": dspy.InputField(),
            "result": dspy.OutputField(),
        })
        module = dspy.ChainOfThought(sig_cls)

    if not _validate_module(module):
        return None
    return module
ConditionAgent TypeUse Case
use_code=True + toolsdspy.RLMFull REPL agent — run Python, call MCP tools, sub-LLM queries
Has tools (no code)dspy.ReActTool-using agent with thought-action-observation loop
use_code=True onlydspy.CodeActCode-capable agent without tool dependencies
Neitherdspy.ChainOfThoughtPlain CoT with dynamically-created signature class via type()
CondiciónTipo de AgenteCaso de Uso
use_code=True + herramientasdspy.RLMAgente REPL completo — ejecuta Python, llama herramientas MCP, sub-LLM
Tiene herramientasdspy.ReActAgente con ciclo pensamiento-acción-observación
Solo use_code=Truedspy.CodeActAgente con capacidad de código sin herramientas
Ningunodspy.ChainOfThoughtCoT con clase de firma creada dinámicamente via type()

Each generated module is smoke-tested with a dummy query. If validation fails, the agent’s failure count increments and it’s deprioritized in MultiChainComparison rankings.

Cada módulo generado se prueba con una consulta dummy. Si la validación falla, el contador de fallos del agente se incrementa y se deprioriza en los rankings de MultiChainComparison.

3. MultiChainComparison Agent Selection

3. Selección de Agentes con MultiChainComparison

Instead of hardcoding a router, the meta-agent uses dspy.MultiChainComparison to evaluate up to three candidate agents and pick the best one for each task direction:

En lugar de codificar un enrutador, el meta-agente usa dspy.MultiChainComparison para evaluar hasta tres agentes candidatos y seleccionar el mejor para cada dirección de tarea:

class SelectAgentCompare(dspy.Signature):
    """Compare candidate agents and select the best one for the task."""
    task: str = dspy.InputField()
    candidate_agent: str = dspy.InputField(
        desc="JSON with name, role, run_count, avg_quality"
    )
    suitability: float = dspy.OutputField(desc="Suitability from 0.0 to 1.0")
    reasoning: str = dspy.OutputField(desc="Why this agent fits")

self._comparison = dspy.MultiChainComparison(SelectAgentCompare, n=3)

The selector serializes each candidate’s performance stats (run count, average quality, failure rate) into JSON, runs MultiChainComparison across three chains, and picks the candidate with the highest suitability score. This means agents improve their chances of selection by performing well — a natural feedback loop for task-to-agent mapping.

El selector serializa las estadísticas de rendimiento de cada candidato (conteo de ejecuciones, calidad promedio, tasa de fallos) en JSON, ejecuta MultiChainComparison a través de tres cadenas y selecciona el candidato con la puntuación de idoneidad más alta. Esto significa que los agentes mejoran sus posibilidades de selección al rendir bien — un bucle de retroalimentación natural para el mapeo tarea-a-agente.

4. The GFL Pipeline

4. El Pipeline GFL

The GFLPipeline runs four optimization strategies in sequence and compares results:

El GFLPipeline ejecuta cuatro estrategias de optimización en secuencia y compara los resultados:

  1. BootstrapFewShot — Traces execution, keeps passing demonstrations, attaches them as few-shot examples to the program.
  2. MIPROv2 — Bootstraps demonstrations, proposes instruction variants, performs Bayesian search over the (instruction, demo) space.
  3. GEPA — Executes the program, reads traces, diagnoses failures, mutates instructions, and selects via Pareto frontier.
  4. Sequential — Chains GEPA (prompt optimization) followed by BootstrapFewShot (demo extraction) — the most powerful combination.
  1. BootstrapFewShot — Traza la ejecución, conserva las demostraciones exitosas y las adjunta como ejemplos few-shot al programa.
  2. MIPROv2 — Bootstrapea demostraciones, propone variantes de instrucciones y realiza búsqueda bayesiana sobre el espacio (instrucción, demo).
  3. GEPA — Ejecuta el programa, lee trazas, diagnostica fallos, muta instrucciones y selecciona mediante frente de Pareto.
  4. Sequential — Encadena GEPA (optimización de prompts) seguido de BootstrapFewShot (extracción de demos) — la combinación más poderosa.
def run_full(self, program: dspy.Module) -> dict[str, tuple[dspy.Module, float]]:
    results = {}
    baseline_score = self.score(program)
    results["baseline"] = (program, baseline_score)

    bs_prog = self.bootstrap_fewshot(program)
    results["bootstrap_fewshot"] = (bs_prog, self.score(bs_prog))

    mipro_prog = self.mipro(program)
    results["mipro"] = (mipro_prog, self.score(mipro_prog))

    gepa_prog = self.gepa(program)
    results["gepa"] = (gepa_prog, self.score(gepa_prog))

    seq_prog = self.sequential(program)
    results["sequential"] = (seq_prog, self.score(seq_prog))

    return results

5. Self-Adaptation with Refine

5. Auto-Adaptación con Refine

When an agent’s output quality falls below 0.7, the meta-agent triggers dspy.Refine to improve the agent’s prompt template:

Cuando la calidad de salida de un agente cae por debajo de 0.7, el meta-agente activa dspy.Refine para mejorar la plantilla del prompt del agente:

class ImproveAgentPrompt(dspy.Signature):
    """Improve an agent's prompt based on its execution results."""
    agent_role: str = dspy.InputField()
    current_prompt: str = dspy.InputField()
    task: str = dspy.InputField()
    execution_result: str = dspy.InputField()
    quality_score: float = dspy.InputField()
    improved_prompt: str = dspy.OutputField()
    improvement_rationale: str = dspy.OutputField()

self._refine = dspy.Refine(
    dspy.ChainOfThought(ImproveAgentPrompt),
    N=3,
    reward_fn=lambda ex, pred: (
        1.0 if len(getattr(pred, "improved_prompt", "")) > 50 else 0.0
    ),
    threshold=0.5,
)

Refine runs up to 3 iterations, each time generating a better prompt based on the execution result and quality score. The refined prompt replaces the agent’s template, and the module cache is cleared so the next execution uses the improved version. This is the mechanism that makes the system genuinely self-adaptive.

Refine ejecuta hasta 3 iteraciones, cada vez generando un mejor prompt basado en el resultado de ejecución y la puntuación de calidad. El prompt refinado reemplaza la plantilla del agente y el caché del módulo se limpia para que la siguiente ejecución use la versión mejorada. Este es el mecanismo que hace que el sistema sea genuinamente auto-adaptativo.

6. Resource Governance

6. Gobernanza de Recursos

The ResourceBudget dataclass enforces hard limits on LLM calls, wall time, and agent count — essential for autonomous operation:

El dataclass ResourceBudget impone límites estrictos en llamadas LLM, tiempo real y conteo de agentes — esencial para la operación autónoma:

@dataclass
class ResourceBudget:
    max_llm_calls: int = 100
    max_wall_seconds: int = 300
    max_agents_generated: int = 10
    max_iterations: int = 20
    _llm_calls_used: int = 0
    _start_time: float = field(default_factory=time.time)

    def check_llm(self) -> None:
        self._llm_calls_used += 1
        if self._llm_calls_used > self.max_llm_calls:
            raise RuntimeError(f"LLM call budget exceeded ({self.max_llm_calls})")

Budgets are checked at three choke points in the execution loop: before each iteration, before each LLM call, and during agent generation. This prevents runaway costs in production deployments — treating agent systems as processes with finite resources.

Los presupuestos se verifican en tres puntos del bucle de ejecución: antes de cada iteración, antes de cada llamada LLM y durante la generación de agentes. Esto evita costos descontrolados en despliegues de producción — tratando los sistemas de agentes como procesos con recursos finitos.

7. Self-Evaluation and Consolidation

7. Auto-Evaluación y Consolidación

After execution, the meta-agent evaluates its own performance through evaluate_self(). It computes average quality, net improvement trend (via LSE), success rates per agent, and budget utilization. Execution trajectories are mined by Trace2Skill to extract reusable reasoning patterns:

Después de la ejecución, el meta-agente evalúa su propio rendimiento mediante evaluate_self(). Calcula la calidad promedio, la tendencia de mejora neta (via LSE), las tasas de éxito por agente y la utilización del presupuesto. Las trayectorias de ejecución son minadas por Trace2Skill para extraer patrones de razonamiento reutilizables:

class ExtractPatterns(dspy.Signature):
    """Extract reusable reasoning patterns from an execution trajectory."""
    trajectory_context: str = dspy.InputField()
    error_patterns: str = dspy.OutputField(desc="What went wrong and why")
    success_patterns: str = dspy.OutputField(desc="Effective patterns to reuse")
    improvement_suggestion: str = dspy.OutputField()

self._consolidator = SkillConsolidator(DIRECT_LM)

Skills are saved as JSON files in memory/skills/, creating a growing knowledge base that future sessions can reference.

Las habilidades se guardan como archivos JSON en memory/skills/, creando una base de conocimiento creciente que las sesiones futuras pueden referenciar.

CLI Commands and How to Run It

Comandos CLI y Cómo Ejecutarlo

The experiment is available in the lab-experiments repository. To run it:

El experimento está disponible en el repositorio lab-experiments. Para ejecutarlo:

git clone https://github.com/OctAg0nO/lab-experiments
cd lab-experiments
uv sync
cp .env.example .env  # Set DEEPSEEK_API_KEY

Five CLI commands provide progressive access to the system:

Cinco comandos CLI proporcionan acceso progresivo al sistema:

CommandDescription
generateAnalyze task and generate agents without executing them
runFull pipeline: generate, execute, consolidate
optimizeGenerate agents then run GEPA optimization on each
gflRun the full GFL pipeline comparing all optimizers
distillDistill compiled agents to a smaller student model
ComandoDescripción
generateAnalizar tarea y generar agentes sin ejecutarlos
runPipeline completo: generar, ejecutar, consolidar
optimizeGenerar agentes y ejecutar optimización GEPA en cada uno
gflEjecutar el pipeline GFL completo comparando todos los optimizadores
distillDestilar agentes compilados a un modelo estudiante más pequeño
# Full autonomous pipeline
uv run python -m lab.11_meta_agent --query "Research transformer attention mechanisms" --iterations 10 run

# GFL optimization comparison
uv run python -m lab.11_meta_agent --query "Classify user intent" gfl

# Distill to student model
uv run python -m lab.11_meta_agent distill

The meta-agent represents a shift from manually designing agent systems to compiling them — treating agent architecture as an optimization problem rather than a design artifact. The full source, including all supporting modules (frontier, LSE, Trace2Skill, MCP bridge), is in the lab-experiments repo.

El meta-agente representa un cambio de diseñar sistemas de agentes manualmente a compilarlos — tratando la arquitectura de agentes como un problema de optimización en lugar de un artefacto de diseño. El código fuente completo, incluyendo todos los módulos de soporte (frontier, LSE, Trace2Skill, MCP bridge), está en el repositorio lab-experiments.


References

Referencias

  • DSPy: Compilando Llamadas Declarativas de Modelos de Lenguaje en Pipelines Auto-Mejorables. ICLR 2024 (Spotlight). github.com/stanfordnlp/dspy
Compartir