DeepSeek Harness (dsh) : j'ai installé l'agent de codage open source de DeepSeek, je l'ai branché à mon application de chat et je l'ai benchmarké

Publié
18 août 2026
Par
Jacob Lloyd — rédigé avec l'aide de l'IA, une fois le projet terminé
Temps de lecture
17 min de lecture

En clair : DeepSeek a publié un « agent de codage » gratuit et open source — un programme qui lit votre projet, modifie des fichiers et exécute des commandes pour vous, comme Claude Code. Cet article montre comment je l'ai installé sur mon PC Linux, configuré pour démarrer automatiquement, connecté à mon application de chat maison, et testé sur de petites tâches de codage réelles avec à la fois les modèles cloud de DeepSeek et un modèle tournant sur mon propre matériel. Ça a fonctionné, c'était peu coûteux, et il y a quelques angles vifs.

Le 13 août 2026, DeepSeek a publié DeepSeek Harnessdsh — un agent de codage sous licence MIT construit sur l’idée que tout est un plugin : l’adaptateur de modèle, les outils, le bac à sable, et même la boucle de l’agent. Il a atteint 95 000 étoiles GitHub en deux jours. Je l’ai installé sur ma machine personnelle la même semaine, lui ai donné une place à côté de Reasonix, mon agent de codage existant, dans DisPatch, je l’ai connecté à la fois aux modèles cloud de DeepSeek et à un modèle sur ma propre station GPU, puis je l’ai fait passer sur une petite suite de tâches. Voici ce que cela a impliqué, ce que cela a coûté et où le bât blesse.

Test rapide avant de lire la suite : survolez-le, puis cliquez dessus. dsh a créé cette version pixel art du logo du site à partir d’un brief écrit — du JavaScript vanilla, aucune dépendance — et l’histoire complète de son déroulement se trouve plus bas.

En bref

  • Ce que c’est : un agent du même type que Claude Code (lit/modifie des fichiers, exécute des commandes shell, tient à jour un plan, lance des sous-agents) fourni sous forme de paquet npm. Deux modes : une interface web sur 127.0.0.1:3080 et un mode headless à exécution unique qui affiche une réponse puis se termine — conçu pour les scripts et d’autres agents.
  • Ce que ça coûte : le logiciel est gratuit. Mon benchmark complet de 5 tâches a coûté environ 3 ¢ sur V4-Flash et environ 7 ¢ sur V4-Pro aux tarifs de pointe ; rien sur le modèle local.
  • Ce qu’il vous faut : Node.js 22.19+/24, une clé API DeepSeek ou n’importe quel serveur compatible OpenAI (j’ai utilisé les deux).
  • Ce que vous obtenez au final : dsh --profile headless "fix the failing test" depuis n’importe quel dossier de projet, une interface web en service d’arrière-plan, le changement de modèle en modifiant un fichier YAML et — dans mon cas — un bot « DeepSeek Harness » dans mon application de chat avec un bouton Run.
  • Le résultat : 15/15 validés sur V4-Flash, V4-Pro et un Gemma-4-26B local, 2 à 18 s par tâche. Aperçu développeur — le README prévient en majuscules que des éléments casseront entre les versions, et j’ai heurté quelques-uns des angles vifs énumérés ci-dessous.

Le parcours, du basique à l'avancé

Les étapes 1 à 3 vous donnent un agent fonctionnel en dix minutes. Les étapes 4 et 5 sont ce que j'ai ajouté par-dessus pour que d'autres logiciels — et d'autres agents — puissent l'appeler.

Ce que vous obtenez au final

Ce que j'utilise réellement, c'est le mode headless. À partir d'un répertoire de projet :

$ 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

