Retrato de Santiago Fernández

CubeLabs

IA · Fintech · Agentes · Automatización
🧠 IA

DeepSeek Harness: Agentes de IA donde Todo es un Plugin

El harness de agentes de DeepSeek AI que convierte cada capacidad — modelo, herramienta, memoria, agente — en un plugin intercambiable.

⏱ 40 min 📊 Intermedio 📝 10 lecciones

DeepSeek Harness (comando dsh) es el framework de agentes open-source de DeepSeek AI que explotó en GitHub con 144.000+ estrellas y la arquitectura "todo es un plugin". No hay núcleo privilegiado: el adaptador de modelos, el registro de herramientas, el log de sesión y el propio bucle del agente son plugins montados sobre Cordis, un framework de composición con contexto, servicios y eventos tipados. Eso significa que puedes extenderlo desde configuración: añadir un proveedor de IA, crear una herramienta nueva con unas pocas líneas de TypeScript, o publicar tu propio bundle. En este curso aprenderás a instalarlo, a manejar la Web UI y la CLI, a usar el Python SDK, a entender Cordis y a escribir y publicar tu primer plugin con herramientas propias.

DeepSeek Harness - deepseek-ai
DeepSeek Harness · deepseek-ai · GitHub Trending

Contenido

  1. ¿Qué es DeepSeek Harness?
  2. Instalación y primer arranque
  3. Tu primera sesión en la Web UI
  4. Configurar modelos y proveedores
  5. CLI, perfiles y composición
  6. El Python SDK
  7. Arquitectura: Cordis y "todo es un plugin"
  8. Tu primer plugin
  9. Construir una herramienta con defineTool
  10. Publicar e instalar plugins como bundles

1. ¿Qué es DeepSeek Harness?

DeepSeek Harness (comando dsh) es un harness de agentes open-source desarrollado por DeepSeek AI, distribuido con licencia MIT. Un harness es la infraestructura que envuelve a un modelo de lenguaje y le da piernas: acceso a archivos, shell, herramientas, memoria de sesión, delegación a subagentes y un bucle de razonamiento. Es el mismo concepto que está detrás de otros agentes de terminal, pero con una diferencia radical en el diseño.

Su arquitectura se resume en una frase: everything is a plugin (todo es un plugin). El adaptador de modelos, el registro de herramientas, el log de sesión, la política de permisos e incluso el propio bucle del agente son plugins que se montan sobre Cordis, un framework de composición cuyas ideas se describen en el paper A Programming Paradigm for Spatiotemporal Composability. No existe un núcleo que haya que parchear: para extender dsh, montas un plugin junto a los demás.

DatoValor
Repodeepseek-ai/deepseek-harness
LenguajeTypeScript (runtime npm) + Python SDK
Estrellas144.000+
LicenciaMIT
EstadoDeveloper preview — hay cambios que rompen compatibilidad
Framework subyacenteCordis (vendorizado dentro del repo)
Web UIhttp://127.0.0.1:3080 por defecto
⚠️ Developer preview

El proyecto está en developer preview e itera rápido. La documentación oficial avisa: THERE WILL BE COMPATIBILITY-BREAKING CHANGES. Úsalo para experimentar y construir, pero no lo trates todavía como una plataforma estable en producción.

💡 Tip

Si ya conoces agentes como OpenClaw o DeerFlow, piensa en dsh como el que lleva la filosofía de plugins hasta el extremo: cada parte del producto es reemplazable desde configuración, sin tocar código del framework.

2. Instalación y primer arranque

La forma más rápida de probar dsh es desde npm con npx, sin clonar el repositorio. Solo necesitas Node.js instalado.

npx @deepseek-ai/dsh web

El comando arranca la Web UI y la sirve en http://127.0.0.1:3080 por defecto. El proceso usa el directorio desde el que lo lanzas como ubicación por defecto del filesystem.

Instalación paso a paso desde source

Si quieres la versión de desarrollo (por ejemplo para escribir plugins en el propio repo), clona y compila:

1

Clonar el repositorio

git clone https://github.com/deepseek-ai/deepseek-harness.git && cd deepseek-harness

2

Instalar dependencias

pnpm install — el repo es un monorepo pnpm; necesitas pnpm instalado.

3

Compilar paquetes y frontend

pnpm run build — las ejecuciones en producción requieren artefactos compilados.

4

Arrancar la Web UI desde el repo

