はじめに

familiarのagentの作り方を大きく変えた。

これまではgateway、agent、infraをすべて内包したモノリシックなfamiliarとして自作してきた。今回、agent部分をまるごと外した。代わりにDeepSeek Harness(以下dsh)をコンテナPodの中に置いて、フル権限で自由に動かす。

familiarはfamiliar-daemonとagent-gatewayの2つに分けた。推論バックエンドを管理するancestorは前からあったので、そのまま使っている。この3つでPodの中のdshを動かす仕組みになっている。

移植は、Codex Astraがリリースされたあと、性能チェックも兼ねて1週間ほどでやった。

familiarを作り始めた経緯と初期設計は、前回の記事にまとめている。この記事はその続きになる。

動画

dshをPodの中でフル権限で動かし、compute上のvLLMで推論させているところを動画にした。

1本目は、deepseek-ai/DeepSeek-V4-Flash-Vision-Expを公式のcheckpointのまま動かし、Djangoのドメイン層を実装させたもの。設定と数値は後半の「Vision-Expでの実行内容」にまとめている。

動画リンク: https://www.youtube.com/watch?v=KD-4jm46izk

2本目は、最近リリースされたDeepSeek-V4.1-FlashのEXL3 2.0bpwで別に撮ったもの。モデルはdiffbotが公開している量子化モデルを使っている。

動画リンク: https://www.youtube.com/watch?v=CBkIkCtIxJI

これまでのfamiliar

前回の記事の時点では、familiarはgateway、agent、infraをすべて内包したモノリシックな作りだった。中には、かなりいろいろなものを持たせていた。

  • コンパクション
  • メモリー
  • サマライズ
  • ロールごとのメモリー管理
  • knowledge基盤の操作
  • 独自ツール
  • MCP。Goで書いていたので、embedでMCPのソースを丸ごと埋め込んだりもしていた
  • OpenTelemetry、Vector、Prometheus、Grafanaへのobservability
  • GrafanaとGraphQLで作ったwhat-ifやreplay
  • Dagster assetsのpipeline
  • shell実行を検証するコンテナの仕組み
  • ツールのリカバリー

前回の記事で「本体より、ツールを作っていた」と書いたとおり、ローカルLLMが苦手なところを周りの仕組みで補う作業が中心だった。

DeepSeek Harnessに乗り換えた

きっかけはdshのpluggableな設計だった。これがすごく良かった。

プラグインもどんどん追加されている。最近は、何か実装する前に検索すると、欲しいものが見つかることも出てきた。

dshのSettings画面でAgent presetsを開いたところ
SettingsのAgent presets。Standard、PTC、Minimal、Creatorのmodeと、Default、Coding、Planning、Testingなどのpresetが並ぶ。

GrafanaとGraphQLで自作していたwhat-ifやreplayも、プラグインで解決できた。たとえばThoughtDAGは、LLMに渡るcontextをグラフで見て編集でき、分岐させて別の流れを試せる。dsh向けのプラグインとしても提供されていて、desktop側のdshではChatの隣のDAGタブで使っている。

ThoughtDAGのSession Atlasでdsh-localのセッション一覧を開いたところ
ThoughtDAGのSession Atlas。ローカルのセッションをproject folderごとに並べ、選んだものをcanvasとして取り込む。canvas側の操作は元の会話ファイルに書き戻さない。
ThoughtDAGのDAG表示でDjangoのセッションから次の質問を分岐させたところ
DAG表示。Djangoのセッションをノードとして取り込み、そこからTailwind CSSでfrontendを作る質問を分岐させている。エッジでつないだものが次の質問のcontextになる。

プラグインはライブラリのような感覚で使えて、自分で作らなくても誰かが開発とメンテナンスをしてくれるのがありがたい。

3つのコンポーネント

agent部分以外を抜き出して、インフラ基盤の部分だけを移植した。役割はこう分けている。

コンポーネント実装役割旧familiarとの関係
familiar-daemon(famd)Godshへの依頼(delegation)の受付、隔離したworkspace、状態の永続化、復旧、結果の回収、Podの管理familiarから分けた
agent-gatewayGodshやOpenAI互換クライアント向けの推論gateway。起動中のAncestorインスタンスを見つけて、公開モデルIDでルーティングするfamiliarから分けた
ancestorRustcompute hostの推論サーバーの管理。presetから構成を作り、Podman Quadletとsystemdのuser serviceで起動、停止する前からある。そのまま使った

famdが持つのは、agentの実行と、自分の運用イベントをNATSへ出すところまで。OpenAI互換の推論エンドポイントや、prompt、tool call、responseのcaptureは持たない。そこはagent-gatewayの担当で、captureは非同期でNATSに流す。OpenTelemetryなどのobservabilityも、agent-gatewayのeventを流用して実装した。