Six secondes, fichier créé, programme exécuté, réponse affichée, code de sortie 0. Il n'écrit rien en dehors du dossier dans lequel vous l'avez lancé (mode d'autorisation par défaut workspace-write), n'affiche que le message final sur stdout, et conserve la trace de chaque exécution afin que vous puissiez l'ouvrir plus tard dans l'interface web et lire précisément ce qu'il a fait — chaque appel d'outil, chaque nombre de tokens.

L'interface navigateur reprend la disposition familière de 2026 — sessions à gauche, chat au centre, une page de paramètres pour les modèles — avec une bonne habitude : la clé API est collée dans la page de paramètres, jamais dans un fichier de configuration. Je l'exécute comme service utilisateur systemd et elle est intégrée à mon application de chat à côté de Reasonix :

Application de chat DisPatch avec le bot « DeepSeek Harness » sélectionné : l'interface web de dsh intégrée dans le panneau principal, montrant une session terminée « Créer et exécuter hello.py » avec ses étapes Think, Write et Bash et la réponse ; en dessous, une barre d'état avec En cours, une liste déroulante Modèle réglée sur DeepSeek-V4-Flash, et des boutons Démarrer/Redémarrer/Arrêter
L'interface web de dsh dans DisPatch, montrant une session terminée avec sa trajectoire d'étapes (Think → Write → Bash) et la ligne de statistiques par tour. La barre en bas contrôle l'unité systemd et le modèle par défaut.

Étape 1 : installation (deux minutes)

Un seul paquet npm. Je garde un préfixe Node privé pour l'outillage des agents, afin que rien n'atterrisse dans l'arborescence système — sur une distribution immuable comme Bluefin, c'est de toute façon le seul endroit raisonnable — mais une installation globale standard correspond à la même commande :

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

Ouvrez l'URL, allez dans Paramètres → Modèles, collez votre clé DeepSeek, enregistrez. Cela écrit ~/.dsh/.credentials.yaml (mode 0600), et le routage du modèle fonctionne immédiatement, sans redémarrage. Pour l'automatiser, le fichier est un simple mapping YAML — DEEPSEEK_API_KEY: sk-… — et j'ai écrit le mien à partir du fichier d'environnement que mes autres services lisent déjà, de sorte que le secret existe dans un endroit de plus, mais jamais dans un historique de shell ni dans un fichier unité.

Deux choses à savoir avant d'aller plus loin :

  • La télémétrie est désactivée par défaut (DSH_TELEMETRY_MODE non défini = désactivée). J'ai vérifié la configuration livrée, pas les textes marketing ; l'exportateur OTLP existe, il est simplement désactivé.
  • Le sandbox est réel mais limité. workspace-write restreint les écritures au répertoire depuis lequel vous l'avez lancé. Les lectures ne sont pas restreintes — la documentation le dit clairement — ne le lancez donc pas depuis votre répertoire personnel pour une tâche réelle, et ne le pointez pas vers des dossiers contenant des secrets.

Étape 2 : l'exécuter en tant que service

L’interface web est un processus Node à exécution longue ; je voulais qu’elle se lance à la connexion, sans terminal, et en bouclage local uniquement. Une unité systemd --user fait l’affaire :

# ~/.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 est la racine de travail par défaut de l’interface web, mais celle-ci vous demande encore de choisir explicitement un espace de travail avant de vous laisser saisir du texte. Un bon choix par défaut. La CLI refuse également --host 0.0.0.0 — les auteurs qualifient la liaison à toutes les interfaces d’« intentionnellement pas encore prise en charge », et comme l’interface ne dispose d’aucune authentification, je partage cet avis. Vous voulez y accéder depuis une autre machine ? Placez un proxy inverse avec authentification devant, ou utilisez le navigateur de la machine hôte via votre solution de bureau à distance (c’est ce que je fais).

Étape 3 : modèles — un fichier YAML, rechargé à chaud

~/.dsh/settings.yaml contient le modèle par défaut et les éventuels fournisseurs supplémentaires, et dsh le relit pour la prochaine requête — sans redémarrage ni nouvelle connexion. Le mien :

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)

Deux détails qui m'ont chacun coûté dix minutes : apiKeyEnv est une référence résolue depuis .credentials.yaml ou l'environnement, jamais une valeur littérale — et un serveur local sans clé a quand même besoin qu'une quelconque référence d'identifiants soit fournie, car le client compatible OpenAI exige un bearer token. Une valeur factice dans .credentials.yaml suffit. Et l'identifiant du fournisseur (buildpc) est permanent dès lors que des sessions le référencent ; pour le renommer, il faut en ajouter un nouveau.

Comme ce fichier est toute l'interface, « passer dsh au modèle local » est une modification de deux lignes que n'importe quel script — ou n'importe quel autre agent — peut effectuer. Mon agent principal reconfigure le harnais vers un modèle local gratuit pour les tâches courantes, puis le ramène à V4-Pro pour les problèmes difficiles, sans toucher à rien d'autre.

Étape 4 : à côté de Reasonix dans DisPatch