pnpm dsh web — ejecuta la entrada TypeScript y reenvía los argumentos.

$ pnpm dsh web
[dsh] Web UI listening on http://127.0.0.1:3080
💡 Tip

Los perfiles web y headless se auto-inicializan en el primer uso a partir de plantillas incluidas. No necesitas crear nada a mano para empezar.

3. Tu primera sesión en la Web UI

Abre http://127.0.0.1:3080. Una Web UI recién arrancada no tiene ningún workspace seleccionado, así que el primer paso es elegir uno.

Configurar el modelo y el workspace

Ejecutar una tarea

Inicia una sesión y escribe algo como:

Resume este repositorio e identifica sus paquetes principales.

El agente puede leer y editar archivos del workspace, ejecutar comandos, delegar trabajo y mantener un plan. La Web UI pide aprobación antes de las operaciones que lo requieran según la política de permisos activa (por defecto, el perfil web pide confirmación para acciones sensibles).

Acción del agente¿Pide aprobación?
Leer archivos del workspaceNo, en general
Editar archivosSegún política de permisos
Ejecutar comandos de shellSí, según la política activa
Delegar a subagentesDepende de la composición
⚠️ Ojo con el workspace

El agente puede modificar cualquier cosa dentro del workspace que selecciones. Para pruebas, usa un directorio aislado y descartable, no tu carpeta personal.

4. Configurar modelos y proveedores

dsh no te ata a DeepSeek. Además de la tarjeta oficial, puedes añadir proveedores del catálogo (Anthropic, OpenAI…) y proveedores personalizados compatibles con OpenAI para gateways corporativos o servidores self-hosted.

Proveedores del catálogo

Elige Add provider, selecciona un proveedor como Anthropic u OpenAI, introduce su API key y guarda. El catálogo instalado aporta endpoint, protocolo y lista de modelos. Los proveedores con autenticación nativa (Bedrock, Vertex, Azure, Codex) necesitan sus credenciales nativas: credenciales AWS, proyecto ADC, api-version o OAuth respectivamente.

Proveedor personalizado (OpenAI-compatible)

Elige Add a custom provider para un endpoint propio. Necesitas un Provider ID en minúsculas, base URL, protocolo API, credencial y al menos un modelo.

# $DSH_HOME/settings.yaml (ejemplo de proveedor personalizado con visión)
llm-pi-ai:
  providers:
    my-gateway:
      apiKeyEnv: GATEWAY_API_KEY
      api: openai-completions
      baseURL: https://gateway.example/v1
      models:
        - id: legacy-chat
        - id: vision-preview
          input: [text, image]
CampoQué hace
apiKeyEnvVariable de entorno con la credencial
apiProtocolo: openai-completions, etc.
baseURLEndpoint del gateway
modelsLista de modelos; input: [text, image] habilita imagen para ese modelo
defaultInputFallback de modalidades para modelos no descritos
💡 Tip

Las claves se guardan en $DSH_HOME/.credentials.yaml y son write-only: la UI recibe solo un descriptor redactado, nunca el secreto literal. Los ajustes guardan únicamente la referencia a la credencial.

⚠️ Errores típicos

MISSING_CREDENTIAL: guarda la clave por la página Models o define la variable de entorno referenciada. UNKNOWN_MODEL: selecciona un modelo configurado o añade el que falte al proveedor personalizado. 401 al listar modelos: la detección llama a GET /models; si tu endpoint no lo expone, introduce los modelos a mano.

5. CLI, perfiles y composición

El comando dsh es el lanzador de perfiles: composiciones ordenadas de capas de bundles de plugins bajo tus propios overlays. Los modos principales:

ComandoPropósito
dsh --profile <nombre>Arranca el perfil nombrado bajo $DSH_HOME/profiles/<nombre>
dsh webAlias de --profile web: Web UI con servidor
dsh --profile headless "job"Ejecuta una sesión nueva, imprime la respuesta final y sale (sin servidor)
dsh plugin --profile <n> <pnpm args>Gestiona los plugins del perfil reenviando a pnpm
dsh --dump-configMuestra el árbol compuesto sin arrancar nada

Cómo se compone el árbol de plugins

Las capas se aplican sobre una lista vacía en este orden, y las capas posteriores ganan por fila:

  1. Cada bundle en el orden de dsh.profile.bundles (empezando por @deepseek-ai/dsh-base).
  2. El cordis.patch.yml del propio perfil.
  3. El cordis.patch.yml de nivel home ($DSH_HOME) — preferencias locales compartidas por todos los perfiles.
  4. Cada overlay --patch <path> en orden de argv.