推論サーバーの起動と停止はancestorの担当で、famdに依頼を投げても推論サーバーが勝手に起動したり止まったりはしない。

今の構成

flowchart LR
  subgraph desktop["desktop.home.arpa"]
    CLI["famdctl"]
    UI["dsh Web UI"]
  end
  subgraph compute["compute.home.arpa"]
    FAMD["familiar-daemon<br/>control / launcher / finalizer"]
    GW["agent-gateway"]
    ANC["ancestor"]
    VLLM["vLLM"]
    RELAY["famd-egress<br/>inference relay"]
    subgraph pod["runtime Pod"]
      DSH["DeepSeek Harness<br/>Full access"]
      WEB["Web relay"]
    end
  end
  NATS[("NATS")]
  CLI --> FAMD
  CLI --> GW
  CLI --> ANC
  FAMD --> pod
  ANC --> VLLM
  DSH --> RELAY --> GW --> VLLM
  GW -. discover .-> ANC
  FAMD -. events .-> NATS
  GW -. capture .-> NATS
  UI -. "SSH tunnel<br/>pod forward" .-> WEB

compute.home.arpaでは、famdのcontrol、launcher、finalizerなどと、agent-gatewayがネイティブのサービスとして動いている。

famd-egressは、Podから見える推論の入口。Podの中のdshはhttp://famd-egress:8080/v1に推論を投げ、famd-egressがそれをagent-gatewayへ中継する。Quadletのコンテナで、root filesystemはread-only、capabilityもすべて落としている。

操作はdesktopのfamdctlから行う。依頼はdelegationとして投げ、受け付けられるとruntime Podができてdshが動き出す。Podの中身はdshのコンテナとWeb relayの2つで、dshはPodの中ではフル権限で自由に動かしている。フル権限にしている理由と注意点は、後半の「フル権限について」に書いた。

famdctlでの操作

famdctlはこういうコマンド構成になっている。

  ksh3@desktop.home.arpa ~ % famdctl
famdctl [--json] [--config path] [--idempotency-key key] <command>

  ancestor    List, inspect, plan, ensure or stop Ancestor services
  delegation  List, submit, inspect, wait for, retrieve or cancel a delegation
  session     List, start, recover, observe or close a DSH session
  events      Stream lifecycle events, resuming with --after
  artifact    Inspect or download a verified artifact
  pod         List, start, stop, remove, drain or forward runtime Pods
  dsh-local   Prepare, sync and open the local DSH Web UI
  workbench   Alias for dsh-local open
  workspace   Initialize or locate the local work records and DSH mirror
  gateway     List public models
  config      Initialize or inspect saved settings
  doctor      Check configuration; --online probes supported service APIs
  completion  Print a bash or zsh completion script

Use famdctl <command> --help for details. Output is formatted for reading.
Add --json anywhere before -- for raw JSON responses and JSONL events.
Exit status: 0 success/waiting, 1 service/local/terminal failure, 2 usage error.
Root submit/run, status, wait, result and cancel are delegation shortcuts.
Save endpoints once with config init, then use alias fam=famdctl.
  

ancestorがancestor、gatewayがagent-gateway、delegationsessioneventsartifactpodがfamdへの操作になっている。3つのサービスは接続先と認証情報を別々に持たせている。

Djangoのドメイン実装タスクを投げたときの操作の例。--detachで投げると受付だけ返ってくる。

  ksh3@desktop.home.arpa ~/familiar-workspace % famdctl delegation submit --workflow django --detach /Users/ksh3/familiar-workspace/task/task.json
Work record: /Users/ksh3/familiar-workspace/workflow/django/2026-09-15/18-03-25
STATUS    DELEGATION           SESSION              ACCEPTED
accepted  del_W9hVQjDgA2zzXQ   ags_NPoXgKU5IGYmAg   2026-09-15 18:04:17
Receipt only; execution is not confirmed.
Next: famdctl delegation wait del_W9hVQjDgA2zzXQ
  

Podの中のdshの様子は、pod forwardでcompute.home.arpa経由のトンネルを張ってブラウザで見る。

  ksh3@desktop.home.arpa ~/familiar-workspace % famdctl pod forward del_W9hVQjDgA2zzXQ
http://127.0.0.1:13080/?token=<redacted>
Pod pod_EaTd58jSdfE2kA is forwarded through compute.home.arpa; press Ctrl-C to close the tunnel.
  

Web UIにはChatとTrajectoryのタブがあり、To-dosの作業計画や、Pod内の/workspaceのファイルを見られる。Podで動いたセッションはdesktopの~/familiar-workspace/dsh-local/Remote/にも同期されて、desktop側のdshからRemote / artifact_…として開ける。