J'avais déjà Reasonix intégré à DisPatch comme pseudo-bot : un clic dessus et le volet de discussion devient un terminal qui exécute sa TUI via un PTY. Je voulais dsh à côté. Le hic : le paquet officiel n'a pas de TUI. Des plugins tiers de type « TUI style Claude Code » existent sur npm, mais ils avaient quatre jours et des dépendances workspace:* cassées, et un paquet non revu avec accès au shell n'atterrit pas sur un serveur familial. Le volet dsh est donc construit à partir de ce que le paquet officiel fournit effectivement :

  • l'interface web intégrée dans une iframe (elle n'envoie pas d'en-têtes anti-frame), affichée uniquement quand le navigateur peut atteindre le loopback de l'hôte — un téléphone sur le tailnet reçoit à la place une simple explication ;
  • Start / Restart / Stop pour l'unité systemd, plus l'état de santé ;
  • un menu déroulant Model qui réécrit agent-default-model dans ce fichier YAML ;
  • un onglet Headless jobs : saisissez une tâche, choisissez un dossier sous le home, appuyez sur Run. Le serveur lance dsh --profile headless "…" comme une liste d'arguments fixe (pas de shell — la tâche est un seul élément argv, donc une tâche contenant ; rm -rf / n'est que du texte), un travail à la fois, et conserve un petit historique avec chaque réponse finale.
L'onglet Headless jobs dans DisPatch : une zone de tâche, un champ de répertoire de travail, et trois cartes de travaux terminés dans ~/Projects/dsh-playground — « Done in 19s » (ajouter une docstring à fizz.py et le relancer), « Done in 5s » (créer primes.py, afficher 2 3 5 7 11…), « Done in 5s » (lister les fichiers et décrire chacun) — chacune montrant la tâche et le texte de la réponse
Les tâches headless depuis l'application de discussion. Chaque carte est une exécution de dsh --profile headless ; la réponse est la dernière chose que dsh a imprimée. Trois exécutions réelles : 5 s, 5 s, 19 s.

Tout dans ce volet se trouve derrière le déverrouillage admin de l'application — une tâche headless est une exécution de code arbitraire, et les appareils familiaux verrouillés ne découvrent jamais l'existence du volet. Si vous intégrez dsh dans vos propres outils, copiez cela : traitez « exécuter une tâche » exactement comme vous traitez un shell.

dsh vs Reasonix, côte à côte

Ce sont tous deux des agents calqués sur Claude Code qui facturent au token sur DeepSeek. Ils diffèrent par leur créateur et par la surface qu'ils vous offrent :

DeepSeek Harness (dsh)Reasonix
QuiDeepSeek, officiel, MITTiers, style Claude Code
SurfaceInterface web + one-shot headless ; pas de TUITUI terminal (session interactive dans un PTY)
L'appeler depuis un scriptdsh --profile headless "…" — une réponse, code de sortieConçu pour un humain dans la boucle ; l'écriture de scripts est maladroite
Changement de modèleModifiez settings.yaml, rechargé à chaud ; pas d'option --modelconfig.toml paliers (flash/pro) + routage par compétence
Modèles locauxN'importe quel serveur compatible OpenAI via un bloc providerMême trio : URL de base, env de la clé, identifiants de modèle
ExtensibilitéTout est un plugin (modèle, outils, sandbox, boucle)Sous-agents, compétences, mémoire par projet
Dans mon DisPatchiframe + contrôles d'unité + onglet tâches headlessterminal xterm.js sur un PTY
MaturitéAperçu développeur (rc.7), ruptures entre les versionsv1.18, auto-updater, config stable

En pratique : Reasonix est ce que j'ouvre quand je vais m'installer avec l'agent ; dsh est ce que mes autres logiciels appellent. C'est cette répartition qui explique que les deux restent.

Le benchmark : 5 tâches, 3 modèles, 15/15

Rien de scientifique — cinq petites tâches que je confierais réellement à un agent de codage, chacune dans un dossier scratch vierge, chacune vérifiée automatiquement (le fichier existe-t-il et s’exécute-t-il ? les tests passent-ils sans toucher au fichier de test ? le renommage a-t-il laissé zéro ancienne référence ?). Le temps total couvre tout le processus, y compris le prompt système d’environ 7 500 tokens ; les tokens proviennent du journal de session propre à dsh.