Los bundles base son la primera capa de todo perfil: dsh-base (adaptadores de modelo, herramientas, persistencia, sandbox, permisos, ajustes, credenciales, telemetría), dsh-web-app (aplicación de navegador) y dsh-headless (runner de una sola pasada sin servidor).

💡 Tip

dsh --profile web --dump-config imprime el árbol que tu máquina arranca de verdad. Cualquier fila que imprima puede ser reemplazada por un patch propio — así se extiende dsh sin tocar el framework.

⚠️ Un patch reemplaza, no fusiona

Un patch que sobrescribe una fila por id sustituye todo su config, no hace merge de claves. Si sobrescribes una fila de dsh-base, debes repetir todas las claves que esa fila necesite, no solo la que cambias.

6. El Python SDK

Si prefieres Python a la Web UI, el SDK publicado deepseek-harness-sdk permite lanzar agentes desde tu propio programa. El runtime empaquetado del SDK no necesita Node.js del sistema.

Instalar y probar el ejemplo

git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
python -m venv .venv
. .venv/bin/activate
python -m pip install deepseek-harness-sdk
export DEEPSEEK_API_KEY=sk-tu-clave
python examples/jsonrpc-agent/minimal.py \
  --workspace /ruta/absoluta/workspace \
  --session-root /ruta/absoluta/sessions \
  --session-id example-001 \
  "Inspecciona el repositorio y arregla los tests que fallan."

El SDK en tu propio programa

from pathlib import Path

from deepseek_harness import DeepSeekHarness

config = Path("examples/jsonrpc-agent/minimal.cordis.yml").resolve()
workspace = Path("/ruta/absoluta/workspace").resolve()
sessions = Path("/ruta/absoluta/sessions").resolve()

with DeepSeekHarness(
    provider="deepseek-official",
    model="deepseek-v4-flash",
    max_tokens=49_152,
    cwd=str(workspace),
    session_root=str(sessions),
    cordis=str(config),
) as harness:
    result = harness.run(
        "Inspecciona el repositorio y arregla los tests que fallan.",
        session_id="example-001",
    )

print(result.final_response)

DeepSeekHarness arranca el runtime perezosamente y lo reutiliza hasta que el context manager sale. Reutilizar el mismo harness y el mismo session_id conserva el proceso Bash de la sesión: directorio de trabajo, variables exportadas y funciones de shell. Para una tarea independiente usa un session id nuevo.

PropiedadValor del ejemplo minimal
System promptDSH_SYSTEM_PROMPT o "You are a helpful software engineer assistant."
Modelo--model → DSH_MODEL → deepseek-v4-flash
Herramientas visiblesbash persistente y str_replace_editor
Timeout de bash300 segundos
PersistenciaJSONL sin comprimir en DSH_SESSION_ROOT
Sandboxdanger-full-access — solo en checkout desechable o contenedor
⚠️ danger-full-access

La composición minimal usa danger-full-access: Bash y el editor pueden modificar cualquier ruta visible por el proceso. Ejecútala solo dentro de un checkout desechable o un contenedor, nunca contra tu sistema personal.

7. Arquitectura: Cordis y "todo es un plugin"

Para escribir plugins necesitas las cinco ideas de Cordis, el framework vendorizado debajo de dsh:

Modo¿Espera?Orden de dispatch¿Devuelve valor?
emitNoOrden de registroNo
waterfallNoOrden de registroSí
parallelSíTodos en paraleloNo
serialSíOrden de registroSí

Paquetes core y ciclo de vida del agente