desktop側のdshでDjangoのドメイン実装セッションのChatを開いたところ
desktop側のdsh。左のWorkspacesに、Podから同期したセッションがRemote / artifact_…として並ぶ。
dshのTrajectoryタブでgate midの失敗stepを開いたところ
Trajectoryタブ。上にInput、Model、Toolsのタイムライン、左にtool callの一覧、右に選んだstepの詳細が出る。Turn 1 Step 33ではgate midがmodel_defectで失敗し、このあとcore/models.pyを直してgateを通している。

compute-server側で、vLLMもPodも動いていないときのpodman psはこうなっている。

  ksh3@compute-server:~$ podman ps
CONTAINER ID  IMAGE                                                                                                            COMMAND               CREATED         STATUS         PORTS       NAMES
77af06a3024b  registry.home.arpa/ml-foundry-mlflow@sha256:e0916a7bce42adc92f5b688e212ece81c0b1ce363e92eaf02b1118161f23cf3b     mlflow server --h...  5 hours ago     Up 5 hours                 mlflow
2144fc65532d  registry.home.arpa/ml-foundry@sha256:df3d06efff1570a47085c8002d980d66085c01c69b5d07d5afab98c4f482c888            dagster api grpc ...  5 hours ago     Up 5 hours                 dagster-user-code
381a9063ea00  registry.home.arpa/ml-foundry@sha256:df3d06efff1570a47085c8002d980d66085c01c69b5d07d5afab98c4f482c888            dagster-webserver...  5 hours ago     Up 5 hours                 dagster-webserver
9df064d0e85b  registry.home.arpa/ml-foundry@sha256:df3d06efff1570a47085c8002d980d66085c01c69b5d07d5afab98c4f482c888            dagster-daemon ru...  5 hours ago     Up 5 hours                 dagster-daemon
c9849a9fd9a5  registry.home.arpa/famd-inference-relay@sha256:702149bdc55611ef25ba40d98c0181d55e9d786507ad5b991e17ccbc96bd4ed4  --upstream http:/...  17 minutes ago  Up 17 minutes              famd-egress
  

famd-egressが推論のrelay。famdのcontrolやagent-gatewayはネイティブのサービスなので、ここには出てこない。

DagsterとMLflowはml-foundry。以前のmodel-foundryを、これもagent-gatewayに合わせて作り直したもので、モデルだけでなく、趣味の投資用のML pipelineも追加した。

ml-foundryの投資用ML pipelineの結果を表示するGrafanaダッシュボード
ml-foundryで作っている投資用ML pipelineのGrafana。候補銘柄と時間帯ごとに、当日のシナリオ予測を見ている。

Vision-Expでの実行では、これに加えてancestorが起動したvLLMのancestor-deepseek-v4-flash-vision-expと、Podのdsh runner、dsh webが動いていた。Podの2つは同じfamd-dsh-runnerイメージで、--managedがdsh本体、--proxy-onlyがWeb relayになる。Podは127.0.0.1のポートを1つだけ公開していて、Web UIへはそこにトンネル越しでつながる。

Vision-Expでの実行内容

1本目の動画では、DeepSeek-V4-Flash-Vision-Expをcompute上のvLLMで動かし、Podman Podの中のdshにフル権限を渡して、Djangoのドメイン層を実装させている。モデルへのリクエストはホストの外に出ない。

構成

  MODEL: deepseek-ai/DeepSeek-V4-Flash-Vision-Exp @ 6821d6ad3681a4b137b066b76094fa82ebd0a380
IMAGE: registry.home.arpa/voipmonitor/vllm:ds4-jovian-r9-5bea08859798
HOST:  compute.home.arpa:8000 (engine) <- pod forward <- desktop.home.arpa (client)
POD:   dsh runner, dsh web
GPU:   NVIDIA RTX PRO 6000 Blackwell Max-Q ×2 (GPU 0,1 / 300W cap)
  