TâcheV4-FlashV4-ProGemma-4-26B (local, machine)
Répondre « PONG » (démarrage + un appel)✅ 2,1 s✅ 2,8 s✅ 13,2 s*
Écrire + exécuter FizzBuzz✅ 5,3 s · 2 outils✅ 8,4 s · 2 outils✅ 4,9 s · 2 outils
Corriger 2 bugs pour que les tests unitaires passent (tests non modifiés)✅ 11,7 s · 8 outils✅ 15,8 s · 7 outils✅ 10,4 s · 8 outils
Résumer une base de code de 6 modules (<150 mots)✅ 8,9 s · 9 outils✅ 10,9 s · 7 outils✅ 12,1 s · 7 outils
Renommer une fonction dans 3 fichiers + tests, prouver que tout est vert✅ 15,1 s · 14 outils✅ 18,2 s · 12 outils✅ 12,2 s · 11 outils
Temps total43 s56 s53 s
Tokens (entrée en cache miss / lecture cache / sortie)42,6k / 136k / 4,5k41,4k / 107k / 3,2k40,5k / 237k / 5,6k
Coût aux tarifs de pointe (hors pointe, moitié prix)≈ 0,027 $≈ 0,072 $0 $ (électricité)

*premier appel après le chargement à froid du modèle sur la machine ; les tâches suivantes montrent la vitesse à chaud. Prix issus de la page de tarification de DeepSeek en date du 2026-08-18 : Flash 0,014 $ / 0,44 $ / 1,32 $ par million (cache hit / miss / sortie), Pro 0,044 $ / 1,32 $ / 3,96 $.

Ce que dit le tableau :

  • Le cache de prompt paie l’addition. Chaque tâche paie environ 7,5k tokens de prompt système, mais après la première étape, ce ne sont presque que des lectures cache à 3 % du prix du miss. Les tâches multi-étapes sont peu coûteuses parce que le harnais garde le préfixe stable.
  • Pro a utilisé moins d’étapes et moins d’outils pour le même résultat (12 appels d’outils contre 14 pour le renommage ; 3 étapes contre 4 pour le résumé). Sur cette série, Flash a été plus rapide et coûtait le tiers du prix, donc il reste mon choix par défaut.
  • Le modèle local a tenu la comparaison. Gemma-4-26B (un MoE à 4B actifs, quantifié en Q4, servi par llama.cpp sur deux RTX 5090) a tout réussi, avec plus d’étapes et plus de tokens, mais un temps total compétitif. C’est la première fois qu’un modèle local est une véritable option pour les tâches courantes ici, et non plus une nouveauté — même si cinq petites tâches ne disent rien d’un refactor de 40 fichiers.

Puis le test « les tâches habituelles » : je l’ai pointé sur le dépôt de ce site et je lui ai demandé de lire le runbook du projet, d’exécuter le build et la vérification des liens, puis de faire un rapport — sans publier, sans modification. Il a lu le runbook, exécuté la bonne commande, rapporté les lignes exactes du vérificateur (290 pages, 1 752 images, aucun lien cassé), le temps de build, et — sans qu’on le lui demande — a remarqué que le runbook disait encore « 288 pages » et a signalé cette dérive. Cinquante-trois secondes. C’est le genre de tâche à faible enjeu que je délègue toute la journée.

Le test amusant : construis-moi un jouet

Les benchmarks, c'est une chose ; je voulais aussi voir ce qu'il fait avec un brief créatif ouvert. Donc : « crée une version pixel-art interactive du logo de ce site — vanilla JS, intégrable n'importe où, le survol fait quelque chose de physique, le clic fait quelque chose de cool, aucune dépendance, teste-la toi-même. » Voici ce qu'il a construit — c'est en ligne, alors allez-y :

Survolez-le, puis cliquez dessus. Les pixels s'éloignent du pointeur et reviennent en rebondissant ; un clic fait voler le logo en éclats, en pixels rebondissants qui finissent par retrouver leur place. Le tactile fonctionne aussi ; il respecte prefers-reduced-motion.
Quatre images du widget de logo en pixels : au repos, des pixels repoussés autour du curseur avec une lueur bleue, un nuage de pixels cyan rebondissant sur les bords juste après un clic, et les pixels qui dérivent pour reformer l'anneau et les deux L
Repos → répulsion au survol → éclatement → réassemblage, capturé dans Chromium headless. 61 fps, zéro erreur console, et il se réassemble bien.