El bucle se organiza en steps (una petición de modelo + las herramientas que llama) y turns (cero o más steps, desde que se reclama la primera entrada hasta que no queda nada pendiente). Los eventos del log de sesión (turn/start, step/start, assistant/*, tool/*) son hechos durables; los eventos agent/* son puntos de extensión en vivo (inbox, status, request, steering).

PaqueteEs dueño deClave ctx
core/sessionLog append-only SessionEventctx.sessions
core/system-promptEnsamblado de secciones de prompt y esquemasctx.systemPrompt
core/toolsRegistro de herramientas con scope y pipeline de ejecuciónctx.tools
core/agent + agent-loopInterfaz Agent, registro y driver por defectoctx.agents, ctx.agentLoop
llm/llmVocabulario de mensajes/stream y el seam de adaptadoresctx.llm
💡 Tip

¿Dónde va cada cosa nueva? Añadir un proveedor de modelo → registrar su adaptador en ctx.llm. Añadir una capacidad visible por el modelo → registrarla en ctx.tools. Interceptar una petición o un turno → escuchar sus eventos agent/* o tools/*. Los eventos son el punto de extensión; los métodos de servicio, la llamada directa.

8. Tu primer plugin

Un plugin de dsh es un módulo TypeScript que exporta una función apply. El framework la llama al cargar el plugin y le pasa un contexto ctx con el que registras capacidades. Eso es toda la configuración.

Crear el proyecto scratch

Desde la raíz del checkout del repositorio:

1

Crear carpetas

mkdir -p scratch-plugin/src

2

Escribir el plugin

Crea scratch-plugin/src/my-plugin.ts:

3

Registrarlo en cordis.yml

Un overlay Web que inserta el plugin local (la ruta debe ser absoluta).

4

Arrancar con el overlay

pnpm dsh web --patch ./scratch-plugin/cordis.yml y abrir http://127.0.0.1:3080: el terminal imprime [hello-plugin] plugin loaded!.

// scratch-plugin/src/my-plugin.ts
import type { Context } from '@deepseek-ai/cordis'

export const name = 'hello-plugin'

export function apply(ctx: Context) {
  // Las dependencias requeridas están listas antes de que apply corra.
  console.log('[hello-plugin] plugin loaded!')
}
# scratch-plugin/cordis.yml — overlay Web que inserta el plugin local
- insert:
    - id: hello
      name: '/ruta/absoluta/al/deepseek-harness/scratch-plugin/src/my-plugin.ts'

Limpieza automática y dependencias

Cualquier cosa registrada con ctx — listeners, herramientas, timers — se limpia al descargar el plugin; no necesitas removeListener manual. Para un recurso con limpieza explícita (una conexión de red), usa ctx.effect() y devuelve su disposer. Si tu plugin consume otro servicio como tools o llm, decláralo en inject y Cordis esperará a que exista:

import type { Context } from '@deepseek-ai/cordis'

export const name = 'my-tool-plugin'
export const inject = ['tools']

export function apply(ctx: Context) {
  // ctx.tools está listo aquí.
  ctx.tools.register(/* ... */)
}

Además de la forma función, un plugin puede usar forma objeto ({ name, inject, apply }) o forma clase (subclase de Service con static inject y constructor que llama a super(ctx, 'miServicio')). La forma función basta en la mayoría de los casos; usa la clase cuando tu plugin preste un servicio a otros plugins.

9. Construir una herramienta con defineTool

Las herramientas son la forma en que el modelo toca el mundo real. Vamos a añadir una herramienta greet a la Web UI, reutilizando el directorio scratch-plugin de la lección anterior.

El plugin de la herramienta

// scratch-plugin/src/my-plugin.ts
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'greet-tool'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: 'Saluda a alguien por su nombre.',
    parameters: {
      name: { type: 'string', required: true, description: 'El nombre a saludar' },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute(args) {
      return `Hello, ${args.name}!`
    },
  }))
}

inject hace que Cordis espere al registro de herramientas. defineTool infiere y valida args a partir de parameters; execute devuelve el valor canónico declarado por output.schema, y output.render convierte ese valor en contenido visible por el modelo.

Probar la herramienta

1

Arrancar con el overlay

pnpm dsh web --patch ./scratch-plugin/cordis.yml

2

Abrir la Web UI

http://127.0.0.1:3080

3

Pedir que la use

Escribe: Usa la herramienta greet para saludar a Ada. El modelo puede llamar a greet y recibe Hello, Ada! como resultado de la herramienta.

Configuración con Schemastery

Para aceptar configuración vía cordis.yml, exporta un tipo Config y un esquema del mismo nombre (Schemastery) con los valores por defecto directamente en los campos:

import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'

export const name = 'my-plugin'

export interface Config {
  greeting: string
  maxRetries: number
  verbose?: boolean
}

export const Config: Schema<Config> = Schema.object({
  greeting: Schema.string().default('Hello'),
  maxRetries: Schema.number().default(3),
  verbose: Schema.boolean().default(false),
})

