ダークファクトリーは .dot ファイルだった
← 全記事StrongDM がコーディングエージェントのパイプラインランナーを構築するための自然言語スペックを公開した。Dan Shapiro がひとつ作った。私たちはさらに 3 つ作った。それらすべてが——独立して、2 つの言語で、異なる目標を持つ異なる人々によって——同じ 3 層アーキテクチャに行き着いた。
このことが頭から離れない。コードではなく、収束のほうが。それが不思議な部分だ。
アトラクターパターン
2 月に StrongDM は attractor をオープンソース化した。統一 LLM クライアント、コーディングエージェントループ、DOT ベースのパイプラインエンジンを記述した 3 つの自然言語スペックだ。スペックはコードではない。散文だ。約 5,700 行。コーディングエージェントに渡して「これを作って」と言えるほど詳細に書かれている。そして実際に作れる。
名前はダイナミカルシステムから借りた——アトラクターとは、システムが収束しようとする状態のことだ。StrongDM の賭けは、これらのスペックが問題に対して非常に自然な設計を記述しているため、独立した実装が同じ場所に収束するだろう、というものだ。大胆な主張だが、実際にそうなった。
彼らはまた AttractorBench もリリースした。コーディングエージェントが自然言語スペックからシステムをどれだけうまく実装できるかを測定するベンチマークだ。段階的になっている——スモークテスト、統一 LLM SDK、コーディングエージェントループ、フルパイプラインランナーの順に進む。言語非依存で、エージェントが実装言語を自分で選ぶ。唯一の契約は make build、make test、そしてモック LLM サーバーに対するコンフォーマンステストスイートだ。実際の API 呼び出しはない。決定論的な検証。コストを意識したスコアリング。「作れたか?」を問うだけではなく、「スペックにどれだけ忠実に従い、コストはいくらかかったか?」を問う。
Dan Shapiro はしばらくこの進歩について考えていた。1 月に彼は “The Five Levels: from Spicy Autocomplete to the Dark Factory” を公開し、NHTSA の運転自動化レベルを AI 支援コーディングに当てはめた。レベル 0 は vi だ。AI なし。すべての文字は自分のもの。レベル 2 は今の「AI ネイティブ」開発者のほとんどが生きている場所——モデルとペアプログラミングをして、生産性を感じている。レベル 4 では PM になっている。スペックを書き、スペックについて議論し、12 時間離れて、テストが通るか確認する。
レベル 5 がダークファクトリーだ。照明オフ。誰もコードをレビューしない。誰も見すらしない。
「ダークファクトリー」という言葉は製造業から来ている——ロボットが照明なしで動かすファクトリー。ロボットは見る必要がないからだ。具体的には、日本の Fanuc Robotics が 2003 年頃に実践したものだ。
ソフトウェアに適用すると、同じくらい恐ろしく、同じくらいわくわくする。
StrongDM のデモの後、Shapiro は “You Don’t Write the Code. You Don’t Read the Code Either.” を書き、そして Kilroy を作った。ローカルファースト Go CLI で、分離された git ワークツリー内で attractor パイプラインを実行し、実行履歴とチェックポイント回復のために CXDB を使う。もう一つの独立した実装。同じ 3 層構造。
どろだんご、あるいは:なぜ私たちは 3 つ作ったか
Jesse Vincent が どろだんごについてブログ記事 を書いた——泥の球を光沢のある球に磨き上げる日本の芸術。Wikipedia の「mud ball」の曖昧さ回避ページは「Big Ball of Mud」というソフトウェアのアンチパターンにリダイレクトされる。Jesse はそれを活かした。このフレームが好きだ。
彼の主張:コードジェン ソフトウェアは使い捨てだ。丁寧にスペックを書いて、エージェントに渡し、出てきたものを磨く。結果が根本的に間違っているとき、デバッグで救われようとはしない。捨てて、スペックから作り直す。彼はエージェントのエンドツーエンドテスト録画が e2e-test-full-run-33.mp4 という名前で保存されているのを発見したときのことを書いていた。1 から 32 のランはエージェントが問題を一つずつ解決していく過程だった。33 回目が成功した。かなりかっこいい。
これが、私たちが二度考えることなく 3 つの attractor 実装を作れた精神的モデルだ。ソフトウェアは今や安い。スペックが高価な部分だ。
Mammoth と Smasher は同じスペックから並行して作られた。Go で書かれた Mammoth は最高の形でスコープを広げた——21 ルールの DOT リンター、設定可能なジョインポリシー(all-success、majority、first-success)を持つファンインノード、ゼロトークンコストでシェルコマンドを実行する検証ノード、5 フェーズのノードライフサイクルが追加された。全体的なスペックエンジンになった。本当にかっこいいが、かなり大きい。Rust で書かれた Smasher はスリムさを保った:LLM クライアントから Web ダッシュボードまでの 5 つのクレート、ライブ SSE ストリーミングとグラフ可視化を備えた HTMX フロントエンド、6 つの組み込みエージェントツール、そして単純に話したいときのための smasher chat REPL。Smasher は日常的に実際に使われているものだ。
Tracker は後から来た。よりシンプルだ。Go、bubbletea TUI、.tracker/runs/ への自動チェックポイント、バックオフ付きリトライ。それでも同じ形状に収束する、週末規模の実装だ。
なぜなら、すべてがそうなるからだ。これらすべて——Kilroy、Mammoth、Smasher、Tracker——は例外なく 3 層になる:
| レイヤー | Kilroy (Go) | Mammoth (Go) | Smasher (Rust) | Tracker (Go) |
|---|---|---|---|---|
| LLM Client | Provider adapters | llm/ — unified OpenAI/Anthropic/Gemini | smasher-llm — streaming, retries, provider quirks | Provider client with trace introspection |
| Agent Loop | Coding agent with tool dispatch | agent/ — steering, loop detection, subagents | smasher-agent — 6 tools, steering rules, subagents | LLM-powered nodes with context injection |
| Pipeline Engine | DOT parser, CXDB checkpoints, worktree isolation | attractor/ — DOT parser, graph engine, node handlers | smasher-attractor — winnow parser, tokio broadcast | DAG walker, checkpointing, human gates |
誰もこれを調整していなかった。スペックがそこに引き寄せた。それがアトラクターだ。
パイプラインこそが製品だ
さて、ここが私をずっと悩ませていることだ。ファクトリーの実装はオープンソースで、増え続けている。いいことだ。だがパイプラインファイル——ファクトリーが実際に何を作るかを記述する DOT グラフ——はほとんど非公開だ。みんなエンジンを共有して、設計図を隠している。
ちょっとした補足——私の人生ずっと、ドットファイルというのは .bashrc や .vim のようなものだった。ここで話しているのは Graphviz の .dot ファイルのことだ。Justin が最初に自分のファクトリーを見せてくれたときに初めて知った。mermaid の先祖、みたいなものだ。
パイプライン DOT ファイルは再利用可能な設計図だ。ワークフローを記述する:どのステップが LLM を必要とするか、どこで人間のゲートが必要か、どこで並行ブランチにフォークするか、進む前にどの検証コマンドを実行するか。標準的な Graphviz 構文。独自仕様は何もない。そして正直に言うと、パイプラインのほうがランナーよりはるかに面白い。
私たちはこれをたくさん書いてきて、2 つのまったく異なるスタイルが浮かび上がってきた。
最初のスタイル——Tracker のサンプルからの脆弱性アナライザー(vulnerability_analyzer.dot):