Ce que ça a donné, honnêtement :

  • Essai 1 (V4-Flash) : une boucle de 10 minutes, aucun fichier. Le brief autorisait un bitmap dessiné à la main ou un bitmap procédural. Il a choisi de dessiner à la main un bitmap 40×40 dans son raisonnement et est tombé dans une boucle dégénérée — le journal de session contient des centaines de lignes de ################ / .... — jusqu'à ce que mon timeout le tue. Environ 2 ¢ gaspillés. Leçon : ne laissez jamais un modèle dessiner des pixels à la main dans sa tête.
  • Essai 2 (V4-Flash, brief modifié pour « rastériser à partir de la géométrie, pas de bitmaps ») : 25 minutes, tout a été livré. 100 étapes de modèle, 201 appels d'outils, 172k jetons de sortie (dont 123k de raisonnement), 16,3 M de jetons de lecture de cache — environ 49 ¢ aux tarifs de pointe. Il a écrit un ll-pixel-logo.js de 399 lignes avec une API à une seule variable globale et des réglages data-, une page de démo, un README, un test unitaire Node pour le rastériseur et — sans qu'on le lui demande — un script Playwright qui capture la démo en trois tailles. Il a atteint ma limite de 25 minutes en peaufinant le README, donc le code de sortie était un timeout, mais le travail était terminé. Les codes de sortie mentent dans les deux sens.
  • L'art : l'anneau et les deux L inclinés se lisent comme le logo au premier coup d'œil ; ils sont plus épais et plus proches d'un Z que le vrai logo, et je passerais dix minutes à régler ses constantes de cisaillement avant de l'utiliser pour de bon. Je ne l'ai pas fait — ce que vous voyez n'a pas été retouché.
  • L'intégrer ici a pris une ligne parce que la CSP du site est script-src 'self' et que le widget ne fait aucune requête réseau. C'était dans le brief ; il l'a respecté.

Pièges

  • Version de développement, et c'est écrit en majuscules. 0.1.0-rc.7 à l'installation ; rc.6 datait de trois jours plus tôt. Les profils, les clés de configuration et l'agencement des plugins peuvent changer. Épinglez la version dans tout ce que vous automatisez et relancez un test de fumée après chaque mise à niveau.
  • Le mode headless reste silencieux jusqu'à la fin. Rien n'est diffusé sur stdout — une tâche longue semble bloquée. Consultez le journal de session (~/.dsh/sessions/…/session.jsonl.zstd, JSONL compressé en zstd) ou suivez-le dans l'interface web. Et le code de sortie 0 signifie « le tour est terminé », pas « la tâche a réussi ». Vérifiez le travail.
  • Pas d'option --model en mode headless. Le modèle par défaut provient de settings.yaml ; modifiez-le à cet endroit (rechargement à chaud) ou dans l'interface.
  • Les serveurs locaux sans clé nécessitent un identifiant factice référencé par apiKeyEnv, et les identifiants de fournisseur sont permanents. C'est expliqué plus haut ; cela vous mordra.
  • Les ressources de l'interface web utilisent des chemins absolus (/assets/…, /api), il est donc impossible de la monter sous un sous-chemin derrière votre propre reverse proxy sans réécriture ; intégrez-la ou donnez-lui son propre nom d'hôte.
  • La langue de l'interface suit celle de votre navigateur — le fichier index.html fourni indique lang="zh-CN" et la première chose que vous voyez est une boîte de dialogue « Avis de test interne ». Cliquez sur Continuer ; tout le reste était en anglais pour moi.
  • L'installation exécute des scripts postinstall (node-pty, koffi, protobufjs). npm vous en avertit. Rien de malveillant que j'aie pu voir, mais c'est du code natif compilé dans votre préfixe — une raison de plus de le conserver dans un préfixe privé, pas dans celui du système.
  • Node 22.19+ ou 24 uniquement. Les versions LTS plus anciennes refusent de l'exécuter.

Ce que j'en retire

Il a gagné sa place en un après-midi. Le mode headless est la forme idéale pour un agent de codage que d'autres logiciels appellent — une commande, une réponse, un code de sortie, un journal auditable — et le modèle défini par YAML signifie que ma pile d'agents peut le diriger vers n'importe quel cerveau adapté à la tâche. Si vous exécutez déjà un serveur de modèles local, essayez les cinq mêmes tâches ; dix cents de crédit API et un dossier de travail suffisent.

Liens connexes : Reasonix (l'autre agent de codage mentionné dans cet article), DeepSeek Everywhere (comment brancher DeepSeek sur Claude Code et une pile d'agents), DisPatch (l'application de chat qui héberge le panneau), et bench-llm (qui évalue les modèles locaux de manière plus sérieuse que moi ici).


← Plus de IA et LLM locaux