export function apply(ctx: Context, config: Config) {
  console.log(config.greeting)  // Valor del usuario o default del esquema.
}

Al cargar el plugin, Cordis usa el esquema exportado para validar la configuración y rellenar los defaults. No exportes un objeto plano como Config: no implementa la interfaz Standard Schema que Cordis necesita.

💡 Tip

El esquema es la fuente de verdad para la UI de configuración del plugin: los defaults del esquema son los que ve el usuario, y los valores del cordis.yml los sobrescriben.

10. Publicar e instalar plugins como bundles

Hasta ahora cargamos plugins locales con overlays --patch. Para distribuirlos, dsh usa dos conceptos definidos en package.json bajo la clave dsh:

Un bundle es lo que escribes y distribuyes; un profile es lo que el usuario arranca con dsh --profile. Nada es ambas cosas.

Estructura de un bundle

hello-plugin/
├── package.json       # declara dsh.bundle
├── cordis.patch.yml   # capa que se aplica al listar el bundle en un perfil
└── index.js           # módulos de plugin referenciados por las filas del patch
// hello-plugin/package.json
{
  "name": "dsh-hello-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "index.js",
  "files": ["index.js", "cordis.patch.yml"],
  "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
# hello-plugin/cordis.patch.yml — la fila referencia el paquete por nombre
- insert:
    - id: hello
      name: dsh-hello-plugin

Instalar en un perfil

dsh plugin reenvía a pnpm dentro del directorio del perfil, así que cualquier verbo pnpm funciona. El primer uso inicializa el perfil con @deepseek-ai/dsh-base como primer bundle y añade el tuyo:

dsh plugin --profile demo add ./hello-plugin
dsh --profile demo --dump-config   # muestra la capa "# == dsh-hello-plugin"
dsh --profile demo

dsh plugin --profile demo remove dsh-hello-plugin elimina tanto la dependencia como la capa.

Instalar desde GitHub: el catch del build

También puedes instalar directo de un host git: dsh plugin --profile demo add github:you/hello-plugin. Pero una instalación git trae fuentes, no artefactos compilados: si tu paquete es TypeScript, llegará sin su salida lib/ y fallará al cargar. Necesitas dos cosas:

  1. El autor debe incluir un script prepare que compile los entry points publicados de forma autónoma.
  2. El usuario debe permitir el build en pnpm-workspace.yaml del perfil: allowBuilds: { dsh-hello-plugin: true }, porque pnpm ≥ 10 se niega a ejecutar scripts prepare de dependencias git sin permiso explícito.
⚠️ allowBuilds = ejecutar código en tu máquina

Esa autorización es permiso para ejecutar el código del paquete en tu máquina durante la instalación, fuera del sandbox del agente. Solo permite paquetes cuya fuente confíes, y fija un commit (github:you/hello-plugin#<sha>) para que un push posterior no cambie silenciosamente lo que se ejecuta.

💡 Tip

¿No quieres pedir permiso de build a tus usuarios? Distribuye artefactos ya compilados: publica a npm con lib/ construido en pnpm publish, o genera un tarball con pnpm pack e instala con dsh plugin add ./hello-plugin-0.1.0.tgz. Así no se necesita ninguna autorización de build.

Preguntas frecuentes

¿Necesito Node.js sí o sí?

Para la Web UI y la CLI sí: el comando npx @deepseek-ai/dsh web requiere Node.js. Pero el Python SDK (deepseek-harness-sdk) incluye un runtime empaquetado que no necesita Node.js del sistema — solo Python 3.10+.

¿Puedo usar modelos que no sean de DeepSeek?

Sí. El catálogo incluye proveedores como Anthropic u OpenAI (con sus credenciales nativas en el caso de Bedrock, Vertex, Azure o Codex), y puedes añadir cualquier proveedor personalizado compatible con OpenAI apuntando a tu propio gateway o servidor self-hosted.

¿Dónde se guardan mis API keys?

En $DSH_HOME/.credentials.yaml, con formato write-only: la UI guarda una referencia redactada y nunca muestra el secreto literal. Los ajustes de settings.yaml pueden referenciar variables de entorno con apiKeyEnv.

¿Es estable para producción?

El proyecto está en developer preview y avisa explícitamente de cambios que rompen compatibilidad. Es ideal para experimentar y construir agentes propios, pero no lo uses todavía como plataforma crítica sin plan de contingencia ante breaking changes.