コンテンツにスキップ

Live Forking:同じタスクを並行して複数の道で走らせ、最良を選ぶ

本書は利用シナリオ(いつ使うべきか、いつ使うべきでないか)を扱います。仕組みとたとえ話は技術解説 28-live-forking.md を、完全な設計は RFC-26 を参照してください。

Live Forking は、実行中のタスクをその場で N 個の競合ブランチに分裂させます。各ブランチは別々のアカウント、独立したワークスペースのコピー、それぞれの予算で走り、走り終えたら AI ジャッジ(あるいはあなた自身)が最良の1本を選んでマージし戻します。デフォルトはオフで、エージェント単位に agent.toml [fork] enabled = true で有効化します。

一言で言う違い:普段はタスクが1本の道を進み、間違えたらやり直す。fork をオンにすると、ひとつのタスクが同時に複数の道を進み、どれが良いかを直接比較できます。


DuDuClaw のエージェントは claude CLI のサブプロセスとして動くため、「ブランチ」1本は隔離されたサブプロセス実行1回であり、以下を持ちます:

  • 自分のワークスペースオーバーレイ(copy-on-write:親ワークスペースは読めるが、書き込みは自分のコピーだけに落ち、互いを汚染しない)
  • 自分のアカウント(AccountRotator が別々のアカウントを割り当てるため、ブランチ同士が同じ rate limit にぶつからない)
  • 自分の予算上限と任意のステアリングメッセージ(steering:このブランチにどの方向を試すかを伝える)
  • 親ワークスペースへの読み取り専用の共有ビュー

各ブランチは走り終えた後、任意で test_command(例:pytest -q)を実行でき、その exit code がジャッジの採点に使われます。


シナリオ1:複数案を並行させ、最良を選ぶ

Section titled “シナリオ1:複数案を並行させ、最良を選ぶ”

ひとつの問題に妥当な解法が複数あり、どれが良いか確信が持てない。1つずつ試して失敗したら戻るより、3本のブランチを一度に開いてそれぞれ別の戦略を走らせ、結果を比較する方が良い。

典型例:「この遅いクエリ、1本はインデックス追加、1本は JOIN の書き換え、1本はキャッシュ追加を試し、実測で最速のものを採用する。」各ブランチは異なる steering を受け取り、ジャッジはテスト通過率と品質で採点し、merge_mode = "auto_with_fallback" は勝者を自動選択しつつ確認ゲートを1つ残します。

シナリオ2:高リスクの変更を、まずブランチの中で試す

Section titled “シナリオ2:高リスクの変更を、まずブランチの中で試す”

間違えると厄介なもの(大規模リファクタリング、コア設定の変更、破壊的マイグレーション)を動かす前に、実際にどう変わるのかを見てから着地させるか決めたい。

ブランチの copy-on-write オーバーレイはまさにこのためにあります:ブランチの書き込みはすべて自分のコピーに落ち、親ワークスペースはマージ前に一文字も変わりません。失敗したらそのオーバーレイを捨てれば親は無傷のまま。うまくいったときだけ勝者の書き込みをマージし戻します。失敗した試みがメインラインに一切触れないため、「先に commit してから revert」よりクリーンです。

シナリオ3:AI ジャッジによる評価

Section titled “シナリオ3:AI ジャッジによる評価”

「複数の結果から最良の1つを選び、なぜそれを選んだか説明できる」ことが必要で、「結果が出た」だけでは足りない場合。

ジャッジは JudgeVerdict を生成し、信頼度スコアは RFC-26 の式で計算されます:quality_spread·0.4 + test_pass_ratio·0.4 + internal_consistency·0.2。これはまさに会議 §15 で問われたシナリオです:1つのエージェントが複数の結果を出した後、別のエージェントを評価者として派遣する。fail-closed は意図的です:ジャッジが存在しない、判定が parse できない、sandbox が起動しない場合はすべて manual に退避し(全ブランチをあなたに提示します)、黙って適当に1つを選ぶことは決してありません。


Fork はデフォルトで開けておくものではありません。以下の場合は使わないでください:

  • 決定的な単一タスク:答えが1つしかなくトレードオフの余地がない(フォーマット変換、単純な照会)。ブランチを開いても N 倍のコストを無駄に燃やすだけです。
  • コストに敏感:ブランチ N 本 = アカウント N 個、予算 N 個分。サブスクリプション枠や API 費用が逼迫しているとき、fork は消費を増幅します。[fork] max_branchesaggregate_budget_usd はハードキャップですが、根本の問いはこのタスクに並行化の価値があるかどうかです。
  • 採点の根拠がないtest_command が空だと、ジャッジの式の test_pass_ratio は中立化され、採点は品質と一貫性だけになり、選抜の客観性が落ちます。実行できるテストがないタスクでは、fork の価値は主にシナリオ2(隔離した試行錯誤)にあり、シナリオ1(客観的な選抜)にはありません。
  • プロセスをまたいでブランチを強制停止したいterminate_branch は同じプロセス内のサブプロセスしか停止できません。別プロセスのブランチサブプロセスを殺すことは、RFC-26 が明記した by-design の除外項目です。

どちらも「AI ジャッジ」を使いますが、用途は正反対です。混同しないでください:

Live Forking duduclaw eval
タイミング 実行時、タスクが走っている最中 事後 / CI、オフライン回帰
目的 複数の道からその場で最良の1本を選んで進む 固定のゴールデンタスクでエージェントが退行していないか検証する
入力 生きているタスク1つ + N 種の戦略 evals/<suite>/<case>.toml の事前記録ケース
出力 勝者ブランチをワークスペースにマージ JSON レポート + 非ゼロ exit で PR をブロック

実装上、duduclaw eval の LLM ジャッジは duduclaw-fork のジャッジプリミティブを再利用しているため、評価ロジックは同じものです。覚え方:fork は前方への偵察、eval は後からの検収。eval の使い方は evals ガイド を参照してください。


agent.toml

[fork]
enabled = false # デフォルトはオフ。エージェント単位で有効化
max_branches = 4 # ブランチ数のハードキャップ(アカウント/枠の爆発を防ぐ)
default_budget_usd = 0.50 # ブランチごとの上限
aggregate_budget_usd = 1.50 # 全ブランチ合計の上限
merge_mode = "auto_with_fallback"
test_command = "" # 任意。空 ⇒ ジャッジの test_pass_ratio が中立化
test_timeout_s = 120

MCP ツール(エージェントが [fork] enabled = true を設定したときだけ登録され、すべて Scope::ForkExecute でゲート、fail-closed):

ツール 何をするか
fork_run 現在のタスクを N 本のブランチに分割
inspect_branches 生きているブランチ + 状態 + 支出を一覧
diff_branches 2本のブランチ間のファイル/出力の差分
merge_or_select fork を収束させる(ジャッジ判定またはあなたの指定)
terminate_branch 暴走したブランチを停止
fork_cost 合計とブランチごとの支出

fork の収束はすべて ~/.duduclaw/fork_history.jsonl と Activity Feed に記録され、ダッシュボードには可視化用の ForkPage があります。


Live Forking は、トレードオフがあり、リスクがあり、比較が必要なタスクに向いています。決定的なタスク、コストに敏感なタスク、採点しようのないタスクには向きません。オンにする前にひとつ自問してください:このタスクで、並行して複数の道を走らせて得る品質は、数倍の出費に見合うか。