DeepSeek Harness (dsh): Cómo instalé el agente de codificación de código abierto de DeepSeek, lo integré en mi aplicación de chat y lo evalué

Categoría
IA y LLM Local
Publicado
18 agosto 2026
Por
Jacob Lloyd — escrito con ayuda de IA, después del proyecto
Tiempo de lectura
16 min de lectura

En palabras simples: DeepSeek lanzó un 'agente de codificación' gratuito y de código abierto: un programa que lee tu proyecto, edita archivos y ejecuta comandos por ti, similar a Claude Code. Este artículo muestra cómo lo instalé en mi PC Linux, hice que se iniciara automáticamente, lo conecté a mi aplicación de chat y lo probé con pequeñas tareas de codificación reales, tanto con los modelos en la nube de DeepSeek como con un modelo que corre en mi propio hardware. Funcionó, fue barato y tiene algunas aristas.

El 13 de agosto de 2026, DeepSeek publicó DeepSeek Harnessdsh — un agente de codificación con licencia MIT basado en la idea de que todo es un plugin: el adaptador de modelo, las herramientas, el sandbox, incluso el bucle del agente. En dos días alcanzó 95.000 estrellas en GitHub. Lo instalé en mi equipo de casa esa misma semana, le di un lugar junto a Reasonix, mi agente de codificación existente, dentro de DisPatch, lo apunté tanto a los modelos en la nube de DeepSeek como a un modelo en mi propio equipo con GPU, y lo ejecuté con una pequeña suite de tareas. Esto es lo que implicó, lo que costó y dónde mordió.

Prueba rápida antes de seguir leyendo: pasa el ratón por encima y haz clic. dsh creó esta versión en píxel art del logo del sitio a partir de una breve descripción — JavaScript puro, sin dependencias — y la historia completa de cómo fue está más abajo.

tl;dr

  • Qué es: un agente de clase Claude Code (lee/edita archivos, ejecuta shell, mantiene un plan, lanza subagentes) distribuido como paquete npm. Dos modos: una interfaz web en 127.0.0.1:3080 y un modo headless de una sola pasada que imprime una respuesta y sale — pensado para scripts y otros agentes.
  • Qué cuesta: el software es gratuito. Todo mi benchmark de 5 tareas costó unos 3¢ en V4-Flash y unos 7¢ en V4-Pro a tarifas máximas; nada en el modelo local.
  • Qué necesitas: Node.js 22.19+/24, una clave de API de DeepSeek o cualquier servidor compatible con OpenAI (usé ambos).
  • Qué obtienes: dsh --profile headless "fix the failing test" desde cualquier carpeta de proyecto, una interfaz de navegador como servicio en segundo plano, cambio de modelo editando un archivo YAML, y — en mi caso — un bot "DeepSeek Harness" en mi aplicación de chat con un botón Run.
  • El resultado: 15/15 aciertos en V4-Flash, V4-Pro y una Gemma-4-26B local, 2–18 s por tarea. Vista previa para desarrolladores: el README advierte en mayúsculas que las cosas se romperán entre versiones, y me encontré con algunos de los bordes afilados que se enumeran a continuación.

La ruta, de lo básico a lo avanzado

Los pasos 1–3 te dan un agente funcional en diez minutos. Los pasos 4–5 son lo que añadí después para que otro software — y otros agentes — puedan llamarlo.

Lo que obtienes al final

Lo que realmente uso es el modo headless. Desde dentro de un directorio de proyecto:

$ cd ~/Projects/dsh-playground
$ dsh --profile headless "Create fizz.py that prints FizzBuzz for 1..15 and run it; reply with the program output only."

1
2
Fizz
4
Buzz
…
FizzBuzz
$ echo $? 0

