tk3.biz
Volver al blog

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.

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.

→ github.com/istb16/dev-orchestra

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.

PiezaFunción
dev-orchestraConstruye el prompt e inicia el comando que devuelve el adaptador
AdaptadorDevuelve “qué comando iniciar y con qué argumentos”
CLIPasa 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 en 0.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.

PuntoPor 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 defaultAnte 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 es python --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.

SOUbicació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)). Si doctor lo 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 familia recommended-coding y, a la hora de revisar, mi adaptador lo rechazó con only 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

RolCon este adaptadorPor qué
RevisiónSíEl diff se pasa en el prompt, así que funciona sin leer archivos
DiseñoParcialFunciona, pero solo ve el prompt que recibe. No puede consultar por su cuenta los archivos relacionados
Implementación / correcciónNoNo 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.1 sin pasar por un proxy (ProxyHandler({}) del paso 1). Si cambias LOCAL_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 adapter en doctor significa 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 doctor una 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.base y escribe resolve_latest y build_command
  • Va en providers/ del directorio de configuración, que sobrevive a las actualizaciones del plugin
  • Una vez colocado, usa doctor para 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