DeepSeek Harness (dsh): DeepSeek製オープンソースのコーディングエージェントをインストールし、チャットアプリに組み込み、ベンチマークを実行しました

公開日
2026年8月18日
著者
Jacob Lloyd — プロジェクト完了後、AIの支援を受けて執筆
読了時間
約21分で読めます

かんたんに言うと: DeepSeekは、Claude Codeのようにプロジェクトを読み取り、ファイルを編集し、コマンドを実行してくれる、無料のオープンソース「コーディングエージェント」をリリースしました。この記事では、Linux PCへのインストール方法、自動起動の設定、自宅のチャットアプリへの接続、そしてDeepSeekのクラウドモデルと自分のハードウェアで動くモデルの両方を使って、小さな実際のプログラミング作業でテストした結果を紹介します。動作したこと、低コストだったこと、そしていくつか注意すべき点も含まれます。

2026年8月13日、DeepSeekはDeepSeek Harnessdsh — をリリースしました。これはMITライセンスのコーディングエージェントで、すべてがプラグインという考え方に基づいています。モデルアダプタ、ツール、サンドボックス、さらにはエージェントループまで。リリースから2日間でGitHubスター95,000を達成しました。同じ週に自宅のマシンへインストールし、DisPatch内で既存のコーディングエージェントであるReasonixの隣に配置し、DeepSeekのクラウドモデルと自前のGPUマシン上のモデルの両方を指定して、小さなタスクスイートを実行しました。この記事は、そのために必要なもの、かかったコスト、そして問題が発生した箇所をまとめたものです。

読み進める前にちょっとしたテスト: マウスオーバーしてからクリックしてください。 dshは、文章による指示(ブリーフ)だけで、このサイトのロゴのピクセルアート版を作成しました — バニラJS、依存関係なし — その顛末は後述します。

tl;dr

  • 概要: Claude Codeクラスのエージェント(ファイルの読み書き、シェル実行、計画の保持、サブエージェントの生成)をnpmパッケージとして提供。2つのモード: 127.0.0.1:3080で動作するWeb UIと、1つの回答を出力して終了するヘッドレスのワンショットモード — スクリプトや他のエージェント向けに設計されています。
  • コスト: ソフトウェアは無料です。私の5タスクのベンチマーク全体で、ピーク時レートでV4-Flashで約3セントV4-Proで約7セント。ローカルモデルは無料です。
  • 必要なもの: Node.js 22.19+/24、DeepSeek APIキー または OpenAI互換サーバー(私は両方を使いました)。
  • 最終的に得られるもの: 任意のプロジェクトフォルダから dsh --profile headless "fix the failing test"、バックグラウンドサービスとしてのブラウザUI、1つのYAMLファイルを編集するだけで切り替えられるモデル、そして — 私の場合は — Runボタン付きの「DeepSeek Harness」ボットがチャットアプリに追加されます。
  • 結果: V4-Flash、V4-Pro、ローカルのGemma-4-26Bで15/15パス、タスクあたり2〜18秒。開発者プレビュー版 — READMEには、バージョン間で壊れる可能性があると大文字で警告があり、私も以下に挙げるいくつかの問題点に実際に遭遇しました。

基本から応用までの流れ

ステップ1〜3で、10分以内に動作するエージェントを準備できます。ステップ4〜5は、他のソフトウェアや他のエージェントから呼び出せるようにするために、私がさらに追加で行った内容です。

最終的に得られるもの

実際に使っているのはヘッドレスモードだ。プロジェクトディレクトリの中で次のように実行する:

$ 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

6秒。ファイル作成、プログラム実行、回答出力、終了コード0。開始したフォルダの外には何も書き込まず(デフォルトの権限モードは workspace-write)、標準出力には最終メッセージのみを出力し、毎回の実行を永続化する。そのため、後でWeb UIで開けば、すべてのツール呼び出しやトークン数まで、何を行ったのかを正確に読むことができる。

ブラウザUIは2026年式の見慣れたレイアウトだ。左にセッション一覧、中央にチャット、モデル設定ページがある。そして良い習慣がひとつある。APIキーは設定ページに貼り付けるもので、設定ファイルに置くことは決してない。私の環境ではsystemdユーザーサービスとして動作し、チャットアプリ内でReasonixの隣に埋め込んでいる:

DisPatchチャットアプリで「DeepSeek Harness」ボットを選択した状態:メインペインに埋め込まれたdsh Web UIが、完了したセッション「Create and run hello.py」と、そのThink・Write・Bashの各ステップと回答を表示。下部のステータスバーにはRunning、DeepSeek-V4-Flashが選択されたModelドロップダウン、Start/Restart/Stopボタンがある。
DisPatch内のdsh Web UI。完了したセッションと、そのステップの軌跡(Think → Write → Bash)、ターンごとの統計行を示している。下部のバーはsystemdユニットとデフォルトモデルを制御する。

ステップ1: インストール(2分)

npmパッケージは1つだけです。エージェントツール用にプライベートなNodeプレフィックスを用意して、システムツリーに何も入らないようにしています。Bluefinのようなイミュータブルなディストリビューションでは、いずれにせよそこが唯一妥当な場所です。ただし、通常のグローバルインストールでも同じコマンドです:

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

URLを開き、Settings → Modelsに移動してDeepSeekキーを貼り付け、保存します。これで~/.dsh/.credentials.yaml(モード0600)が書き込まれ、モデルルートが即座に機能します。再起動は不要です。スクリプトから使う場合、このファイルは単純なYAMLマッピング — DEEPSEEK_API_KEY: sk-… — です。私は自分のものを、他のサービスがすでに読み込んでいるenvファイルから作成したので、シークレットはもう1箇所に存在することになりますが、シェル履歴やユニットファイルに入ることは決してありません。

先に進む前に知っておくべきことが2つあります:

  • テレメトリーはデフォルトでオフです(DSH_TELEMETRY_MODE未設定=無効)。私はマーケティング用の案内文ではなく同梱の設定を確認しました。OTLPエクスポーターは存在しますが、単にオンになっていないだけです。
  • サンドボックスは本物ですが、範囲は狭いです。workspace-write書き込みを起動したディレクトリに限定します。読み取りは制限されません — ドキュメントにもはっきりそう書かれています — したがって、実際のタスクではホームディレクトリから起動しないでください。シークレットが入ったフォルダを指定するのも避けてください。

ステップ2: サービスとして実行する

Web UIは長時間実行されるNodeプロセスです。ログイン時に起動し、ターミナル不要、ループバックのみで動作させたいと思いました。systemd--userユニットで実現できます:

# ~/.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はWeb UIのデフォルトのワークスペースルートですが、それでもUIは入力できるようになる前にワークスペースを明示的に選択させます。良いデフォルトです。CLIも--host 0.0.0.0を拒否します。作者は全インターフェースへのバインドを「意図的にはまだサポートされていない」としていますが、UIには認証が一切ないので、私も同意見です。別のマシンから使いたい場合は?認証付きリバースプロキシを前面に置くか、リモートデスクトップ経由でホストのブラウザにアクセスしてください(私がやっている方法です)。

ステップ3: モデル — 1つのYAMLファイル、ホットリロード

~/.dsh/settings.yaml にはデフォルトモデルと追加のプロバイダが保存され、dsh はこのファイルを次のリクエストのために再読み込みします — 再起動も再ログインも不要です。私の例:

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)

それぞれ10分かかった2つの詳細: apiKeyEnv.credentials.yaml または環境変数から解決される参照であり、リテラル値ではありません。また、キーを必要としないローカルサーバーであっても、OpenAI互換クライアントがベアラートークンを要求するため、何らかの資格情報を参照する必要があります。.credentials.yaml 内のプレースホルダー値で満たせます。さらに、プロバイダID(buildpc)は、セッションがそれを参照すると永続的になります。名前を変更するには新しく追加します。

ファイルがインターフェース全体であるため、「dsh をローカルモデルに切り替える」のは、どのスクリプトでも、また他のどのエージェントでも実行できる2行の編集です。私のリードエージェントは、雑務にはハーネスを無料のローカルモデルへ、難しい問題には V4-Pro へ、他の何にも触れずに切り替えています。

ステップ4: DisPatch で Reasonix の隣に

