Puente Cordis
El puente Cordis integra el motor DeepSeek Harness (DSH) en el backend de Libre WebUI. DSH se ejecuta como un árbol de plugins dentro de un entorno Cordis alojado por Libre WebUI, por lo que sus capacidades se ofrecen como servicios Cordis en lugar de módulos importados.
El puente está desactivado de forma predeterminada. Nada de lo descrito aquí ocurre hasta que un operador lo activa (consulta Configuración de Cordis).
Por qué un puente en lugar de una integración directa
Importar los paquetes DSH desde los servicios de Libre WebUI sería más corto, pero peor. La importación directa convierte el motor en una dependencia de compilación, de modo que cambiar un adaptador de modelo, sustituir el bucle del agente o retirar el motor exige modificar y volver a desplegar Libre WebUI.
El puente invierte esa relación. Libre WebUI depende de un único contrato abstracto y un documento de composición de Cordis decide qué lo implementa:
- Cambiar de destino sin recompilar. La composición es un archivo YAML, por lo que apuntar el motor a otro proveedor es un cambio de configuración.
- Configurar capacidades. Cada capacidad corresponde a una entrada del cargador. Los cambios en la composición mantenida por el operador se aplican la próxima vez que arranca el host.
- Retirar limpiamente. Cada servicio, receptor y efecto instalado por el motor pertenece a la fibra raíz. Liberarla revierte todo ello y permite detener el motor sin reiniciar Libre WebUI.
Capas
Las dependencias concretas de DSH permanecen en backend/src/cordis/dsh/. Las rutas y los servicios de la aplicación consumen los contratos del puente. El controlador de Work tiene una composición independiente en memoria y nunca monta los plugins del sistema de archivos del host.
Contratos
El contrato está en backend/src/cordis/contracts.ts. Su alcance es deliberadamente limitado: las estructuras que necesita la API de Libre WebUI, expresadas sin vocabulario interno del motor.
| Contrato | Finalidad |
|---|---|
DshEngine.status() | Estado del ciclo de vida de cada servicio del motor (pending / ready / failed) |
DshEngine.modelConfiguration() | Modelo y proveedor predeterminados de la composición en ejecución |
DshEngine.listSessions() | Resúmenes de sesiones, empezando por las más recientes |
DshEngine.getSession(id) | Una sesión con sus mensajes proyectados |
DshEngine.createSession(opts) | Reservar un identificador de sesión y un directorio de trabajo |
DshEngine.updateSessionSettings(id, settings) | Guardar la selección real del modelo y el modo nativo de permisos del sistema de archivos mientras la sesión está inactiva |
DshEngine.decideApproval(id, approvalId, decision) | Resolver una aprobación nativa de herramienta pendiente para la sesión a la que pertenece |
DshEngine.deleteSession(id) | Terminar una sesión y liberar su agente |
DshEngine.listAgents() | Agentes activos, marcados como raíz o hijo |
DshEngine.listTools() | Herramientas accesibles al modelo que registró el motor |
DshEngine.sendMessage(id, txt) | Iniciar un turno y devolver un identificador de flujo |
DshEngine.cancel(id) | Cancelar el turno en curso de una sesión |
El contrato se publica como servicio Cordis libreDshEngine. Un consumidor lo lee mediante ctx.get('libreDshEngine') y nunca importa el módulo del puente.
EngineStreamChunk transporta text, reasoning, tool-call, tool-result, approval-request, approval-decision, error y done. Las tramas en vivo se encaminan según el agente y la sesión a los que pertenecen; el mensaje persistente del asistente correspondiente no se emite por segunda vez. sendMessage devuelve un identificador cuyo subscribe reproduce todo lo ya emitido, por lo que un primer token rápido no puede perderse entre el inicio del turno y la conexión del receptor HTTP.
Secuencia de un turno de chat
Se usa NDJSON en lugar de WebSocket porque un turno es una única secuencia del servidor al cliente tras la solicitud. Mantenerla en el POST evita una segunda negociación, un ticket y un protocolo de reconexión, y conserva todo el turno dentro de una solicitud autenticada.
DONE y PENDING
Cordis activa un plugin cuando están disponibles los servicios que declara, por lo que una entrada pasa tiempo en estados en los que aún no se ejecuta. Hay dos conceptos distintos importantes; confundirlos es la causa más habitual de un motor que no responde.
Estado de una entrada del cargador. El cargador sigue cada entrada a través de PENDING → LOADING → ACTIVE o FAILED. Una entrada cuyos servicios declarados faltan permanece pendiente indefinidamente en lugar de fallar. Por eso una composición incompleta puede producir un motor que arranca, pero no ofrece nada.
Disponibilidad del servicio. El host comunica cada servicio esperado como:
| Estado | Significado | Causa |
|---|---|---|
pending | No registrado en el contexto | La entrada que lo proporciona no se ha activado o está desactivada |
ready | Registrado y utilizable | La entrada que lo proporciona se ha activado |
failed | Declarado, pero inutilizable | Se comunica con una cadena detail |
host.status() lista todos los servicios esperados con su disponibilidad e identifica los obligatorios que faltan; GET /api/cordis/health expone la misma información. Una composición que omite un servicio requerido provoca un error al arrancar, en lugar de publicar un motor que responde con listas vacías.
Es fácil equivocarse en dos cadenas de dependencias:
dsh-toolsno puede arrancar sinsystemPrompt.dsh-agent-loopno puede arrancar hasta que existanagents,sessions,llm,tools,systemPromptysessionProjections.
Si falta cualquiera de ellos, la composición produce un almacén de sesiones funcional y un motor que nunca responde a un mensaje.
Configuración de proveedores
La entrada incluida libre-webui-llm-adapter atiende los proveedores de modelos configurados en Libre WebUI. El selector de la página del motor elige un modelo de proveedor para una sesión sin sustituir esa entrada.
Los cambios en las entradas de plugins de la composición se aplican en el siguiente arranque del host. Reinicia el backend o desactiva y vuelve a activar Cordis cuando el interruptor de administrador esté desbloqueado. Las sesiones persistentes permanecen en el almacén configurado y se reanudan mediante la composición actual.
El código de integración de confianza puede utilizar directamente las API del ciclo de vida del cargador Cordis. El puente no expone un endpoint para intercambiar adaptadores ni restaura automáticamente el anterior si falla su sustituto.
Reversión
Liberar la fibra raíz del host retira todo lo que instaló el motor. Esa relación de propiedad constituye toda la garantía, porque:
- Los servicios los registran los plugins, por lo que se retiran junto con su fibra.
- Las suscripciones a
session/eventse registran dentro del constructor del puente y pertenecen a la fibra de su entrada. - El puente mantiene los identificadores de agentes y los libera en su efecto de limpieza.
- El host libera el contexto raíz, propietario de todas las entradas.
stopCordisHost() es idempotente y está integrado en la secuencia de cierre del backend. Así se liberan los temporizadores e identificadores de archivos del motor sin esperar a que termine el proceso.
Identidad y persistencia de sesiones
La página del motor reserva un identificador opaco al crear una sesión. Si la persistencia está activada, su cabecera se guarda inmediatamente, de modo que incluso una sesión vacía sobrevive al reinicio. El puente lista sesiones almacenadas y activas, lee registros guardados mediante la API de persistencia validada de DSH y reanuda el agente con el mismo identificador al continuar. Los nuevos mensajes del usuario utilizan el constructor de mensajes identificados de DSH.
Eliminar una sesión cancela y libera su agente antes de retirar su artefacto. El adaptador local de eliminación JSONL valida las rutas del almacén y la sesión y rechaza enlaces simbólicos. Los backends de persistencia personalizados sin soporte de eliminación devuelven un error en vez de afirmar que se eliminaron los datos.
La cancelación llega al agente nativo, a la solicitud del modelo y al trabajo de las herramientas. Desconectar el cliente cancela su turno; los mensajes terminados siguen siendo legibles. La reproducción del flujo almacenado en búfer está limitada y conserva las respuestas rápidas anteriores a la conexión del lector.
El motor del host es una función del modo individual con una sola réplica. Los despliegues de equipo no pueden montar su entorno JSONL local. Work aislado utiliza en su lugar los repositorios SQL existentes de tareas, ejecuciones, mensajes, aprobaciones y eventos.
Interfaz HTTP
| Método | Ruta | Finalidad |
|---|---|---|
GET | /api/cordis/health | Estado del puente; sin autenticación |
GET | /api/cordis/sessions | Listar sesiones |
POST | /api/cordis/sessions | Crear una sesión |
GET | /api/cordis/sessions/:id | Leer una sesión y sus mensajes |
DELETE | /api/cordis/sessions/:id | Terminar una sesión |
POST | /api/cordis/sessions/:id/messages | Enviar un mensaje y transmitir NDJSON |
POST | /api/cordis/sessions/:id/cancel | Cancelar el turno en curso |
GET | /api/cordis/agents | Listar agentes activos |
GET | /api/cordis/tools | Listar herramientas registradas |
Todas las rutas excepto /health requieren una sesión de administrador autenticada y responden 503 con el code CORDIS_DISABLED, CORDIS_STARTING o CORDIS_UNAVAILABLE mientras el puente no pueda atender solicitudes.