Seis segundos: archivo creado, programa ejecutado, respuesta impresa, código de salida 0. No escribe nada fuera de la carpeta desde la que lo iniciaste (modo de permisos predeterminado workspace-write), imprime solo el mensaje final en stdout y persiste cada ejecución para que puedas abrirla más tarde en la interfaz web y leer exactamente lo que hizo: cada llamada a herramienta, cada recuento de tokens.

La interfaz del navegador es el diseño familiar de 2026: sesiones a la izquierda, chat al centro, una página de ajustes para los modelos, con un buen hábito: la clave de API se pega en la página de ajustes, nunca en un archivo de configuración. La mía funciona como un servicio de usuario de systemd y está integrada en mi aplicación de chat junto a Reasonix:

La aplicación de chat DisPatch con el bot 'DeepSeek Harness' seleccionado: la interfaz web de dsh incrustada en el panel principal mostrando una sesión finalizada «Crear y ejecutar hello.py» con sus pasos Think, Write y Bash y la respuesta; debajo hay una barra de estado con «En ejecución», un desplegable de Modelo con DeepSeek-V4-Flash y botones Iniciar/Reiniciar/Detener
La interfaz web de dsh dentro de DisPatch, que muestra una sesión finalizada con su trayectoria de pasos (Think → Write → Bash) y la línea de estadísticas por turno. La barra inferior controla la unidad de systemd y el modelo predeterminado.

Paso 1: instalar (dos minutos)

Un único paquete de npm. Mantengo un prefijo de Node privado para las herramientas de agente, de modo que nada acabe en el árbol del sistema — en una distro inmutable como Bluefin es, de todos modos, el único lugar sensato para ello —, pero una instalación global normal es el mismo comando:

npm install -g @deepseek-ai/dsh
dsh --version          # 0.1.0-rc.7 at the time of writing
dsh web                # starts the UI, prints http://127.0.0.1:3080

Abre la URL, ve a Ajustes → Modelos, pega tu clave de DeepSeek y guarda. Eso escribe ~/.dsh/.credentials.yaml (modo 0600) y la ruta del modelo funciona de inmediato, sin reiniciar. Para automatizarlo, el archivo es un mapeo YAML simple — DEEPSEEK_API_KEY: sk-… — y yo escribí el mío a partir del archivo de entorno que ya leen mis otros servicios, de modo que el secreto existe en un lugar más, pero nunca en un historial de shell ni en un archivo de unidad.

Dos cosas que conviene saber antes de continuar:

  • La telemetría está desactivada por defecto (DSH_TELEMETRY_MODE sin definir = desactivada). Comprobé la configuración incluida, no el texto de marketing; el exportador OTLP existe, simplemente no está activado.
  • El sandbox es real pero limitado. workspace-write confina las escrituras al directorio desde el que lo lanzaste. Las lecturas no están confinadas — la documentación lo dice claramente —, así que no lo lances desde tu directorio personal para una tarea real, y no lo apuntes a carpetas que contengan secretos.

Paso 2: ejecutarlo como un servicio

La interfaz web es un proceso de Node de larga duración; quería que se iniciara al iniciar sesión, sin terminal, solo con loopback. Una unidad --user de systemd lo consigue:

# ~/.config/systemd/user/dsh-web.service
[Unit]
Description=DeepSeek Harness web UI (dsh web) on 127.0.0.1:3080
After=network.target

[Service]
WorkingDirectory=%h
Environment=DSH_HOME=%h/.dsh
Environment=DSH_PERMISSION_MODE=workspace-write
ExecStart=/usr/local/bin/dsh web --host 127.0.0.1 --port 3080   # `which dsh`
Restart=on-failure
RestartSec=3