digraph VulnerabilityAnalyzer {
graph [
goal="Run a deterministic static vulnerability scan against a known
vulnerable application and emit a report with evidence.",
rankdir=LR,
default_max_retry=1
];
Start [shape=Mdiamond];
Exit [shape=Msquare];
CloneTarget [
shape=parallelogram,
label="Clone vulnerable target",
tool_command="set -eu
mkdir -p .ai/vuln
git clone --depth 1 https://github.com/digininja/DVWA.git .ai/vuln/target
printf 'ready'"
];
StaticScan [
shape=parallelogram,
label="Run static scan",
tool_command="set -eu
rg -n 'mysql_query\\(|eval\\(|shell_exec\\(' .ai/vuln/target > .ai/vuln/findings.txt
printf 'scanned'"
];
WriteReport [
shape=parallelogram,
label="Write vulnerability report",
tool_command="set -eu
count=$(wc -l < .ai/vuln/findings.txt)
echo \"# Report\" > .ai/vuln/report.md
echo \"Finding count: $count\" >> .ai/vuln/report.md
printf 'report_written'"
];
Start -> CloneTarget -> StaticScan -> WriteReport -> Exit;
}
すべてのノードが tool_command——単なるシェルスクリプトだ。LLM 呼び出しなし。トークンコストなし。決定論的で、再現可能で、秒単位で実行される。グラフ_そのものが_プログラムだ。最高だ。
次に、Mammoth のサンプルから別のスタイルと比べてみよう。これは build_pong.dot、Pong ゲームを構築するパイプラインだ:

digraph build_pong {
graph [
goal="Build a two-player Pong TUI game in Go",
retry_target="implement",
default_max_retry=3,
model_stylesheet="
* { llm_model: claude-sonnet-4-5; llm_provider: anthropic; }
.code { llm_model: claude-opus-4-6; llm_provider: anthropic; }
"
]
start [shape=Mdiamond]
done [shape=Msquare]
plan [label="Plan", class="planning", prompt="Plan the architecture..."]
scaffold [label="Scaffold", class="code", prompt="Initialize Go module..."]
implement [label="Implement", class="code", prompt="Write the full game...",
goal_gate=true, max_retries=3]
compile [label="Compile", class="code", prompt="Run go build and go vet..."]
compile_ok [shape=diamond, label="Compiles?"]
review [label="Review", class="review", prompt="Review all generated code..."]
start -> plan -> scaffold -> implement -> compile -> compile_ok
compile_ok -> review [label="Pass", condition="outcome=success"]
compile_ok -> implement [label="Fail", condition="outcome=fail"]
review -> done [label="Pass", condition="outcome=success"]
}
このスタイルはビルドレシピだ。すべてのステップ——計画、足場作り、実装、レビュー——で LLM に頼る。CSS のようなセレクターをプロバイダーにマッピングする model_stylesheet があり、これはかなり賢い。また高価で、遅く、非決定論的でもある。
私たちは最初のスタイルを好むようになった。決定論的にできるものにはシェルコマンドを使ったツールノード。本当に推論が必要なところにだけ LLM ノード。脆弱性アナライザーは秒単位で動き、コストはゼロだ。Pong ビルダーは 20 分かかり、API 呼び出しに 15 ドルかかり、しかも毎回同じゲームが得られない。午前 2 時にスマホから実行したいのはどちらだろう。
最も興味深いパイプラインは両方を組み合わせる:セットアップ、検証、デプロイには決定論的なツールノードを使い、本当にモデルに考えさせる必要があるポイントにのみ LLM ノードを使う。Tracker のスプリント実行パイプライン(sprint_exec.dot)がこれをやっている——台帳管理とビルド検証にはシェルスクリプト、実装とレビューには LLM ノード、そして出荷するかループバックするかを最終的な統合が決める前に、3 つのモデルが並行ファンアウトでお互いのレビューを批評する。

そして dotpowers.dot がある——Jesse’s の Superpowers を DOT ファイルにクローンしようとした私たちの試みだ。目標は、ソフトウェア開発ライフサイクル全体を単一の DOT ファイルに符号化することだ。7 つのフェーズにわたる 53 ノード:人間とのブレインストーミング、デザインブリーフの作成、計画の下書きと監査、プロジェクトのセットアップ、エスカレーションパスを持つ TDD ループでのタスク実装、クロスクリティーク付きマルチモデルレビュー、そして最後にマージ、PR 作成、または廃棄で終わる。すべての意思決定ポイントに人間のゲート。3 つの異なる LLM プロバイダーが敵対的レビューを行う。パイプラインが永遠にループするのではなく、優雅に失敗するためのリトライバジェット。

1 ファイル。標準的な DOT 構文。Mammoth で動く。パイプラインをスクリプトとして考えるのをやめて、プロセス定義として考え始めてから初めて意味をなすものだ。シェルスクリプトというより、BPMN ダイアグラムに近い。不思議だ。けっこう好きだ。
dot ファイルを共有しよう
ファクトリーコードはどろだんご——磨き、捨て、スペックから作り直せばいい。パイプラインファイルこそが永続するアーティファクトだ。共有する価値があるのはそちらだ。
だから共有してほしい!あなたの「Rails アプリを監査する」パイプラインはどんな形をしている?「新しいエンジニアをオンボードする」グラフは?「モバイルリリースを出荷する」DAG は?.dot ファイルを gist に貼り、ブログに投稿し、どこかに PR を送ってみてほしい。ダークファクトリーパターンは実在し、再現可能であり、エージェントはスペックからファクトリーを作れる。
もはやファクトリーの作り方を問う時代ではない。それで何を作るかを問う時代だ。