La página está en frontend/src/pages/CordisPage.tsx y se accede a ella en /cordis desde la barra lateral. Lista sesiones y herramientas registradas, crea sesiones y transmite un turno a la conversación. Si el puente está desactivado o no puede arrancar, muestra el motivo en lugar de una lista vacía, porque «sin sesiones» y «sin motor» parecerían lo mismo.
El cliente del navegador está en frontend/src/utils/api/cordisApi.ts. Solo se comunica con esta interfaz; no importa tipos del backend ni paquetes @deepseek-ai/*, por lo que el motor puede sustituirse sin cambiar el frontend. Se consume un turno con sendMessage(sessionId, text, { onChunk }); el cliente analiza el JSON delimitado por saltos de línea y admite fragmentos divididos entre lecturas de red.
Controles del chat del motor
La página del motor muestra Markdown, tablas y bloques de código con resaltado de sintaxis, con controles para copiar respuestas y código. Los prompts del sistema y el contexto de ejecución inyectado se agrupan en una sección Contexto de sesión contraída; no se presentan como mensajes escritos por el usuario. El razonamiento expuesto y la actividad de herramientas tienen secciones desplegables separadas, y los resultados siguen vinculados a la operación correcta después de recargar.
Elige un modelo real de proveedor en el editor. El selector utiliza los modelos locales y de plugins disponibles para el administrador autenticado, incluida la identidad del proveedor. Las selecciones de personas y agentes de Chat no son identificadores de modelo ni inyectan instrucciones en una conversación del motor. Las antiguas cabeceras de modelos de personas fallidos se ignoran como indicios del modelo predeterminado sin modificar el registro guardado.
Cada sesión tiene su propia opción Solo lectura o Escritura en el espacio de trabajo, aplicada por la política del sistema de archivos de DSH y el límite canónico del espacio de trabajo del puente. El editor muestra su ámbito. Los ajustes se guardan como eventos nativos de sesión y sobreviven al reinicio; no pueden cambiarse durante un turno activo.
Una solicitud nativa de elevación aparece como tarjeta Permitir una vez / Rechazar asociada a la operación. La aprobación solo se aplica a esa solicitud y deja intacto el modo permanente de permisos. Las solicitudes obsoletas o canceladas no se pueden aprobar, y las llamadas de Chat sin interfaz rechazan las preguntas que no pueden mostrar. El puente no ofrece acceso ilimitado al host.
Los endpoints adicionales para administradores son:
| Método | Ruta | Finalidad |
|---|---|---|
GET | /api/cordis/models | Modelos de proveedores disponibles y modelo real predeterminado actual |
PATCH | /api/cordis/sessions/:id/settings | Establecer el modelo o modo de permisos de esta sesión |
POST | /api/cordis/sessions/:id/approvals/:approvalId | Resolver una solicitud pendiente con allowed-once o rejected |
Usar el motor en Chat
Activa Acceso y políticas → Modelos de agentes CLI y Motor Cordis. Los administradores podrán seleccionar DeepSeek Harness en Chat. Cada solicitud recibe una nueva sesión transitoria del motor que contiene la conversación suministrada para esa solicitud Chat. La base de datos habitual de Chat sigue siendo la referencia; conversaciones independientes, bifurcaciones y reintentos no pueden compartir un historial invisible del motor. El registro transitorio se elimina al terminar o cancelar y no aparece en la página del motor.
La composición estándar de proveedores también muestra opciones DeepSeek Harness · modelo (proveedor) en el grupo Agentes. Sus identificadores guardados encapsulan la misma ruta cualificada que la página del motor: dsh:lwui:ollama:<model> o dsh:lwui:plugin:<plugin>:<model>, con cada componente del proveedor codificado porcentualmente. Una conexión DSH nativa local opcional añade opciones dsh:native:<provider>:<model> del catálogo activo de esa instancia. Reutilizan la configuración y las credenciales del proveedor nativo. Instala su paquete independiente Apache-2.0 desde libre-webui/dsh-native-provider, o prepara un bundle desde la distribución de Libre WebUI. Ambas vías usan el nombre @libre-webui/dsh-native-provider y conservan las claves del proveedor en DSH nativo. La conexión requiere el mismo host Unix y la misma cuenta del sistema operativo, con un socket Unix privado; no puede aislar aplicaciones que comparten esa cuenta. Solo expone inferencia de modelos, sin sesiones de agentes ni ejecución de herramientas nativas. Consulta la guía de configuración para instalar, reiniciar perfiles, actualizar y eliminar. Si faltan la conexión o el modelo elegido, la solicitud falla sin cambiar de proveedor. Las llamadas nativas también aparecen en Uso del proveedor, con el modelo seleccionado, tokens reportados, latencia y estado del resultado. El perfil base dsh conserva el modelo predeterminado de la composición en ejecución. Las composiciones de adaptadores personalizados exponen ese perfil base sin anunciar cambios de proveedor de Libre WebUI no admitidos.
Los títulos y resúmenes de razonamiento resuelven una selección DSH hacia su proveedor subyacente y realizan una solicitud directa de texto sin herramientas ni sesión de agente. Las solicitudes del perfil base leen los valores predeterminados del motor activo, incluidas las anulaciones de la entrada del puente, en vez de adivinarlos a partir del catálogo actual. Los adaptadores personalizados necesitan un modelo de tareas Ollama o de plugin configurado explícitamente para estas funciones. Un proveedor seleccionado no disponible produce el fallo normal o la vista previa local del título; no provoca una solicitud a otro proveedor.
La solicitud utiliza los ajustes y credenciales del proveedor del administrador autenticado. Nunca se eligen implícitamente las credenciales de otro administrador. El espacio Cordis configurado sigue siendo el predeterminado; Chat no lo sustituye por el directorio personal del usuario del servidor.
Work aislado
Con Cordis activado, Work ofrece un control Motor separado con las opciones Libre WebUI y DeepSeek Harness. El selector mantiene los nombres de modelos e identidades de proveedores habituales. Internamente, la selección DSH se guarda como dsh:<model> para proveedores gestionados por LWUI. Las opciones DSH nativas guardan en cambio providerType: dsh, el identificador exacto del proveedor nativo y el identificador original del modelo. Siguen aplicándose las comprobaciones habituales de acceso y soporte de herramientas; las credenciales nativas exigen además un administrador activo.
Cada ejecución crea un bucle de agente DSH aislado en memoria. Su adaptador recibe la conversación Work actual, metadatos del proveedor, imágenes y esquemas de herramientas. Las implementaciones de las herramientas solo esperan los resultados devueltos por Work; no pueden leer archivos ni iniciar procesos del host.
Work sigue siendo responsable de validar argumentos, solicitar aprobaciones, ejecutar herramientas en el entorno del espacio de trabajo, guardar resultados y estado de reproducción del proveedor en SQL, aplicar presupuestos y publicar eventos. Una herramienta rechazada produce el resultado normal de denegación. La cancelación libera DSH y sigue la limpieza de contenedores existente en Work. Tras recuperar un worker, un controlador DSH nuevo recibe el contexto Work restaurado y no repite efectos secundarios de herramientas ya completados.
La integración Work no necesita la composición del motor del host ni el almacén de sesiones JSONL. Sigue las reglas de ejecución y despliegue Docker/Kubernetes de Work, incluidos los requisitos de persistencia compartida del modo equipo.
Límite de seguridad
La página del motor y el agente Chat del host son exclusivos de administradores. Las sesiones del motor son una consola administrativa compartida, incluidos sus prompts del sistema, no un espacio por usuario. Las cuentas ordinarias no pueden leerlas, crearlas, modificarlas ni cancelarlas mediante la API.
Las herramientas de sistema de archivos incluidas restringen lecturas y escrituras al espacio configurado mediante destinos canónicos del sistema de archivos, incluida la resolución de enlaces simbólicos. Los directorios de trabajo específicos de sesión deben permanecer dentro de ese espacio. Siguen aplicándose la política nativa de modificación de DSH y las aprobaciones puntuales del motor. Los plugins de composición instalados por el operador son código de servidor de confianza y pueden otorgar capacidades adicionales. Las aprobaciones del motor son independientes del flujo de aprobación y ejecución en contenedores de Work.
El controlador DSH de Work es independiente: no monta plugins del sistema de archivos, shell o persistencia del host y solo puede ejecutar mediante la autorización y el aislamiento existentes de Work. Los proveedores remotos siguen siendo opcionales y utilizan la ruta configurada de la cuenta seleccionada.