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 Harness — dsh — 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ó.
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:3080y 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:
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_MODEsin 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-writeconfina 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-modelen 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.
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én | DeepSeek, oficial, MIT | De terceros, estilo Claude Code |
| Superficie | Web UI + one-shot headless; sin TUI | TUI de terminal (sesión interactiva en una PTY) |
| Llamarlo desde un script | dsh --profile headless "…" — una respuesta, código de salida | Diseñado para un humano en el bucle; el scripting es incómodo |
| Cambio de modelo | Editar settings.yaml, recarga en caliente; sin flag --model | Niveles en config.toml (flash/pro) + enrutamiento por skill |
| Modelos locales | Cualquier servidor compatible con OpenAI mediante un bloque de proveedor | El mismo trío: base URL, env de clave, ids de modelo |
| Extensibilidad | Todo es un plugin (modelo, herramientas, sandbox, bucle) | Subagentes, skills, memoria por proyecto |
| En mi DisPatch | iframe + controles de unidad + pestaña de trabajos headless | Terminal xterm.js sobre una PTY |
| Madurez | Vista previa para desarrolladores (rc.7), se rompe entre versiones | v1.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.
| Tarea | V4-Flash | V4-Pro | Gemma-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 transcurrido | 43 s | 56 s | 53 s |
| Tokens (entrada sin caché / lectura de caché / salida) | 42.6k / 136k / 4.5k | 41.4k / 107k / 3.2k | 40.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:
prefers-reduced-motion.
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.jsde 399 líneas con una API global única y controlesdata-, 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.7en 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
--modelen el modo headless. El modelo predeterminado viene desettings.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í).