私はすでに Reasonix を疑似ボットとして DisPatch の中に置いている。クリックするとチャットペインが PTY 上で TUI を実行するターミナルになる。私はその隣に dsh を欲しかった。問題は、「公式パッケージには TUI がない」ことだ。npm にはサードパーティ製の「Claude-Code スタイル TUI」プラグインが存在するが、それらは公開から4日で workspace:* の依存関係が壊れており、シェルアクセスを伴う未レビューのパッケージを家族用サーバーに入れるわけにはいかない。そこで dsh ペインは、公式パッケージが提供するものを使って構築した。

  • iframe に埋め込んだ組み込み Web UI(フレームブロックヘッダーは送信されない)。ブラウザがホストのループバックに到達できる場合にのみ表示され、tailnet 上のスマートフォンには代わりに簡潔な説明が表示される。
  • systemd ユニット用の開始 / 再起動 / 停止と、ヘルスチェック。
  • その YAML ファイル内の agent-default-model を書き換えるモデルドロップダウン。
  • ヘッドレスジョブタブ: タスクを入力し、ホーム配下のフォルダを選び、実行を押す。サーバーは固定の引数リストとして dsh --profile headless "…" を起動する(シェルを介さない。タスクは1つの argv 要素であり、; rm -rf / を含むタスクも単なるテキストにすぎない)。ジョブは一度に1つ実行され、各最終回答とともに短い履歴が保持される。
DisPatch のヘッドレスジョブタブ: タスクボックス、作業ディレクトリフィールド、そして ~/Projects/dsh-playground にある3つの完了済みジョブカード — 「19秒で完了」(fizz.py に docstring を追加して再実行)、「5秒で完了」(primes.py を作成し、2 3 5 7 11… を出力)、「5秒で完了」(ファイルを一覧表示してそれぞれを説明) — 各カードにはタスクと回答テキストが表示されている
チャットアプリからのヘッドレスジョブ。各カードは dsh --profile headless の1回の実行で、回答は dsh が最後に出力したもの。実際の実行結果は3件: 5秒、5秒、19秒。

このペインのすべてはアプリの管理者ロックの背後にある。ヘッドレスジョブは任意のコード実行であり、ロックされた家族用デバイスはこのペインの存在を知ることはない。dsh を自分の何かに組み込むなら、これを踏襲してほしい。「タスクの実行」をシェルとまったく同じように扱うのだ。

dsh vs Reasonix: 並べてみる

どちらも Claude Code 型のエージェントで、DeepSeek 上でトークン単位で課金される。違いは、誰が作ったかと、どのようなインターフェースを提供するかだ。

DeepSeek Harness (dsh)Reasonix
製作者DeepSeek 公式、MITサードパーティ、Claude Code スタイル
インターフェースWeb UI + ヘッドレス単発実行。TUI なしターミナル TUI (PTY 上の対話セッション)
スクリプトから呼び出すdsh --profile headless "…" — 1つの回答と終了コード人間が介在することを想定した設計。スクリプト化は難しい
モデル切替settings.yaml を編集してホットリロード。--model フラグはなしconfig.toml のティア (flash/pro) + スキルごとのルーティング
ローカルモデルプロバイダーブロック経由で任意の OpenAI 互換サーバー同じ3点セット: base URL、キー環境変数、モデル ID
拡張性すべてがプラグイン (モデル、ツール、サンドボックス、ループ)サブエージェント、スキル、プロジェクトごとのメモリ
私の DisPatch 内での形態iframe + ユニット制御 + ヘッドレスジョブタブPTY 上の xterm.js ターミナル
成熟度開発者プレビュー (rc.7)、バージョン間で破壊的変更ありv1.18、自己更新、安定した設定

実際のところ、Reasonix はエージェントと向き合って作業するときに開くものであり、dsh は私の他のソフトウェアが呼び出すものだ。その住み分けがあるからこそ、両方を使い続けている。

ベンチマーク:5タスク、3モデル、15/15

科学的なものではありません——実際にコーディングエージェントに任せるような小さなタスクを5つ、それぞれ新しいスクラッチフォルダ内で実行し、それぞれ自動チェックしました(ファイルは存在して実行できるか?テストファイルに触れずにテストは通るか?リネーム後に古い参照が残っていないか?)。ウォールタイムは、約7,500トークンのシステムプロンプトを含むプロセス全体の時間で、トークン数はdsh自身のセッションログから取得しています。

