Claude Orchestrator — Agent on Agent
Un sistema agent-on-agent que lanza Claude Code CLI como subproceso y, cuando Claude presenta opciones o preguntas de diseño, un evaluador de OpenAI (GPT-4o) elige automáticamente la mejor opción para continuar.
Arquitectura
┌─────────────────────────────────────────────────────────┐
│ ORCHESTRATOR (Node.js) │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ index.ts │───>│orchestrator.ts│───>│decision-maker │ │
│ │ (CLI) │ │ (multi-turn │ │ (OpenAI) │ │
│ └──────────┘ │ loop) │ └───────┬───────┘ │
│ └──────┬───────┘ │ │
│ │ │ │
│ spawn │ API call │ │
│ ┌──────────▼──────┐ ┌─────────▼───────┐ │
│ │ Claude Code │ │ OpenAI API │ │
│ │ CLI (-p mode) │ │ (GPT-4o) │ │
│ │ stream-json │ │ json_object │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘
Flujo multi-turno
El problema central: Claude Code en modo -p (print) es single-turn — ejecuta, responde, y termina. Cuando Claude presenta opciones, las escribe como texto plano y sale.
La solución es un loop de turnos:
Turn 1: Orchestrator envía la tarea a Claude
Claude responde con opciones (A/B/C)
Claude termina (exit 0)
│
▼
OpenAI analiza la respuesta
Detecta que hay opciones
Elige la mejor
│
▼
Turn 2: Orchestrator relanza Claude con --resume <session_id>
Envía la elección como nuevo prompt
Claude continúa donde lo dejó
(repite si hay más preguntas)
│
▼
Turn N: OpenAI detecta que no hay pregunta → FIN
Estructura del proyecto
claude-orchestrator/
├── package.json # Dependencias: openai, tsx, typescript
├── tsconfig.json # Config TypeScript (ESNext, NodeNext)
├── README.md
└── src/
├── index.ts # Entry point CLI — parsea argumentos, lanza orchestrator
├── orchestrator.ts # Núcleo: spawn de Claude, loop multi-turno, stream-json
├── decision-maker.ts # Llama a OpenAI para evaluar opciones o responder preguntas
├── config.ts # Constantes (modelo evaluador, max turns, budget)
├── logger.ts # Logger con colores diferenciados por actor
└── types.ts # Interfaces TypeScript
Archivos clave
orchestrator.ts — El cerebro del sistema:
- resolveClaudePath(): encuentra el binario de Claude en el PATH (normaliza backslashes en Windows)
- run(): loop principal que itera turnos hasta que no haya más preguntas
- executeTurn(): spawna claude -p <prompt> --output-format stream-json --verbose y parsea el stream JSON línea por línea
- isQuestion(): heurísticas rápidas para detectar si Claude terminó con una pregunta
- analyzeAndDecide(): delega a OpenAI para analizar la respuesta completa y generar la elección
decision-maker.ts — El evaluador (OpenAI):
- evaluate(): para opciones estructuradas con labels — responde en JSON {chosenLabel, reasoning}
- answerFreeText(): para preguntas abiertas sin opciones predefinidas
- analyzeAndChoose(): analiza texto libre de Claude, detecta opciones embebidas, y elige (método principal usado por el loop multi-turno)
- Usa response_format: { type: "json_object" } para respuestas estructuradas
- Fallback: si OpenAI falla, elige la primera opción
logger.ts — Colores diferenciados:
- CLAUDE (azul brillante): output de Claude con barra lateral |
- ORCHESTRATOR >>> CLAUDE (verde brillante): respuestas enviadas a Claude
- DECISION (magenta): caja con la decisión de OpenAI
- TURN N/M (badge blanco sobre gris): separador de turnos
Desafíos resueltos durante la construcción
1. SDK de Claude Code no soporta Windows
El paquete npm @anthropic-ai/claude-code falla en Windows con "Claude Code is not supported on Windows". Solución: spawn directo del binario CLI con child_process.spawn.
2. Resolución del binario en Windows/Git Bash
where claude devuelve rutas con backslashes (C:\Users\...) que spawn() no puede resolver. Solución: normalizar a forward slashes con .replace(/\\/g, "/").
3. --input-format stream-json cuelga el proceso
Claude con este flag espera input indefinidamente en stdin. Sin él, procede después de un warning de 3 segundos. Solución: no usar este flag; la comunicación multi-turno se hace con --resume.
4. --bare rompe la autenticación
El flag --bare salta el keychain/OAuth y Claude responde "Not logged in". Solución: no usar --bare.
5. Claude presenta opciones como texto, no como tool_use
En modo -p, Claude no usa AskUserQuestion — escribe las opciones directamente en su respuesta. Solución: loop multi-turno con detección heurística + análisis de OpenAI.
Instalación
cd claude-orchestrator
npm install
Requisitos
- Node.js >= 18
- Claude Code CLI instalado y autenticado (claude --version)
- OpenAI API Key con acceso a GPT-4o
Uso
# Variable de entorno requerida
set OPENAI_API_KEY=sk-proj-... # CMD
export OPENAI_API_KEY=sk-proj-... # Bash
# Ejecución básica
npx tsx src/index.ts --task "tu tarea aquí" --cwd ./tu-proyecto
# Con debug (muestra todos los mensajes internos)
npx tsx src/index.ts --task "tu tarea" --cwd ./mi-proyecto --debug
# Especificar modelo evaluador
npx tsx src/index.ts --task "tu tarea" --evaluator-model gpt-4o-mini
# Limitar presupuesto de Claude
npx tsx src/index.ts --task "tu tarea" --max-budget 2.0
# Limitar turnos de conversación
npx tsx src/index.ts --task "tu tarea" --max-turns 5
Todas las opciones
| Flag | Descripción | Default |
| --task <prompt> | Tarea para Claude (o argumento posicional) | requerido |
| --cwd <dir> | Directorio de trabajo para Claude | directorio actual |
| --worker-model <m> | Modelo de Claude Code | default de claude |
| --evaluator-model <m> | Modelo OpenAI para decisiones | gpt-4o |
| --max-budget <n> | Presupuesto máximo en USD | 5.0 |
| --max-turns <n> | Máximo de turnos pregunta/respuesta | 10 |
| --permission-mode <m> | Modo de permisos de Claude | bypassPermissions |
| --no-skip-permissions | No usar --dangerously-skip-permissions | false |
| --debug | Logging detallado | false |
Variables de entorno
| Variable | Descripción |
| OPENAI_API_KEY | API key de OpenAI (requerida) |
| CLAUDE_CLI | Ruta al binario de Claude (default: claude) |
| LOG_LEVEL | Nivel de log: debug, info, warn, error |
Ejemplos de uso
1. Crear un proyecto con decisiones automáticas
npx tsx src/index.ts \
--task "Crea una CLI de calculadora. Presenta opciones de diseño cuando las tengas." \
--cwd ./test-project \
--debug
Resultado esperado:
TURN 1/10
CLAUDE
| Tengo algunas opciones de diseño:
| Lenguaje: A) Python B) Node.js C) Go
| Interfaz: A) REPL interactivo B) Comando único
| ...
+------------------------------------------------------------+
| DECISION (OpenAI)
| Question: ¿Qué prefieres para cada punto?
| Chosen: A) Python, A) REPL interactivo, B) Extendidas
| Reasoning: OpenAI evaluated Claude's options
+------------------------------------------------------------+
ORCHESTRATOR >>> CLAUDE
| A) Python, A) REPL interactivo, B) Extendidas
TURN 2/10
CLAUDE
| Perfecto. Creando la calculadora...
| [ejecuta herramientas, crea archivos]
| ¡Listo! La calculadora está en calc.py
2. Revisar un proyecto existente
npx tsx src/index.ts \
--task "Revisa este proyecto y sugiere mejoras de seguridad" \
--cwd ./mi-app
3. Refactoring con presupuesto limitado
npx tsx src/index.ts \
--task "Refactoriza el módulo de autenticación para usar JWT" \
--cwd ./backend \
--max-budget 1.0 \
--max-turns 3
4. Usar un modelo más económico para decisiones
npx tsx src/index.ts \
--task "Crea tests unitarios para el módulo de users" \
--cwd ./api \
--evaluator-model gpt-4o-mini
5. Guardar el log completo
# Log a archivo, resultado a terminal
npx tsx src/index.ts --task "tu tarea" --cwd ./proyecto --debug 2> session.log
# Todo a terminal Y archivo
npx tsx src/index.ts --task "tu tarea" --cwd ./proyecto --debug 2>&1 | tee session.log
Output del reporte final
Al terminar, el orchestrator muestra:
=== Final Report ================================================
Status SUCCESS
Cost $0.2341
Duration 45.2s
Decisions 2
=== Auto-Decisions Made =========================================
Question ¿Qué lenguaje prefieres?
Chosen Python
Why OpenAI analysis of Claude's options
Question ¿REPL o comando único?
Chosen REPL interactivo
Why OpenAI analysis of Claude's options
=== Result ======================================================
La calculadora está lista en calc.py ...