[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user enable --now dsh-web.service
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3080/   # 200

WorkingDirectory es la raíz del espacio de trabajo predeterminada de la interfaz web, pero la interfaz aún te obliga a elegir un espacio de trabajo explícitamente antes de dejarte escribir. Buen valor por defecto. La CLI también rechaza --host 0.0.0.0 — los autores califican la vinculación a todas las interfaces como «intencionalmente aún no compatible», y como la interfaz no tiene autenticación alguna, estoy de acuerdo. ¿Quieres usarla desde otra máquina? Pon un proxy inverso con autenticación delante, o accede al navegador del propio host mediante tu vía de escritorio remoto (que es lo que yo hago).

Paso 3: modelos: un archivo YAML, recargado en caliente

~/.dsh/settings.yaml contiene el modelo por defecto y cualquier proveedor adicional, y dsh lo vuelve a leer para la siguiente solicitud: sin reiniciar ni volver a iniciar sesión. Este es el mío:

agent-default-model:
  provider: deepseek-official
  model: deepseek-v4-flash          # or deepseek-v4-pro

llm-deepseek:
  reasoningEffort: high             # off | low | high | max

# A local OpenAI-compatible server (mine is llama.cpp-based on a GPU rig).
llm-pi-ai:
  providers:
    buildpc:
      displayName: StudioForge (GPU rig)
      apiKeyEnv: STUDIOFORGE_PLACEHOLDER_KEY   # a reference, not a value
      api: openai-completions
      baseURL: http://my-gpu-rig:1234/v1      # your server; mine sits on the tailnet
      defaultContextWindow: 65536
      models:
        - id: unsloth/gemma-4-26B-A4B-it-qat-GGUF/gemma-4-26B-A4B-it-qat-UD-Q4_K_XL
          name: Gemma 4 26B-A4B (rig)

Dos detalles que me costaron diez minutos cada uno: apiKeyEnv es una referencia que se resuelve desde .credentials.yaml o el entorno, nunca un valor literal; y un servidor local sin clave todavía necesita que se referencie alguna credencial, porque el cliente compatible con OpenAI exige un token de portador. Un valor de marcador de posición en .credentials.yaml lo satisface. Y el ID del proveedor (buildpc) es permanente una vez que las sesiones lo referencian; para renombrarlo, añade uno nuevo.

Como el archivo es toda la interfaz, «cambiar dsh al modelo local» es una edición de dos líneas que cualquier script —o cualquier otro agente— puede hacer. Mi agente principal reorienta el harness hacia un modelo local gratuito para las tareas rutinarias y lo devuelve a V4-Pro para los problemas difíciles sin tocar nada más.

Paso 4: junto a Reasonix en DisPatch

Ya tenía a Reasonix viviendo dentro de DisPatch como un pseudo-bot: haces clic en él y el panel de chat se convierte en una terminal ejecutando su TUI sobre una PTY. Quería dsh a su lado. La pega: el paquete oficial no tiene TUI. Existen plugins de terceros de «TUI estilo Claude Code» en npm, pero tenían cuatro días de antigüedad con dependencias workspace:* rotas, y un paquete sin revisar con acceso a shell no va a un servidor familiar. Así que el panel de dsh está construido a partir de lo que el paquete oficial sí te da:

  • la interfaz web embebida en un iframe (no envía cabeceras anti-enmarcado), que se muestra solo cuando el navegador puede alcanzar el loopback del host — un teléfono en la tailnet recibe una explicación sencilla en su lugar;
  • Iniciar / Reiniciar / Detener para la unidad de systemd, además del estado de salud;
  • un desplegable de Modelo que reescribe agent-default-model en ese archivo YAML;
  • una pestaña de Trabajos headless: escribe una tarea, elige una carpeta dentro de home, pulsa Ejecutar. El servidor lanza dsh --profile headless "…" como una lista de argumentos fija (sin shell — la tarea es un elemento argv, así que una tarea que contenga ; rm -rf / es solo texto), un trabajo a la vez, y mantiene un historial corto con cada respuesta final.
La pestaña Trabajos headless en DisPatch: una caja de tarea, un campo de directorio de trabajo y tres tarjetas de trabajos terminados en ~/Projects/dsh-playground — «Hecho en 19s» (añade un docstring a fizz.py y vuelve a ejecutarlo), «Hecho en 5s» (crea primes.py, salida 2 3 5 7 11…), «Hecho en 5s» (lista los archivos y describe cada uno) — cada una mostrando la tarea y el texto de la respuesta
Trabajos headless desde la app de chat. Cada tarjeta es una ejecución de dsh --profile headless; la respuesta es lo último que dsh imprimió. Tres ejecuciones reales: 5 s, 5 s, 19 s.

Todo en ese panel está tras el desbloqueo de administrador de la app — un trabajo headless es ejecución de código arbitrario, y los dispositivos familiares bloqueados nunca saben que el panel existe. Si conectas dsh con algo propio, copia eso: trata «ejecutar una tarea» exactamente igual que tratas un shell.

dsh vs Reasonix, lado a lado

Ambos son agentes con forma de Claude Code que cobran por token en DeepSeek. Se diferencian en quién los hizo y qué superficie te ofrecen:

DeepSeek Harness (dsh)Reasonix
QuiénDeepSeek, oficial, MITDe terceros, estilo Claude Code
SuperficieWeb UI + one-shot headless; sin TUITUI de terminal (sesión interactiva en una PTY)
Llamarlo desde un scriptdsh --profile headless "…" — una respuesta, código de salidaDiseñado para un humano en el bucle; el scripting es incómodo
Cambio de modeloEditar settings.yaml, recarga en caliente; sin flag --modelNiveles en config.toml (flash/pro) + enrutamiento por skill
Modelos localesCualquier servidor compatible con OpenAI mediante un bloque de proveedorEl mismo trío: base URL, env de clave, ids de modelo
ExtensibilidadTodo es un plugin (modelo, herramientas, sandbox, bucle)Subagentes, skills, memoria por proyecto
En mi DisPatchiframe + controles de unidad + pestaña de trabajos headlessTerminal xterm.js sobre una PTY
MadurezVista previa para desarrolladores (rc.7), se rompe entre versionesv1.18, autoactualizable, configuración estable

En la práctica: Reasonix es lo que abro cuando voy a sentarme con el agente; dsh es a lo que mi otro software llama. Esa división explica por qué ambos se quedan.

El benchmark: 5 tareas, 3 modelos, 15/15

Nada científico: cinco tareas pequeñas que de verdad le daría a un agente de codificación, cada una en una carpeta temporal nueva, cada una verificada automáticamente (¿existe el archivo y se ejecuta? ¿pasan las pruebas sin tocar el archivo de prueba? ¿el renombrado no dejó ninguna referencia antigua?). El tiempo transcurrido es todo el proceso, incluido su prompt de sistema de ~7,500 tokens; los tokens provienen del registro de sesión de dsh.

TareaV4-FlashV4-ProGemma-4-26B (local, en el equipo)
Responder "PONG" (arranque + una llamada)✅ 2.1 s✅ 2.8 s✅ 13.2 s*
Escribir y ejecutar FizzBuzz✅ 5.3 s · 2 herramientas✅ 8.4 s · 2 herramientas✅ 4.9 s · 2 herramientas
Corregir 2 bugs para que pasen las pruebas unitarias (pruebas sin tocar)✅ 11.7 s · 8 herramientas✅ 15.8 s · 7 herramientas✅ 10.4 s · 8 herramientas
Resumir una base de código de 6 módulos (<150 palabras)✅ 8.9 s · 9 herramientas✅ 10.9 s · 7 herramientas✅ 12.1 s · 7 herramientas
Renombrar una función en 3 archivos + pruebas, comprobar en verde✅ 15.1 s · 14 herramientas✅ 18.2 s · 12 herramientas✅ 12.2 s · 11 herramientas
Tiempo total transcurrido43 s56 s53 s
Tokens (entrada sin caché / lectura de caché / salida)42.6k / 136k / 4.5k41.4k / 107k / 3.2k40.5k / 237k / 5.6k
Costo a tarifa punta (fuera de punta es la mitad)≈ $0.027≈ $0.072$0 (electricidad)

*Primera llamada después de cargar el modelo en frío en el equipo; las tareas posteriores muestran la velocidad en caliente. Precios de la página de precios de DeepSeek el 2026-08-18: Flash $0.014 / $0.44 / $1.32 por millón (acierto de caché / fallo / salida), Pro $0.044 / $1.32 / $3.96.

Qué dice la tabla:

  • El almacenamiento en caché del prompt es el que paga la factura. Cada tarea paga ~7.5k tokens de prompt de sistema, pero después del primer paso casi todo son lecturas de caché al 3% del precio de fallo. Las tareas de varios pasos son baratas porque el entorno mantiene estable el prefijo.
  • Pro usó menos pasos y menos herramientas para el mismo resultado (12 frente a 14 llamadas a herramientas en el renombrado; 3 frente a 4 pasos en el resumen). En esta suite, Flash fue más rápido y costó un tercio del precio, así que sigue siendo mi opción predeterminada.
  • El modelo local estuvo a la altura. Gemma-4-26B (un MoE de 4B activos, cuantización Q4, servido por llama.cpp en dos RTX 5090) pasó todo, con más pasos y más tokens, pero con un tiempo transcurrido competitivo. Es la primera vez que un modelo local es una opción real para las tareas de aquí, en lugar de una novedad, aunque cinco tareas pequeñas no dicen nada sobre un refactor de 40 archivos.

Después, la prueba de «lo normal»: lo apunté al repositorio de este sitio web y le pedí que leyera el runbook del proyecto, ejecutara la verificación de compilación y enlaces, e informara de los resultados — sin publicar ni hacer ediciones. Leyó el runbook, ejecutó el comando correcto, informó las líneas exactas del verificador (290 páginas, 1,752 imágenes, sin enlaces rotos), el tiempo de compilación y — sin que se lo pidiera — notó que el runbook todavía decía «288 páginas» y señaló la discrepancia. Cincuenta y tres segundos. Esa es la tarea de bajo riesgo que delego todo el día.

La prueba divertida: constrúyeme un juguete

Los benchmarks son una cosa; también quería ver qué hace con un encargo creativo abierto. Así que: «haz una versión de píxeles interactiva del logotipo de este sitio — vanilla JS, incrustable en cualquier lugar, el hover hace algo físico, el clic hace algo genial, sin dependencias, pruébalo tú mismo». Esto es lo que construyó — está en vivo, así que adelante:

Pasa el ratón por encima y luego haz clic. Los píxeles fluyen alejándose del puntero y vuelven a su lugar; un clic rompe la marca en píxeles que rebotan y encuentran el camino de vuelta. El tacto también funciona; respeta prefers-reduced-motion.
Cuatro fotogramas del widget de logotipo de píxeles: inactivo, píxeles apartados alrededor del cursor con un resplandor azul, una nube cian de píxeles rebotando en los bordes justo después de un clic, y los píxeles volviendo a la forma de anillo y dos eles
Inactivo → repulsión al pasar el ratón → rotura → reensamblaje, capturado en Chromium headless. 61 fps, cero errores de consola, y sí se reensambla.

Cómo fue, honestamente:

  • Intento 1 (V4-Flash): un bucle de 10 minutos, sin archivos. El encargo permitía un mapa de bits dibujado a mano o uno procedural. Eligió dibujar a mano un mapa de bits de 40×40 dentro de su razonamiento y cayó en un bucle degenerado — el registro de la sesión son cientos de líneas de ################ / .... — hasta que mi tiempo de espera lo mató. Unos 2¢ desperdiciados. Lección: nunca dejes que un modelo dibuje píxeles a mano en su cabeza.
  • Intento 2 (V4-Flash, encargo modificado a «rasterizar desde geometría, sin mapas de bits»): 25 minutos, todo entregado. 100 pasos de modelo, 201 llamadas a herramientas, 172k tokens de salida (123k de ellos de razonamiento), 16.3M tokens de lectura de caché — ≈ 49¢ a tarifas máximas. Escribió un ll-pixel-logo.js de 399 líneas con una API global única y controles data-, una página de demostración, un README, una prueba unitaria de Node para el rasterizador y — sin que se lo pidieran — un script de Playwright que captura capturas de pantalla de la demo en tres tamaños. Alcanzó mi límite de 25 minutos mientras pulía el README, por lo que el código de salida fue un timeout, pero el trabajo estaba hecho. Los códigos de salida mienten en ambos sentidos.
  • El arte: el anillo y las dos L inclinadas se leen como la marca de un vistazo; son más gruesas y más parecidas a una Z que el logotipo real, y yo pasaría diez minutos ajustando sus constantes de cizallamiento antes de usarlo en serio. No lo hice — lo que ves está intacto.
  • Incrustarlo aquí tomó una línea porque el CSP del sitio es script-src 'self' y el widget no hace solicitudes de red. Eso estaba en el encargo; lo respetó.

Advertencias

  • Vista previa para desarrolladores, y lo dice en mayúsculas. 0.1.0-rc.7 en la instalación; rc.6 era de tres días antes. Los perfiles, las claves de configuración y la estructura de los plugins pueden cambiar. Fija la versión en cualquier cosa que automatices y vuelve a ejecutar una prueba de humo después de cada actualización.
  • El modo headless permanece en silencio hasta que termina. Nada se transmite a stdout — una tarea larga parece bloqueada. Lee el registro de sesión (~/.dsh/sessions/…/session.jsonl.zstd, zstd-compressed JSONL) o míralo en la interfaz web. Y el código de salida 0 significa «el turno se completó», no «la tarea tuvo éxito». Comprueba el trabajo.
  • No hay una opción --model en el modo headless. El modelo predeterminado viene de settings.yaml; cámbialo allí (recarga en caliente) o desde la interfaz web.
  • Los servidores locales sin clave necesitan una credencial de relleno referenciada por apiKeyEnv, y los ID de proveedor son permanentes. Se ha explicado antes; te va a morder.
  • Los recursos de la interfaz web usan rutas absolutas (/assets/…, /api), por lo que no puedes montarla en un subdirectorio detrás de tu propio proxy inverso sin reescribir; intégrala o asígnale su propio nombre de host.
  • La locale de la interfaz sigue a tu navegador — el index.html incluido dice lang="zh-CN", y lo primero que ves es un diálogo de «Aviso de pruebas internas». Haz clic en Continuar; todo lo demás fue inglés para mí.
  • La instalación ejecuta scripts de postinstall (node-pty, koffi, protobufjs). npm advierte sobre ello. No vi nada malicioso, pero es código nativo compilándose en tu prefijo — otra razón para tenerlo en un prefijo privado, no en el del sistema.
  • Solo Node 22.19+ o 24. Las LTS anteriores se niegan a ejecutarlo.

Dónde me deja esto

Se ganó su lugar en una tarde. El modo headless es la forma adecuada para un agente de codificación al que otro software llama: un comando, una respuesta, un código de salida, un registro auditable — y el modelo por YAML significa que mi stack de agentes puede apuntarlo al cerebro que mejor se ajuste al trabajo. Si ya ejecutas un servidor de modelos local, prueba las mismas cinco tareas; diez centavos de crédito de API y una carpeta temporal son todo lo que se necesita.

Relacionado: Reasonix (el otro agente de codificación mencionado en este artículo), DeepSeek Everywhere (integrando DeepSeek en Claude Code y un stack de agentes), DisPatch (la aplicación de chat en la que vive el panel), y bench-llm (evaluando modelos locales más en serio de lo que hice aquí).


← Más de IA y LLM Local