タスクV4-FlashV4-ProGemma-4-26B(ローカル、RIG)
「PONG」と返信(ブート+1コール)✅ 2.1秒✅ 2.8秒✅ 13.2秒*
FizzBuzzを作成して実行✅ 5.3秒・ツール2回✅ 8.4秒・ツール2回✅ 4.9秒・ツール2回
ユニットテストが通るようにバグを2つ修正(テストは変更なし)✅ 11.7秒・ツール8回✅ 15.8秒・ツール7回✅ 10.4秒・ツール8回
6モジュールのコードベースを要約(150語未満)✅ 8.9秒・ツール9回✅ 10.9秒・ツール7回✅ 12.1秒・ツール7回
3ファイル+テストにわたる関数のリネーム、グリーンを確認✅ 15.1秒・ツール14回✅ 18.2秒・ツール12回✅ 12.2秒・ツール11回
ウォールタイム合計43秒56秒53秒
トークン(入力ミス / キャッシュ読み取り / 出力)42.6k / 136k / 4.5k41.4k / 107k / 3.2k40.5k / 237k / 5.6k
ピーク時レートのコスト(オフピークは半額)≈ $0.027≈ $0.072$0(電気代のみ)

*モデルをRIGにコールドロードした直後の初回呼び出し。以降のタスクはウォーム時の速度を示す。価格は2026-08-18時点のDeepSeekの料金ページより:Flashは100万トークンあたり$0.014 / $0.44 / $1.32(キャッシュヒット / ミス / 出力)、Proは$0.044 / $1.32 / $3.96。

この表が示すこと:

  • コストを支えているのはプロンプトキャッシュだ。各タスクは約7.5kトークンのシステムプロンプトを支払うが、最初のステップ以降はほぼすべてキャッシュ読み取りになり、ミス価格の3%で済む。マルチステップのタスクが安いのは、ハーネスがプレフィックスを安定に保つからだ。
  • Proは少ないステップ・少ないツールで同じ結果を出した(リネームではツール呼び出し12回 vs 14回、要約ではステップ3回 vs 4回)。このスイートではFlashの方が速く、価格も3分の1だったため、デフォルトは引き続きFlashだ。
  • ローカルモデルも互角に渡り合った。Gemma-4-26B(4BアクティブのMoE、Q4量子化、2枚のRTX 5090上でllama.cppが提供)は、ステップとトークンは多めだったものの、ウォールタイムは遜色なく、すべてのタスクをクリアした。ここでの雑務において、ローカルモデルが目新しいものではなく現実的な選択肢になったのは初めてだ——ただし、5つの小さなタスクが40ファイルのリファクタリングについて何かを語るわけではない。

次に「普通の作業」テスト:このウェブサイトのリポジトリを対象に指定し、プロジェクトのランブックを読み、ビルド&リンクチェックを実行して報告するよう依頼した——公開も編集もなしで。するとランブックを読み、正しいコマンドを実行し、チェッカーの該当行を正確に報告し(290ページ、1,752画像、壊れたリンクなし)、ビルド時間も報告し、さらに——指示なしに——ランブックがまだ「288ページ」と書かれていることに気づき、その乖離を指摘した。53秒。これこそ、私が一日中任せている低リスクの雑務だ。

楽しいテスト:おもちゃを作ってもらう

ベンチマークもひとつの指標ですが、オープンエンドな創造的ブリーフで何をするかも見てみたかった。そこで、こんな依頼をしてみた。「このサイトのロゴのインタラクティブなピクセルアート版を作ってください — バニラJS、どこにでも埋め込み可能、ホバーで物理的な動き、クリックでかっこいい演出、依存関係なし、自分でテストして」と。それが作ったのがこれ — 実際に動作するので、ぜひ触ってみてほしい:

マウスを乗せて、クリックしてみて。ピクセルがポインターから逃げるように流れ、バネのように戻ってきます。クリックするとロゴが砕け、跳ねるピクセルたちがやがて元の場所に戻っていきます。タッチ操作にも対応しており、prefers-reduced-motion にも配慮しています。
ピクセルロゴウィジェットの4フレーム:待機中、カーソルの周りでピクセルが青い光を放ちながら押しのけられている様子、クリック直後にシアンのピクセルの雲が端で跳ね返る様子、そしてピクセルがリングと2つのL字の形に戻っていく様子
待機 → ホバーで反発 → 粉砕 → 再集合。ヘッドレスChromiumで撮影。61fps、コンソールエラーゼロ、きちんと再集合します。