項目設定
context1M(max-model-len 1048576)、最大出力393,216 tokens
重み公式checkpointのまま。FP8(e4m3、128×128 block)、expertはFP4、それ以外はbfloat16。追加の量子化はしていない
並列TP2(tensor-parallel-size 2)、NVLinkなし
speculative decodingDSpark fixed-probabilistic K3(3 token)
KV cacheGPU KVのみ、LMCacheなし
GPU memorygpu-memory-utilization 0.968
同時リクエスト4(max-num-seqs 4
batchmax-num-batched-tokens 4096
reasoning efforthigh
その他tool calling有効、vision有効、OpenAI互換のchat completions API

vLLMのserviceのExecStartはこれ。env fileはancestorのactiveディレクトリから読んでいる。

  ExecStart=/usr/bin/podman run --name=ancestor-deepseek-v4-flash-vision-exp --cidfile=%t/%N.cid --replace --rm --cgroups=split --network=host --init --sdnotify=conmon -d --ulimit stack=67108864:67108864 --device=nvidia.com/gpu=0 --device=nvidia.com/gpu=1 -v /mnt/data/hf/hub/models--deepseek-ai--DeepSeek-V4-Flash-Vision-Exp:/models:ro -v /mnt/data/hf/jit/ds4-vision-exp-jovian-r9:/cache -v /mnt/data/hf/tmp/ds4-vision-exp-jovian-r9:/container-tmp --env-file /home/ksh3/.local/state/ancestor/active/ancestor-deepseek-v4-flash-vision-exp.env --pull never --entrypoint=/usr/local/bin/lmcache-mp-wrapper.sh --ipc=host registry.home.arpa/voipmonitor/vllm:ds4-jovian-r9-5bea08859798 /usr/local/bin/serve-ds4-flash.sh
  

serving profileは、local-inference-lab/rtx6kproで公開されているDeepSeek-V4-Flash Jovian Judgement r9のVision向けspecをベースにした。gpu_memory_utilizationだけは、ドキュメントにある値(GPU KVのみなら0.975、LMCacheを使うなら0.970)ではなく0.968にしている。

entrypointはLMCacheのwrapper(lmcache-mp-wrapper.sh)を通るが、LMCACHE_MODEを設定していない。なのでhost側のtierは使われず、GPU KVだけの実行になっている。

同じRTX PRO 6000 2枚で、DeepSeek V4 Flash 0731とDSpark K5を測った記録は別の記事に書いている。モデルも設定も違うので、数字は並べて比べていない。

engineログの数値

約7分のengineログの範囲で、chat completionのリクエストは70件だった。

指標
decode throughput(平均)180 tok/s(ピーク257.9 tok/s)
prefill throughput(prompt処理中の窓の平均)444 tok/s
DSpark draftの採択53,246 / 72,615 tokens(73.3%)、depth 3での平均採択長3.20
prefix cache hit rate87.5% → 98.0%
decodeだけの窓(平均)216.6 tok/s
prefillが混ざる窓(平均)156.3 tok/s

prefix cache hit rateは、窓の中で87.5%から98.0%まで上がっていった。途中、新しいprefixが入ってきたところで3回下がっている。

decodeだけの窓とprefillが混ざる窓を比べると、同じengineの上でchunked prefillがdecodeを28%くらい削っている計算になる。

動画のdshのステータスバーには201 tok/s、cache hit 93%と出ている。これはクライアント側が別の期間、別の分母で数えている値なので、上のengineのカウンターと一致するものではない。

数字の注意点

数字はどれもengineログの10秒ごとの窓の平均で、TTFT、ITL、リクエストごとのlatencyは取っていない。token数の合計も、正確なカウンターではなく「rate×10秒」からの概算になる。

また、r9の公開値は600WのWorkstationカードで測られている。こちらは300WのMax-Qなので、そのまま比べられる数字ではない。

フル権限について

フル権限にしているのは、以前のfamiliarで一番苦しんだのが過剰なガード機構だったから。ガードのせいで負のスパイラルに陥り、品質がどんどん落ちていった。旧版の時点で、複雑なパイプなどのshellだけはコンテナ側でdry-runしてから実行する仕組みにして、ガードはなくしていた。

今年の3月頃と比べると、OSSモデルの性能はかなり上がっている。ds4glm5.3-flashqwen38-flash-nextは普通に作業で使っている。

とはいえ、制限なしで実行させる以上、破壊的な操作のリスクはそのまま残る。試すなら、隔離してスナップショットを取ってからにしてほしい。

所感

6ヶ月位自作agentを合間で作っていたけど、とても楽しかった。 モデル選定や実際に動かしたときの課題などもよくよく分かってMCP開発もずいぶんやってきた。 僕はできるだけOSSからコードを借りてきて、自分ではできるだけ書かないスタイルが好きだったけど、 AIコーディングのおかげでここ半年は車輪の再発明が楽しくて没頭していた。

dshはプラグインに対応していて、コミュニティもできてきている。1から作らなくても継続してメンテナンスされる状態で借りられるようになるのも、そう遠くないと思う。 それに、中国の技術力はすごくて、今もすごい勢いでいろんなツールが公開されている。なんというか場が盛り上がっているので、いろんな技術的恩恵を受けられるという考えもあって、自分で作っていたagentは捨てて、dshに載せ替えた。 ローカル環境でagentを動かすときの参考になればうれしい。