Conecté un agente propio a dev-orchestra (edición LLM local)
Introducción
En el artículo anterior escribí sobre cómo reorganizar revisores y roles en dev-orchestra. Esta vez conecto una IA distinta de Claude Code, Codex o Antigravity con un adaptador escrito por mí. Como ejemplo, añado como revisor el LLM local que tengo funcionando en mi propia máquina.
Ya he escrito dos artículos sobre cómo conectar un LLM local.
- Añadir un LLM local como revisor de código de dev-orchestra (en inglés): los tanteos hasta conseguir conectarlo y cómo entró el mecanismo oficial con la 0.9.0
- Revisar código confidencial con IA sin enviarlo a la nube (en inglés): revisiones usando solo un LLM local
Este artículo omite los antecedentes y se centra en los pasos para escribir un adaptador desde cero en dev-orchestra 1.0.0.
dev-orchestra es de código abierto en GitHub (licencia MIT). Si te parece útil, una estrella me haría mucha ilusión.
Cómo funciona: el adaptador solo “construye un comando”
dev-orchestra no llama a las IA directamente. Simplemente inicia una CLI, le pasa el prompt por la entrada estándar y lee la respuesta de la salida estándar. El adaptador (provider) es quien construye el comando de esa CLI.
| Pieza | Función |
|---|---|
| dev-orchestra | Construye el prompt e inicia el comando que devuelve el adaptador |
| Adaptador | Devuelve “qué comando iniciar y con qué argumentos” |
| CLI | Pasa a la IA el prompt recibido por la entrada estándar y escribe la respuesta en la salida estándar |
Es decir, se puede conectar cualquier IA siempre que tengas una CLI que lea de la entrada estándar y escriba en la salida estándar.
Pasos
1. Preparar una CLI que reciba un prompt y devuelva una respuesta
Los LLM locales (como llama-server de llama.cpp) exponen una API compatible con OpenAI. Escribí una CLI que solo envía el prompt allí e imprime la respuesta. Basta con la biblioteca estándar.
"""Send a prompt from stdin to a local OpenAI-compatible server and print the answer."""
import json
import os
import sys
import urllib.request
URL = os.environ.get("LOCAL_LLM_URL", "http://127.0.0.1:8080") + "/v1/chat/completions"
def main():
# dev-orchestra writes the prompt as UTF-8, whatever the OS locale is
sys.stdin.reconfigure(encoding="utf-8")
prompt = sys.stdin.read()
body = json.dumps({"messages": [{"role": "user", "content": prompt}]}).encode("utf-8")
request = urllib.request.Request(URL, data=body, headers={"Content-Type": "application/json"})
# Ignore proxy settings so the prompt never leaves this machine
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
with opener.open(request, timeout=1800) as response:
answer = json.load(response)["choices"][0]["message"]["content"]
sys.stdout.reconfigure(encoding="utf-8")
sys.stdout.write(answer)
if __name__ == "__main__":
main()
Guárdalo como local_llm_review.py donde quieras.
En esta CLI tuve cuidado con dos cosas.
| Lo que escribí | Por qué |
|---|---|
sys.stdin.reconfigure(encoding="utf-8") | dev-orchestra escribe el prompt en UTF-8. El Windows en japonés lee por defecto en cp932, así que los diffs con comentarios en japonés salen con caracteres corruptos o fallan al leerse |
Usar un opener creado con ProxyHandler({}) | urlopen respeta variables de entorno como HTTP_PROXY y la configuración de proxy de Windows. Incluso para 127.0.0.1, tu código puede salir de la máquina a través de un proxy |
Haz que el servidor del LLM local escuche solo en
127.0.0.1(localhost). Si escucha en0.0.0.0, cualquiera de la misma red puede usarlo.
2. Escribir el adaptador
Ahora el adaptador. Me basé en el ejemplo mínimo de la documentación de dev-orchestra y lo escribí para el LLM local.
"""Adapter for a local LLM behind an OpenAI-compatible server. Review only."""
from __future__ import annotations
import sys
from typing import Any, Dict, List, Optional, Sequence
from orchestrator.providers.base import (
READ_ONLY_MODES,
ModelCandidate,
ModelResolutionError,
Provider,
ResolvedModel,
)
# The small CLI that sends the prompt to the local server
CLI = r"C:\path\to\local_llm_review.py"
class LocalLLMProvider(Provider):
name = "localllm"
display_name = "Local LLM"
executable = sys.executable
fallback_models = (ModelCandidate("", "default", "server default", "builtin-fallback"),)
fallback_updated = "2026-10-12"
option_keys = ("args",)
def resolve_latest(self, family: str) -> ResolvedModel:
if family in ("", "default"):
return ResolvedModel(self.name, "default", "latest", None, "server default", "cli-default")
raise ModelResolutionError("localllm: only the default model is supported, not %r" % family)
def build_command(
self,
mode: str,
resolved: ResolvedModel,
cwd: str,
extra_args: Sequence[str] = (),
options: Optional[Dict[str, Any]] = None,
) -> List[str]:
# The CLI cannot read or write files, so it can only review a diff it is given.
if mode not in READ_ONLY_MODES:
raise ModelResolutionError("localllm cannot implement: it has no file access")
return [self.executable, CLI] + self.option_args(options)
def build_provider(executable: Optional[str] = None) -> LocalLLMProvider:
return LocalLLMProvider(executable)
Hay cuatro puntos clave.
| Punto | Por qué |
|---|---|
from orchestrator.providers.base import ... | Este archivo se carga desde fuera del paquete del plugin, así que no funcionan los imports relativos (from .base) |
name = "localllm" | El nombre que escribes en provider: de la configuración. No se permiten claude, codex, agy ni mock |
resolve_latest solo acepta default | Ante un nombre de modelo que no puede verificar, se detiene en lugar de adivinar, siguiendo la regla de dev-orchestra en general |
| Rechazar ejecuciones que no sean de solo lectura (implementación) | Esta CLI no puede leer ni escribir archivos. Aunque la pongan como implementadora, nunca debe fingir que tuvo éxito sin cambiar nada |
Cambia CLI por la ubicación real del archivo que guardaste en el paso 1. El r"..." del ejemplo permite escribir tal cual la \ de las rutas de Windows. En macOS / Linux, escribe algo como CLI = "/home/you/local_llm_review.py".
Como
executable = sys.executable, lo que dev-orchestra ejecuta para comprobar la instalación espython --version. La CLI no necesita tener su propio--version.
3. Colocarlo en providers/ del directorio de configuración
El adaptador va en providers/ del directorio de configuración.
| SO | Ubicación |
|---|---|
| Windows | %APPDATA%\dev-orchestra\providers\ |
| macOS / Linux | ~/.config/dev-orchestra/providers/ |
Ponle al archivo un nombre que sirva como identificador, como localllm.py.
No lo pongas dentro del plugin (
~/.claude/plugins/cache/...). Lo que haya allí se borra cuando el plugin se actualiza. El directorio de configuración es donde vive el archivo de configuración (config.yaml), así que sobrevive a las actualizaciones.
En Windows, si usas la versión de Python de la Microsoft Store, es posible que Python no vea los archivos que pongas en
%APPDATA%(perdí medio día con esto en un artículo anterior (en inglés)). Sidoctorlo detecta, muestra la ubicación real y cómo solucionarlo.
4. Comprobar con doctor que se ha cargado
Una vez colocado el adaptador, ejecuta dev-orchestra doctor antes que nada (pedírselo al agente funciona igual). Esto es lo que obtuve (extracto, sin rutas completas).
Local LLM (localllm)
Installed: yes
Source: user module <config dir>\providers\localllm.py
Model selection: supported
Models (builtin-fallback): server default
Read-only runs: not reported by this adapter
Resume: NOT SUPPORTED -- localllm does not resume sessions
User providers
Directory: <config dir>\providers
Code in this directory is imported at startup, from outside the plugin.
Imported: localllm <- <config dir>\providers\localllm.py
Incluso muestra desde qué archivo se cargó. Si termina con No problems found., ya está.
Aunque cometas un error tipográfico, dev-orchestra en conjunto sigue funcionando y doctor te indica el archivo y el error.
5. Registrarlo como revisor
Añádelo como revisor. No olvides --model default.
dev-orchestra reviewer add --provider localllm --model default --role general
Al principio olvidé
--model. Se registró con el nombre de familiarecommended-codingy, a la hora de revisar, mi adaptador lo rechazó cononly the default model is supported, not 'recommended-coding'. Es exactamente el comportamiento de “detenerse ante nombres que no puede verificar” del paso 2.
6. Hacer que revise
Lo probé con el ejemplo con errores plantados del artículo anterior. Pero lo que quería comprobar aquí era si la conexión funciona. Así que, en lugar del LLM local, usé un servidor ficticio que devuelve una respuesta fija.
ok localllm-general localllm server default 1 finding(s)
1 successful, 0 failed
La respuesta del servidor ficticio volvió a través de mi adaptador. Lo que esto confirmó es la conexión del adaptador y la lectura del formato de la respuesta. Si la respuesta sigue el formato de dev-orchestra (## Finding …), pasa por la deduplicación y el triaje igual que los hallazgos de los demás revisores.
Sobre lo bien que revisa realmente un LLM local, consulta un artículo anterior (en inglés) (comparado con dos comerciales, se adoptó 1 de 6 hallazgos) y el artículo sobre código confidencial (en inglés).
7. Confirmar que se niega cuando se le pide implementar
Por si acaso, puse este adaptador como implementador y lo ejecuté.
localllm cannot implement: it has no file access
Se detuvo antes de iniciar la CLI. Aunque lo pongas por error en el rol de implementador, no te encontrarás con ese fallo difícil de notar de “tuvo éxito pero no cambió nada”.
Roles que puede y no puede asumir
| Rol | Con este adaptador | Por qué |
|---|---|---|
| Revisión | Sí | El diff se pasa en el prompt, así que funciona sin leer archivos |
| Diseño | Parcial | Funciona, pero solo ve el prompt que recibe. No puede consultar por su cuenta los archivos relacionados |
| Implementación / corrección | No | No puede escribir archivos, así que el adaptador lo rechaza |
Claude Code y Codex pueden seguir e investigar por su cuenta los archivos relacionados. Esta CLI solo ve lo que se le da, así que funciona mejor como un revisor que aporta otra perspectiva.
Precauciones
- Los archivos de
providers/se ejecutan con tus permisos cada vez que se inicia dev-orchestra. No hay sandbox. Coloca solo archivos que hayas escrito tú o que hayas leído y en los que confíes - El servidor del LLM local escucha solo en localhost (la nota del paso 1)
- Por el lado de la CLI, envía a
127.0.0.1sin pasar por un proxy (ProxyHandler({})del paso 1). Si cambiasLOCAL_LLM_URL, mantenlo también en una dirección local - Si quieres que tu código no salga de tu máquina, no llames a dev-orchestra desde una IA en la nube como Claude Code, porque la IA que lo invoca lee el código. Ejecútalo directamente desde el terminal y limítalo al LLM local con
--only(el artículo sobre código confidencial (en inglés)) - Tu propio adaptador no se añade automáticamente a los presets. Solo se ejecuta cuando lo nombras en la configuración
Read-only runs: not reported by this adapterendoctorsignifica que este adaptador no informa de cómo garantiza el modo de solo lectura. Esta CLI nunca toca archivos, así que no hay daño real, pero cuando conectes una CLI que pueda tocar archivos, tendrás que comprobar por ti mismo que realmente se mantiene en solo lectura- Las partes en las que se apoyan los adaptadores (las que usa el ejemplo de este artículo) están cubiertas por la promesa de compatibilidad de la 1.0.0. Aun así, es tranquilizador ejecutar
doctoruna vez tras actualizar dev-orchestra
Resumen
- dev-orchestra solo inicia una CLI. Con una CLI que lea de la entrada estándar y escriba en la salida estándar, se puede conectar cualquier IA
- El adaptador tiene unas 50 líneas. Importa desde
orchestrator.providers.basey escriberesolve_latestybuild_command - Va en
providers/del directorio de configuración, que sobrevive a las actualizaciones del plugin - Una vez colocado, usa
doctorpara comprobar desde qué archivo se cargó - Se registra con
reviewer add --provider localllm --model default --role general - Haz que una CLI que no puede tocar archivos sea solo de revisión y que el adaptador rechace la implementación
- El código de
providers/se ejecuta con tus permisos. Coloca solo lo que sea de confianza
Las reglas detalladas están en la documentación de providers.
Todos los artículos de esta serie están en la lista de artículos de “dev-orchestra”.
GitHub: istb16/dev-orchestra (una estrella siempre se agradece)
Artículos relacionados
Reorganizando los revisores y los roles de dev-orchestra
Cómo indicar el modelo de cada rol de dev-orchestra, cómo elegir los enfoques de los revisores y config suggest-roles, que propone especialistas de dominio. También le mostré a 9 revisores un cambio con bugs plantados a propósito para medir qué cambia al aumentar su número.
Comparamos los presets quality / standard / fast de dev-orchestra
Expandí los presets de configuración inicial de dev-orchestra, quality / standard / fast, en tres entornos (solo Claude Code, solo Codex y ambos) y los comparé. Resumo cómo cambian el modelo por rol, el número de revisores y cuánto se recorta el coste, y cuál conviene elegir.
Probé dev-orchestra en Antigravity
También instalé dev-orchestra en Antigravity. Funciona sin problemas como el lugar donde hablas, pero encargarle el diseño y la revisión a la CLI de Antigravity (agy) es limitado. Aquí resumo lo que se puede hacer, lo que no y cómo recomiendo usarlo.