正直なところ、こんな結果だった:

  • 試行1(V4-Flash):10分間のループ、ファイルなし。ブリーフでは手描きビットマップでもプロシージャル生成でもよいとしていた。しかしモデルは推論の中で40×40のビットマップを手描きしようとし、退化したループに陥った — セッションログは ################ / .... の行が何百行も続くだけ — そしてタイムアウトで強制終了した。約2セントの無駄。教訓:モデルに頭の中でピクセルを手描きさせてはいけない。
  • 試行2(V4-Flash、「ジオメトリからラスタライズせよ、ビットマップは使うな」とブリーフを修正):25分、すべて納品。モデルステップ100回、ツール呼び出し201回、出力トークン172k(うち推論123k)、キャッシュ読み取りトークン16.3M — ピーク時レートで約49セント。399行の ll-pixel-logo.js を書き、グローバルAPIを1つと data- 属性による調整機能、デモページ、README、ラスタライザー用のNodeユニットテスト、さらに(頼んでもいないのに)3サイズでデモをスクリーンショットするPlaywrightスクリプトも作成した。READMEを仕上げているところで25分の制限に達し、終了コードはタイムアウトだったが、作業自体は完了していた。終了コードは当てにならないものだ。
  • アートについて:リングと傾いた2つのL字はひと目でロゴとわかる。本物のロゴより太めでZ字っぽいが、実戦で使う前にシアー定数を10分ほど調整したいところだ。ただ、それはしなかった — 見えているものは無調整のまま。
  • ここへの埋め込みは1行で済んだ。このサイトのCSPが script-src 'self' であり、ウィジェットがネットワークリクエストを行わないからだ。それはブリーフに含まれており、きちんと守ってくれた。

落とし穴

  • 開発者プレビューであり、その旨が大文字で明記されている。 インストール時点のバージョンは 0.1.0-rc.7。rc.6は3日前にリリースされていた。プロファイル、設定キー、プラグインのレイアウトは変更される可能性がある。自動化するものにはバージョンを固定し、アップグレードのたびにスモークテストを再実行すること。
  • ヘッドレスモードは完了まで何も出力しない。 stdoutには何もストリームされないため、長時間のタスクはハングしているように見える。セッションログ(~/.dsh/sessions/…/session.jsonl.zstdzstd圧縮のJSONL)を読むか、Web UIで確認する。また、終了コード0は「ターンが完了した」ことを意味し、「タスクが成功した」ことを意味するわけではない。成果物を確認すること。
  • ヘッドレスモードには --model フラグがない。 デフォルトのモデルは settings.yaml から取得される。そこで変更するか(ホットリロード)、UIで変更する。
  • キー不要のローカルサーバーには apiKeyEnv で参照されるプレースホルダー認証情報が必要で、プロバイダーIDは永続的である。前述のとおりだが、必ずつまずくポイントだ。
  • Web UIのアセットは絶対パス/assets/…/api)で参照されるため、リライトなしでは独自のリバースプロキシ配下のサブパスにマウントできない。埋め込むか、専用のホスト名を与える必要がある。
  • UIのロケールはブラウザに追従する — 同梱のindex.htmlには lang="zh-CN" と記載されており、最初に表示されるのは「Internal Testing Notice」ダイアログだ。Continueをクリックすれば、その後の表示は私の環境ではすべて英語だった。
  • インストール時にはpostinstallスクリプトが実行される(node-pty、koffi、protobufjs)。npmもその旨を警告する。私が見た限り悪意のあるものはなかったが、プレフィックス内でネイティブコードがコンパイルされるという点で、システムのプレフィックスではなくプライベートなプレフィックスに保持すべきもう一つの理由になる。
  • Node 22.19+ または 24 のみ。 古いLTSでは実行できない。

私の結論

このツールは、半日でその地位を勝ち取った。ヘッドレスモードは、他のソフトウェアから呼び出されるコーディングエージェントにとって理想的な形だ。一つのコマンド、一つの答え、一つの終了コード、監査可能なログ。そして、YAMLによるモデル指定により、私のエージェントスタックは、このエージェントを仕事に合ったどんなモデルにも向けられる。すでにローカルモデルサーバーを運用しているなら、同じ5つのタスクを試してみてほしい。必要なのは、10セント分のAPIクレジットとスクラッチフォルダだけだ。

関連:Reasonix(この記事で紹介したもう一つのコーディングエージェント)、DeepSeek Everywhere(DeepSeekをClaude Codeとエージェントスタックに組み込む)、DisPatch(そのペインが組み込まれているチャットアプリ)、およびbench-llm(ここでの私のやり方よりも本格的にローカルモデルをベンチマークするツール)。


← AIとローカルLLMをもっと見る