Capa de Orquestación para el Reparto de Recursos, Agnóstica a la Labor
CORRAL convierte otros clientes LLM (Gemini, OpenCode, Ollama, Kiro…) en
herramientas MCP que Claude Code puede usar como bash o Read:
- Claude Code actúa como orquestador (planifica, decide, ensambla).
- Los agentes subordinados se limitan a ejecutar subtareas y escribir ficheros.
- Los resultados se intercambian vía sistema de ficheros, no por texto volátil.
Casos de uso típicos:
- Arbitraje de costes: delegar tareas baratas/rápidas a modelos pequeños.
- Paralelizar subtareas independientes (varios agentes en paralelo).
- Mantener control total sobre datos y costes (frente a SaaS de orquestación).
Objetivo: tener CORRAL funcionando con OpenCode en 5–10 minutos, sin cuenta externa ni configuración de modelo.
- Claude Code instalado (
npm install -g @anthropic-ai/claude-code) - OpenCode instalado (
npm install -g opencode-ai) - Python 3.x con pip3 disponible
(Opcional: Gemini, Ollama, Kiro – ver sección de instalación completa.)
# Debian/Ubuntu
sudo apt install python3-pip
pip3 install mcp --break-system-packages
# Fedora
sudo dnf install python3-pip -y
pip3 install mcp --break-system-packagesgit clone https://github.com/mmasias/pyCorral
cd pyCorral
mkdir -p ~/mcp-servers
cp servers/base.py ~/mcp-servers/
cp servers/opencode_mcp.py ~/mcp-servers/
cp servers/opencode-wrapper.sh ~/mcp-servers/
chmod +x ~/mcp-servers/opencode-wrapper.shclaude mcp add opencode --scope user -- python3 ~/mcp-servers/opencode_mcp.py
claude mcp listEn una sesión de Claude Code:
-
opencode_runsíncrono:prompt:Crea un fichero llamado hola.txt con el texto "hola mundo"workdir:~/misRepos/corral/opencode
-
Verificar el resultado con las herramientas de Claude:
Globen~/misRepos/corral/opencodeReaden~/misRepos/corral/opencode/hola.txt
Si ves el fichero, CORRAL está funcionando. El modelo por defecto es opencode/deepseek-v4-flash-free — sin autenticación ni cuota. A partir de aquí puedes:
- Añadir Gemini / Ollama / Kiro (ver instalación completa).
- Probar el modo
opencode_run_async+opencode_done. - Explorar patrones de orquestación paralela.
En un entorno habitual de trabajo con LLMs, un usuario tiene ya instalados varios clientes CLI: Claude Code, Gemini, OpenCode u otros. Cada uno tiene sus puntos fuertes, sus modelos y sus costes. El problema es que operan en silos: no hay forma nativa de que uno invoque a otro.
Claude Code ya tiene el bucle de control, el acceso al filesystem y el criterio de parada. Lo que no tiene es capacidad para delegar subproblemas a otros modelos con perspectivas distintas. CORRAL resuelve eso exponiendo cada CLI externo como herramienta MCP: Claude Code llama a gemini_run o opencode_run igual que llama a bash.
El problema no es solo técnico. Los modelos LLM tienen costes de razonamiento distintos: mantener a Claude concentrado en la coordinación y el criterio de calidad, mientras tareas de generación simple (borradores, análisis, ficheros) se delegan a modelos más rápidos y baratos, es una decisión de diseño que afecta tanto al coste como al resultado final.
Las plataformas SaaS de orquestación ofrecen esta capacidad, pero a cambio de pérdida de control, dependencia de proveedor y envío de datos a intermediarios. Este sistema resuelve el mismo problema desde la infraestructura propia.
El sistema opera bajo una separación de responsabilidades clara:
| Control Plane | Data Plane |
|---|---|
| Claude Code | Gemini, OpenCode, Ollama, Qwen, Gemma, Kiro |
| Decide, planifica, delega y ensambla. Es la unidad de razonamiento. | Ejecutan subproblemas acotados, producen artefactos y terminan sin estado persistente. No saben que están siendo orquestados. |
Claude Code (orquestador)
│
│
├── gemini_run / gemini_run_async / gemini_done
│ └── gemini_mcp.py -> gemini CLI -> Google Gemini
│
│
├── opencode_run / opencode_run_async / opencode_done
│ └── opencode_mcp.py -> opencode-wrapper.sh -> z.ai / OpenCode
│
│
├── ollama_run / ollama_run_async / ollama_done
│ └── ollama_mcp.py -> HTTP API -> Ollama (local)
│
│
├── qwen_run / qwen_run_async / qwen_done
│ └── qwen_mcp.py (OllamaAgentMCP) -> HTTP API -> Ollama qwen2.5:7b
│
│
├── gemma_run / gemma_run_async / gemma_done
│ └── gemma_mcp.py (OllamaAgentMCP) -> HTTP API -> Ollama gemma3-it-qat-tools:4b
│
│
└── kiro_run / kiro_run_async / kiro_done
└── kiro_mcp.py -> kiro-cli-chat CLI -> Kiro (AWS)
CORRAL no está acoplado a LLMs. El contrato que debe cumplir cualquier worker es ejecutarse de forma no interactiva, recibir instrucciones (vía argumentos o ficheros) y escribir resultados en un directorio de trabajo. Lo que haya dentro del proceso es irrelevante para el orquestador.
Hoy los workers son LLMs invocados desde CLI. Mañana pueden ser un renderizador de diagramas (PlantUML, Graphviz), un verificador formal (TLC, Alloy), un sintetizador de audio (Csound) o un transcodificador multimedia (ffmpeg).
La diferencia entre CORRAL y un coordinador de procesos estático (Make, Snakemake, Airflow) no es que los workers sean LLMs. Es que el coordinador —Claude— razona en tiempo de ejecución: decide qué delegar, a quién y cuándo recoger, en función del estado real del proyecto, sin un DAG declarado de antemano.
- El output de cada agente son ficheros escritos en un directorio de trabajo (workdir).
- El orquestador inspecciona los resultados con sus propias herramientas (Read, Glob, Grep).
Esto elimina la dependencia de flujos de texto volátiles y permite verificación determinista sobre el artefacto final.
| Herramienta | Modo | Comportamiento |
|---|---|---|
| gemini_run / opencode_run / ollama_run / qwen_run / gemma_run / kiro_run | Síncrono | Bloquea hasta terminar; escribe ficheros en workdir |
| gemini_run_async / opencode_run_async / ollama_run_async / qwen_run_async / gemma_run_async / kiro_run_async | Async | Devuelve job_id inmediatamente |
| gemini_done / opencode_done / ollama_done / qwen_done / gemma_done / kiro_done | Consulta | Devuelve "pendiente", "listo" o "error: ..." |
Los servidores son estructuralmente idénticos — solo difieren en el mecanismo de invocación. Los tokens de Gemini y OpenCode se imputan a su propio proveedor; Ollama (y sus becarios qwen y gemma) es local y no tiene coste de API.
| Limitación | Descripción |
|---|---|
| Logs de error en /tmp | El estado de los jobs persiste entre reinicios en ~/.local/share/corral/. Los detalles de error (logs en /tmp) pueden perderse si el SO reinicia antes de que el job sea consultado. |
| Sin retry automático | Si un agente falla a mitad de tarea, no hay rollback ni reintento. |
| Sin observabilidad | No hay logs estructurados del ciclo de vida de cada job. |
| Aislamiento por convención | La seguridad depende de que el prompt especifique un workdir adecuado. |
| Agente | Rol | Velocidad |
|---|---|---|
| Gemini | Verificador / critic | ~30 segundos |
| OpenCode / GLM-5.1 | Generador / arquitecto | ~2-3 minutos |
| Ollama / qwen2.5:7b | Inferencia local con function calling / sin coste de API | variable (CPU-only) |
| Qwen (qwen2.5:7b) | Becario Ollama por modelo, workdir propio (~/misRepos/corral/qwen) |
variable (CPU-only) |
| Gemma (gemma3-it-qat-tools:4b) | Becario Ollama por modelo, workdir propio (~/misRepos/corral/gemma) |
variable (CPU-only) |
| Kiro | Agente AWS / desarrollo con contexto de proyecto | ~30-60 segundos |
El modo async permite lanzar varios agentes simultáneamente sobre subtareas independientes. El tiempo total se aproxima al del agente más lento, no a la suma de todos.
El patrón "agente como herramienta" permite añadir cualquier CLI con modo no-interactivo sin modificar el orquestador.
Frente a plataformas SaaS: la ventaja es control total. La contrapartida es real: las plataformas cerradas incluyen observabilidad, retry logic y gestión de estado que aquí son responsabilidad del operador.
| Alternativa | Sí | Pero | Y entonces CORRAL… |
|---|---|---|---|
| LangChain | Muy madura, gran ecosistema | Abstracción excesiva, difícil de depurar, tus datos pasan por su stack | CORRAL es transparente: cada llamada es un proceso que puedes inspeccionar, matar o redirigir desde el terminal |
| LlamaIndex | Excelente para RAG y contexto documental | No es orquestación multi-agente real | CORRAL orquesta agentes heterogéneos con modelos y proveedores distintos, no documentos |
| CrewAI | Multi-agente con roles, fácil de arrancar | Los agentes no son CLIs reales, todo ocurre dentro del mismo proceso Python | CORRAL usa CLIs reales con sus propios modelos y costes imputados a sus proveedores, no a uno solo |
| AutoGen | Conversación entre agentes sofisticada | Complejo, opinionado, difícil de controlar el flujo | CORRAL delega el criterio al orquestador (Claude Code), no a un framework |
| n8n / Make | Visual, rápido para flujos simples | Ejecuta recetas fijas, sin razonamiento | CORRAL no ejecuta un flujo predefinido: Claude Code decide en tiempo real qué delegar, a quién y cuándo recoger |
| Dify / Flowise | GUI, cero código | Tus datos en servidores ajenos, sin control real | CORRAL corre en tu máquina, tus tokens van directo a cada proveedor, sin intermediarios |
| Make / Snakemake / Airflow | Coordinan procesos arbitrarios, no solo LLMs | El grafo de tareas está declarado de antemano; el coordinador no razona | En CORRAL el coordinador es Claude: decide en tiempo real qué delegar, a quién y cuándo recoger, en función del estado real del proyecto |
- Claude Code instalado (
npm install -g @anthropic-ai/claude-code) - gemini CLI instalado y autenticado
- opencode CLI instalado y autenticado con z.ai (v1.14+ para modo no-interactivo)
- Python 3.x con pip3 disponible
- Ollama instalado y ejecutándose (
ollama serve) — opcional, solo para el agente local
Debian/Ubuntu:
sudo apt install python3-pip
pip3 install mcp --break-system-packagesFedora:
sudo dnf install python3-pip -y
pip3 install mcp --break-system-packagesgit clone https://github.com/mmasias/pyCorral
cd pyCorralopencode models 2>/dev/null | head -10Anotar el modelo elegido. Se configurará como variable de entorno CORRAL_OPENCODE_MODEL.
mkdir -p ~/mcp-servers
cp servers/gemini_mcp.py ~/mcp-servers/
cp servers/opencode_mcp.py ~/mcp-servers/
cp servers/opencode-wrapper.sh ~/mcp-servers/
cp servers/ollama_mcp.py ~/mcp-servers/
cp servers/qwen_mcp.py ~/mcp-servers/
cp servers/gemma_mcp.py ~/mcp-servers/
cp servers/kiro_mcp.py ~/mcp-servers/
chmod +x ~/mcp-servers/opencode-wrapper.shAñadir a ~/.bashrc o ~/.zshrc:
export CORRAL_OPENCODE_MODEL="zai-coding-plan/glm-5.2" # sustituir por el modelo detectadoLos proveedores retiran modelos con el tiempo -- si este valor lleva meses sin tocarse, volver a correr opencode models antes de asumir que sigue existiendo.
Verificar que Ollama está corriendo:
curl http://127.0.0.1:11434/api/tagsHay tres becarios respaldados por Ollama, todos instancias de la clase OllamaAgentMCP:
| Becario | Servidor | Modelo default | Variable de entorno | Workdir default |
|---|---|---|---|---|
ollama |
ollama_mcp.py | qwen2.5:7b | CORRAL_OLLAMA_MODEL |
~/misRepos/corral/ollama |
qwen |
qwen_mcp.py | qwen2.5:7b | CORRAL_QWEN_MODEL |
~/misRepos/corral/qwen |
gemma |
gemma_mcp.py | aliafshar/gemma3-it-qat-tools:4b | CORRAL_GEMMA_MODEL |
~/misRepos/corral/gemma |
Para cambiar el modelo de un becario, añadir a ~/.bashrc o ~/.zshrc:
export CORRAL_OLLAMA_MODEL="nombre-del-modelo"
export CORRAL_QWEN_MODEL="nombre-del-modelo"
export CORRAL_GEMMA_MODEL="nombre-del-modelo"
export CORRAL_OLLAMA_URL="http://127.0.0.1:11434" # si escucha en otro puerto (compartido por los tres)Los servidores usan function calling (endpoint /api/chat con tools). El modelo debe declarar la capability tools — verificar con:
curl -s http://127.0.0.1:11434/api/show -d '{"model": "nombre-del-modelo"}' | python3 -c "import sys,json; print(json.load(sys.stdin).get('capabilities'))"qwen2.5 (cualquier tamaño), llama3.1, mistral-nemo y aliafshar/gemma3-it-qat-tools:4b son opciones probadas. Ojo: gemma3 nativo (gemma3:latest, gemma3:1b) no soporta tools en Ollama (error 400) — de ahí el fine-tune QAT como default del becario gemma.
Cualquiera de los tres acepta además el parámetro model por llamada para usar un modelo puntual distinto del default.
Modelos disponibles:
ollama listpython3 -c "import ast; ast.parse(open('servers/gemini_mcp.py').read()); print('ok')"
python3 -c "import ast; ast.parse(open('servers/opencode_mcp.py').read()); print('ok')"
python3 -c "import ast; ast.parse(open('servers/ollama_mcp.py').read()); print('ok')"
python3 -c "import ast; ast.parse(open('servers/qwen_mcp.py').read()); print('ok')"
python3 -c "import ast; ast.parse(open('servers/gemma_mcp.py').read()); print('ok')"
python3 -c "import ast; ast.parse(open('servers/kiro_mcp.py').read()); print('ok')"
mkdir -p /tmp/gemini_test
cd /tmp/gemini_test && gemini -y -p "Crea un fichero llamado hola.txt con el texto 'hola mundo'"
ls /tmp/gemini_test/
mkdir -p /tmp/opencode_test
echo "Crea un fichero llamado hola.txt con el texto 'hola mundo'" > /tmp/test_prompt.txt
cd /tmp/opencode_test && ~/mcp-servers/opencode-wrapper.sh /tmp/test_prompt.txt
ls /tmp/opencode_test/Añadir a ~/.claude/settings.json:
{
"permissions": {
"allow": [
"mcp__gemini__*",
"mcp__opencode__*",
"mcp__ollama__*",
"mcp__qwen__*",
"mcp__gemma__*",
"mcp__kiro__*"
]
}
}claude mcp add gemini --scope user -- python3 ~/mcp-servers/gemini_mcp.py
claude mcp add opencode --scope user -- python3 ~/mcp-servers/opencode_mcp.py
claude mcp add ollama --scope user -- python3 ~/mcp-servers/ollama_mcp.py
claude mcp add qwen --scope user -- python3 ~/mcp-servers/qwen_mcp.py
claude mcp add gemma --scope user -- python3 ~/mcp-servers/gemma_mcp.py
claude mcp add kiro --scope user -- python3 ~/mcp-servers/kiro_mcp.py
claude mcp listPrueba 1 - OpenCode sync: opencode_run con workdir /tmp/octest, crear hola.txt
Prueba 2 - Gemini sync: gemini_run con workdir /tmp/gtest, crear resumen.md
Prueba 3 - Ollama sync: ollama_run con workdir /tmp/oltest, crear resumen.md
Prueba 4 - Qwen sync: qwen_run con workdir /tmp/qwtest, crear resumen.md
Prueba 5 - Gemma sync: gemma_run con workdir /tmp/gmtest, crear resumen.md
Prueba 6 - async: opencode_run_async, anotar job_id, recoger con opencode_done
Regla fundamental: nunca enviar a dos agentes a escribir el mismo fichero en paralelo.
# 1. Claude crea estructura con placeholders
# 2. Delegar en paralelo
job_g = gemini_run_async(prompt="...", workdir="/tmp/webtest")
job_o = opencode_run_async(prompt="...", workdir="/tmp/webtest")
# 3. Hacer otras cosas. No poll ansioso.
# 4. Recoger
gemini_done(job_g)
opencode_done(job_o)
# 5. Ensamblarpython3 ~/mcp-servers/gemini_mcp.py
# Si se queda colgado: correcto. Si muestra error: corregir.- PATH incorrecto: los scripts usan detección automática, pero verificar con
which gemini - Modelo no disponible: verificar con
opencode models - opencode no autenticado: ejecutar opencode en TUI
Cambié CORRAL_OPENCODE_MODEL (o CORRAL_GEMINI_MODEL/CORRAL_OLLAMA_MODEL) en ~/.bashrc y sigue fallando igual
Los servidores MCP son procesos Python de larga duración que capturan os.environ una sola vez, al arrancar. Editar ~/.bashrc no lo actualiza en un proceso ya en marcha -- solo afecta a shells nuevas. Hay que reiniciar el proceso que tiene la variable vieja en memoria (reiniciar Claude Code, o matar el proceso python3 .../opencode_mcp.py correspondiente y dejar que el cliente MCP lo relance) antes de que el fix surta efecto. Confundir esto con un problema de API key o de modelo (que dan el mismo síntoma: el job falla o se queda colgado) cuesta tiempo de diagnóstico -- verificar primero si el proceso del servidor lleva vivo más tiempo que el cambio en ~/.bashrc.
chmod +x ~/mcp-servers/opencode-wrapper.sh
mkdir -p /tmp/oc_test
echo "Crea resultado.txt con 'prueba ok'" > /tmp/test_prompt.txt
cd /tmp/oc_test && ~/mcp-servers/opencode-wrapper.sh /tmp/test_prompt.txt
ls /tmp/oc_test/El estado de los jobs persiste entre reinicios de Claude Code en ~/.local/share/corral/jobs_<agente>.json. Este error indica un job_id que nunca existió o que fue resuelto antes de que el servidor arrancara por primera vez con esta versión.
Causa: stdin heredado. Solución: stdin=DEVNULL en todos los Popen/subprocess.run. Ya está en los scripts.
ps aux | grep gemini
ps aux | grep opencodecurl http://127.0.0.1:11434/api/tagsSi falla: ollama serve en otra terminal.
ollama listSi falta el modelo: ollama pull qwen2.5:14b
Es CPU-only — los modelos grandes son lentos en hardware sin GPU. Opciones:
- Usar el modelo default
qwen2.5:7b(equilibrio velocidad/capacidad) - Aceptar la latencia para tareas que no sean time-sensitive
Cualquier ejecutable con modo no interactivo puede convertirse en herramienta del corral — no solo LLMs. Los pasos son los mismos independientemente de lo que haya dentro del proceso:
- Paso 1: verificar modo no-interactivo (
--batch,-y, argumentos directos — sin prompts de teclado) - Paso 2: verificar que el proceso escribe resultados en el directorio de trabajo actual
- Paso 3: copiar
gemini_mcp.pycomo plantilla; adaptar el comando y los parámetros de entrada - Paso 4: registrar con
claude mcp addy añadir permiso ensettings.json
CORRAL funciona de forma autónoma, pero combinado con myClaudeContext la ecuación se completa: myClaudeContext aporta memoria persistente y contexto sincronizado entre máquinas; CORRAL aporta la invocación en tiempo real de agentes subordinados.
El contexto fluye por convención de ficheros: Gemini CLI carga ~/.gemini/GEMINI.md como contexto global y el GEMINI.md del directorio de trabajo como contexto de proyecto. Si esos ficheros están sincronizados entre máquinas mediante myClaudeContext, cada agente subordinado opera con el mismo conocimiento de base que el orquestador, sin configuración adicional. El workdir que CORRAL pasa a cada agente actúa como frontera natural: arrancas Claude Code desde el directorio que te interesa, y ese scope se propaga automáticamente a los agentes delegados.
Claude: Orquestador de Recursos Remotos para Agentes en Loop / Claude Orchestrated Runtime for Remote Agent Loop
