コンテンツにスキップ

自律ゴールループ(Goal Loop)

会話の中でゴールを1つ渡すだけで、AI従業員は自律的に計画・実行・自己検収し、完了または行き詰まったときに戻ってきて知らせてくれます。このページでは、チャンネルから /goal を使う方法、自律度(AutonomyLevel)の段階、関連する設定キー、そして人による対応が必要になったときのボタンの意味を説明します。

ディスパッチエンジンは v1.59 以降デフォルトで有効です(ゴールタスクがないときは定期的な SQLite ポーリングのみを行い、タスクが実際に review に入って初めて LLM 呼び出しが発生します——後述の「二段階検収判定」を参照)。通常の一問一答の会話には一切影響しません。不要な場合は config.toml[dispatch] enabled = false を設定するか、ダッシュボードの「設定 → 自動化」で「ディスパッチエンジン(派工引擎)」のスイッチを切ってください(再起動不要でホットリロードされます)。


接続済みのどのチャンネル(Telegram / Discord / Slack / LINE / …)からでも、AI従業員に対して次のように入力します。

コマンド 動作
/goal <ゴールの説明> 現在の会話の AI従業員に割り当てられた自律ゴールタスクを作成します。検収基準を別途指定しない場合は、ゴールの説明そのものが検収基準になります。
/goal <ゴール> || <検収基準> || で区切ります。前半がゴール、後半が検収基準(ジャッジが照合する基準)です。
/goal <ゴール> || <検収基準> || outcome:<spec> さらに構造化された成果物検収の層を追加します(後述の「構造化された成果物検収」を参照)。納品前にゼロコストの決定的チェックを実行し、基準未達ならジャッジを呼ばずにそのまま修正へ差し戻します。
/goal status 現在の AI従業員が進行中のゴールタスクを一覧表示します(短縮コード/状態/何ラウンド目か)。
/goal 使い方を表示します。

/goal この顧客データをまとめて月次レポートを作り、送信して || レポートには月次売上グラフを含め、boss@example.com に送ること
/goal Q3の月次レポートを作成 || 月次売上グラフを含めること || outcome:files:report.docx

作成すると、タスクの短縮コード、ラウンド上限、そして「完了または行き詰まったらここで通知します」という確認メッセージが返信されます。タスクの進捗と人的対応が必要な通知は、AI従業員の [proactive] 通知チャンネルだけでなく、あなたがゴールを作成したこの会話(発信元チャンネル)にも押し戻されます

ディスパッチエンジンが無効([dispatch] enabled = false)の場合でもタスクは作成されますが、確認メッセージで自動的には実行が始まらない旨が案内されます。


ゴール契約:作成時に凍結され、後からこっそり変更できない

Section titled “ゴール契約:作成時に凍結され、後からこっそり変更できない”

ゴールを作成した瞬間、検収基準は不変のベースライン(acceptance_criteria_baseline)として凍結されます。以後のすべての判定——一次評価器、MAV検収ジャッジ——はこの凍結されたベースラインのみを読み、後から編集される可能性のあるフィールドは決して参照しません。これは両方向の「契約のこっそり変更」を防ぐためのものです。AI従業員がタスク実行中に自分で検収基準を緩めることはできず、また運用者もダッシュボードのフィールドを編集しただけで判定基準そのものを変更したと誤解することがありません。

  • AI従業員は変更できない:エージェント身分で MCP の tasks_update を呼び出し、自分のゴールタスクの acceptance_criteria を変更しようとしても、その呼び出しはまるごと拒否され、監査ログに1件記録されます(理由:goal_contract_frozen)。
  • 運用者は表示用のコピーを編集できるが、判定基準そのものは変わらない:ダッシュボードの tasks.update はタスク上に表示される acceptance_criteria(人が読むための補足など)を編集できますが、凍結されたベースライン値は連動して変わりません。ジャッジと評価器は引き続き、タスク作成時に設定された基準で判定を続けます。本当に検収基準を変えたいなら、それは新しいゴールを作ることと同じです。
  • 凍結ベースラインを持たない古いタスク(この仕組みが導入される前に作成されたもの)は、変更可能なフィールドを読む従来通りの挙動にフォールバックします。

検収基準を指定しなかった場合のガイダンス

Section titled “検収基準を指定しなかった場合のガイダンス”

/goal <ゴールの説明>|| なし)でもいつも通りタスクは作成され、ゴールの説明そのものが検収基準になりますが、確認メッセージには次回に向けてより明確にするためのヒントが1段付け加わります。

💡 今回は検収基準を別途指定していません。次回はこの4点を考えておくとより的確になります。
・ゴール:何を達成したいか
・入力:どんなデータや素材が必要か
・出力形式:成果物がどんな形か(例:Word/Excel/文章/画像)
・制約:スタイル、期限などの制限、そして「何をもって完了とするか」
具体的で結果が確認できる検収基準を3〜5個用意してみてください(「よくできている」ではなく「レポートに月次売上グラフを含む」のように)。
/goal 説明 || 基準 の形式で再提出すればOKです。

|| で検収基準を明示している場合、このヒントは表示されません。planner_enabled を有効にしたサブタスク分解も同じ規律に従います。各検収基準は「結果」を書き「やり方」は書かない(HOW を凍結すると、別の正しい経路で作ったのに誤って失敗判定される可能性があります)、3〜5個に絞る(契約が重くなるほどサブタスクは収束しにくくなります)、範囲外の事項は検収基準に無理やり詰め込まず Non-goals として扱う、という点です。


ゴールタスクの状態が遷移するたびに、発信元の会話へ短い(1〜3行程度の)進捗メッセージが押し戻されます。

  • 実行開始/再試行(上限中の第Nラウンド)
  • 検収中
  • 却下 → 修正して再試行(ジャッジのフィードバック要約付き)
  • 完了 ✅(結果の要約付き)
  • 行き詰まり → あなたの判断が必要(同時に承認ボタンを送信、一時停止理由の分類付き——後述の「needs_human ボタンの意味」を参照)

同じタスク・同じ状態は重複して通知されません。発信元の会話が存在しない場合は AI従業員の [proactive] チャンネルにフォールバックし、両方とも存在しない場合はダッシュボードの Activity Feed に書き込まれるだけで、あなたを煩わせません。

タイムアウト時の進捗レポート

Section titled “タイムアウト時の進捗レポート”

タスクが認領(in_progress)された後、[goal_loop] progress_report_minutes(デフォルト10分)を超えても観測可能な進捗シグナルが一切ない場合(Activity Feed のイベントを基準とします——updated_at フィールドは lease renewer によって定期的に更新されるだけなので「動いている」証拠にはなりません)、ドライバーは「実行開始からX分経過しても進捗報告がなく、まだ実行中です」という通知を1件送信します(Activity Feed +発信元の会話)。同一ラウンド内では最大1回のみです。

これはあくまで通知であり、介入ではありません。再ディスパッチも、エスカレーションも、タスクのキャンセルも行いません。実際に手を動かすのは従来通り stalled_secs(再ディスパッチ)、iteration_cap(人的対応へ)、wall_clock_hours(人的対応へ)の各ガードだけです。0(または負数)に設定するとこの機能全体がオフになり、オフの間は追加のクエリも一切発生しません。


ツール連打アドバイザリー(tool streak)

Section titled “ツール連打アドバイザリー(tool streak)”

自律ループの中で、AI従業員が「同じツールを同じパラメータで何度も繰り返し呼び出す」ループにはまることがあります。結果はとっくに得られているのに、自分が繰り返していることに気づいていない状態です。これは後述の「停滞検知」とは異なります。停滞検知は連続する2ラウンド(ディスパッチ→検収)が揃って初めて行き詰まりを検知しますが、ツール連打アドバイザリーは1ラウンド内のツール呼び出しの並びを見ており、ジャッジが介入する前に先に注意を促せます。

判定基準は同じツール・同じマスク済みパラメータ(監査ログですでに行っている機密マスキングを再利用)の連続呼び出し回数で、閾値ごとに警告の強さが上がっていきます。

連続回数 警告内容
3回 前回の実行結果を読み直し、必要な情報がすでに得られていないか確認することを提案し、無駄な繰り返しでラウンドを消費しないよう促す
5回 現在のやり方では進展していない可能性があると指摘し、別の方法や角度を試すよう提案する
8回 繰り返しを止めるよう強く提案する。現時点で得られている結果をまとめて報告するか、tasks_block を呼んで何に阻まれているかを説明し助けを求める

このリマインダーは次のラウンドのディスパッチの <state> ブロックに注入され、LLMコストはゼロで純粋にアドバイザリーです。ツール呼び出しやディスパッチをブロックしたり、リトライさせたり、拒否したりすることは一切なく、従うかどうかは AI従業員自身の判断に委ねられます。リマインダー自体は意図的に state_hash から除外されており、既存の(状態、行動)振動検知を妨げません。

config.toml [goal_loop] tool_streak_advisory(デフォルト true)でこの機能全体をオフにできます。


各 AI従業員の自律度は agent.toml [capabilities] autonomy_level という1つのダイヤルで制御されます。未設定または解析不能な場合はデフォルトの Approver(保守的:行き詰まったときや人的対応が必要なときだけ質問する)になります。

レベル 動作
operator ループはまったく自律駆動しません。タスクは作成後静止し、人が手動で進めます。
collaborator 最初のディスパッチ前に人による承認(キックオフ承認)が必要です。承認後は完了まで自律的にリトライします。
consultant collaborator と同じキックオフ承認が必要です。
approver デフォルト。キックオフのゲートはなく、行き詰まったとき、または本当に人的対応が必要なときだけエスカレーションします。
observer 完全自動。人的対応が必要な場合も通知するだけで待機しません(タスクは自動的に終了します)。
agent.toml
[capabilities]
autonomy_level = "approver"

タスク単位の権限付与(scoped_tools、v1.41)

Section titled “タスク単位の権限付与(scoped_tools、v1.41)”

高リスクなツールは「権限付与がなければ使えない」と宣言できます。scoped_tools に列挙されたツールは、AI従業員が有効な権限付与(grant)を得るまで一律拒否され、しかも権限は単一タスクのライフサイクルの中でのみ生きています。そのタスクが終了(受理・却下・人的対応へ・キャンセル)した瞬間にすべて自動的に取り消され、次のタスクへ持ち越されることはありません。

agent.toml
[capabilities]
scoped_tools = ["shared_wiki_delete", "odoo_execute"] # これらのツールはタスクごとの権限付与が必要
grant_ttl_secs = 3600 # 権限付与が生存できる秒数の上限、デフォルト 3600

権限付与を得る方法は2つあります。

  1. AI従業員自身が申請する:MCP ツール capability_request { tool, reason, task_id? } を呼び出すと承認リクエストに変換され(他の承認と同じ通知/ダッシュボードの導線を使います)、あなたが承認すれば権限が有効になります。期限までに判断されなければ拒否扱いになります。
  2. ゴールタスクの開始時にまとめて付与する:ゴールタスクの tags に grant:<ツール名> を追加しておくと、キックオフ承認(collaborator/consultant レベル)が承認された時点で原子的に付与され、タスク終了時に自動的に回収されます。

判定は常に fail-closed です。権限データベースが読めない場合は「権限なし」として扱われます。scoped_tools に列挙されていないツールはまったく影響を受けません。


[dispatch]
enabled = true # 自律ディスパッチエンジン(goal loop ドライバーを含む)を有効化。デフォルト false
policy = "fixed_hierarchy" # ディスパッチポリシー(どの AI従業員がタスクを引き受けるか)。後述「ディスパッチポリシー」を参照。デフォルト fixed_hierarchy
grounding_precheck_enabled = true # 検収前のグラウンディング事前チェック(「グラウンディング事前チェック」を参照)。デフォルト true
two_stage_judge = true # 検収前に安価な一次評価を実行するか(「二段階検収判定」を参照)。デフォルト true
judge = "mav" # 誰が検収を判定するか(「検収ジャッジの差し替え」を参照)。mav / evaluator_only / external / human_only。デフォルト mav
admission = "queue" # エフェメラルな子エージェント(ephemeral spawn)が並行数上限に達したときの扱い、"queue" または "fail"。デフォルト queue(後述「エフェメラル spawn の受け入れキュー」を参照)
[task_forward_model] # タスク層のフォワードモデル(同名セクションを参照)。デフォルトは全体オフ
enabled = false
[goal_loop]
iteration_cap = 5 # 難しいゴールのディスパッチ回数のハード上限。超えると人的対応へ。デフォルト 5
iteration_cap_simple = 3 # 簡単なゴールのディスパッチ回数上限(動的ジャッジ深度による)。デフォルト 3
wall_clock_hours = 24 # 作成時点からの経過時間の予算(時間)。超えると人的対応へ。デフォルト 24
max_concurrent = 3 # 同時に飛んでいるゴールタスクの上限(spawn の暴走防止)。デフォルト 3
tick_secs = 30 # ドライバーのポーリング間隔(秒)。デフォルト 30
stalled_secs = 600 # ディスパッチ後にこの秒数だけ認領がなければ停滞とみなし再ディスパッチ可能にする。デフォルト 600
planner_enabled = false # 有効にすると、ゴールを依存関係付きのサブタスク DAG に分解できる(「並列サブタスク」を参照)。デフォルト false
resume_on_restart = "pause" # gateway 再起動時の進行中ゴールタスクの扱い、"auto" または "pause"(「再起動時の挙動」を参照)。デフォルト pause、ダッシュボードの「設定 → 自動化」で切り替え可能
progress_report_minutes = 10 # 認領済みタスクが進捗シグナルなしにどれだけ経過したら1回通知するか、`0` で無効化(「タイムアウト時の進捗レポート」を参照)。デフォルト 10
tool_streak_advisory = true # 同じツールを同じパラメータで3/5/8回連続呼び出した際にリマインダーを注入するか(「ツール連打アドバイザリー」を参照)。デフォルト true
[dispatch_guard] # フィードバック経路のサーキットブレーカー(自己増幅型の無限ループを防ぐ)
window_secs = 60 # スライディングウィンドウの長さ(秒)。デフォルト 60
max_in_window = 20 # 1ウィンドウ内で許容されるディスパッチ回数、超えるとトリップ。デフォルト 20
cooldown_secs = 60 # トリップ後、ディスパッチを拒否するクールダウン秒数。デフォルト 60
max_hop_depth = 5 # 委任チェーンをまたぐプロセス間 re-spawn の深さ上限。デフォルト 5

すべてのブロックは省略可能で、未設定または一部だけ設定されている項目は上表のデフォルト値にフォールバックします。未知の policy 値は常に fixed_hierarchy にフォールバックし、警告を1件記録します。


並列サブタスク(依存関係 DAG)

Section titled “並列サブタスク(依存関係 DAG)”

[goal_loop] planner_enabled = true にすると、ゴール作成時にまず AI従業員が依存関係を注釈したサブタスクの集合に分解することを「試みます」(例:2つのデータソースをそれぞれ調べてから統合する、など)。分解されたサブタスクはそれぞれ Task Board 上に載り、depends_on がすべて満たされたサブタスクは並行して実行され、それぞれ独立に検収されます。並行度は依然として max_concurrentdispatch_guard サーキットブレーカーの制約を受け、それを迂回することはありません。

  • 強制ではありません:モデルが分解不要と判断した場合(またはその返答が解析できなかった場合)は単一タスクにフォールバックし、この機能をオフにしたときと完全に同じ挙動になります。
  • 循環依存の防止:分解された計画に循環依存(またはインデックス範囲外の参照)が含まれる場合、計画全体が破棄され単一タスクにフォールバックし、警告が記録されます。壊れた DAG が着地することは決してありません。
  • 上流の行き詰まりは下流を孤児化しない:あるサブタスクの上流の依存先が failed / cancelled / needs_human になった場合(または依存先が存在しない場合)、下流のサブタスクはエスカレーションを継承して同じく人的対応へ移行し、行き詰まった一連の枝全体が見えるようになります。上流がまだ実行中なだけであれば、下流はそのラウンドは凍結され、次のラウンドで改めて判断されます。

想定される効果が最も大きいのは「複数データソースの照会」型のゴールです。独立した再テストではおよそ 1.25 倍の高速化が確認されました(論文が自己申告する 3.7 倍ではありません)。一般化する前に、eval による実測を基準にしてください。


ディスパッチポリシー(DispatchPolicy)

Section titled “ディスパッチポリシー(DispatchPolicy)”

[dispatch] policy は「どの AI従業員がゴールタスクを引き受けるか」を決めます。デフォルトの fixed_hierarchy は従来と完全に同じ挙動です(タスクに元から割り当てられている相手にディスパッチします)。

ポリシー 動作
fixed_hierarchy デフォルト。タスク既存の assigned_to にそのままディスパッチします。LLM コストはゼロで、完全に決定的です。
round_robin 「タスクのカテゴリ」(タグがあれば最初のタグ、なければ優先度)に基づき、名簿を順番に回します。状態はメモリ上にのみ存在し、再起動でリセットされます。
llm_select LLM がツール呼び出しを通じて名簿の中から最適な AI従業員を選びます。fail-closed:出力が名簿に存在しない場合、あるいは解析や LLM 呼び出しが失敗した場合は、常に fixed_hierarchy の結果にフォールバックし、存在しない AI従業員にディスパッチすることは決してありません。モデル名はハードコードされておらず、設定されたユーティリティ用ランタイムを使用します。

名簿 = <home>/agents/ 配下の AI従業員ディレクトリ。名簿が空の場合、round_robinllm_select はいずれも元の割り当てにフォールバックします(タスクを孤児化しません)。再割り当てはタスクの assigned_to に書き戻されるため、ハートビートの取得とアクティビティログの整合性が保たれます。


エフェメラル spawn の受け入れキュー

Section titled “エフェメラル spawn の受け入れキュー”

ゴール分解や委任などの経路では、短命な子エージェント(ephemeral spawn)を立ち上げる必要がある場合があります。並行数上限(ephemeral_max_active、デフォルト 32)に達したとき、config.toml [dispatch] admission が上限を超えたリクエストの扱いを決めます。

動作
queue デフォルト。上限付き FIFO キュー。リクエストは決して消えてなくなることはなく、空きが出れば順番に実行されます。各キュー項目は TTL(queue_item_ttl_secs、デフォルト 600秒)を持ち、期限を過ぎると破棄され監査ログに1件記録されます。キュー自体にも深さの上限(queue_max_depth、デフォルト 64)があり、満杯の場合ははっきりと拒否されます(無制限のキューがそれ自体新たな暴走リスクになるのを避けるためです)。リクエストを発行した turn/session が終了すると、それに属するキュー項目もまとめて無効化されるため、すでに終わったプロセスから遅れて子エージェントが飛び出してくることはありません。
fail 旧来の挙動:上限を超えたら即座に拒否し、キューには入れません。

ephemeral_max_active 自体は「調整可能だがゼロにはできない」というルールに従います。0 に設定すると 1 にクランプされ警告が記録され、並行数上限が完全にオフになることは決してありません。

config.toml
[dispatch]
admission = "queue" # "queue"(デフォルト)または "fail"
queue_max_depth = 64 # キューの深さ上限、超えると拒否
queue_item_ttl_secs = 600 # キュー項目が生存できる秒数、期限切れは破棄され監査される
ephemeral_max_active = 32 # 並行数上限、0 は 1 にクランプされる

構造化された成果物検収(outcome schema、WP2.4)

Section titled “構造化された成果物検収(outcome schema、WP2.4)”

/goal … || outcome:<spec> を使うと、自由記述の検収基準に加えて機械的に検証可能な成果物契約をもう1層追加できます。AI従業員が完了を報告しタスクが review に入ると、この契約は決定的で LLM コストがゼロのチェックを検収ジャッジより前に実行します。

  • チェック不合格 → タスクは即座に revising に差し戻され、フィードバックには具体的な不足点(どのフィールドが欠けているか、どのファイルがないか)が示されます。ジャッジは一切呼び出しません。これはジャッジの偽陽性を防ぐ防御線です。構造的に明らかに不合格な成果物が過度に寛容なジャッジに通されることはなく、ジャッジ呼び出しも1回も無駄になりません。
  • チェック合格 → ここで初めてジャッジに到達し、ジャッジのプロンプトには「構造化された成果物検収はすでに決定的チェックに合格済み」という注記が付き、ジャッジは品質面に集中できます。

3種類の spec があります。

spec 意味
outcome:text デフォルト。構造化契約なし。outcome を付けなかった場合とまったく同じ挙動(永続化されず、ジャッジの前にチェックも走りません)。
outcome:json:<JSON Schema> JSON Schema のサブセット(object / array / string / number / integer / booleanproperties / required / items に対応)。AI従業員の最終返信中の ```json ブロックをチェックします(fenced ブロックが見つからない場合は返信全体を解析します)。フィールドの欠落や型の不一致はすべて具体的な不足点として列挙されます。
outcome:files:<glob,glob> AI従業員の作業ディレクトリ配下に、各 glob(*? 対応)に一致する成果物ファイルが存在することを表明します。例:outcome:files:report.docx, out/*.pdf

/goal 今四半期の売上数値をエクスポート || outcome:json:{"type":"object","required":["revenue","month"],"properties":{"revenue":{"type":"number"},"month":{"type":"string"}}}
/goal 四半期レポートを作成して保存 || 売上グラフを含めること || outcome:files:report.docx,charts/*.png

境界と fail-closed の挙動:

  • パストラバーサルは拒否されますfiles: の glob が絶対パス、ホームディレクトリ(~)、または上位ディレクトリを指す .. を含む場合、/goal 作成時点で一律拒否され(fail-closed、タスクは作成されません)、検証時にも再度チェックされます。作業ディレクトリの基準は <home>/agents/<agent>/ です。
  • 不正な spec は拒否されますjson: が有効な JSON オブジェクトでない、files: のリストが空、未知の型プレフィックスである場合、いずれも /goal は即座にエラーを返しタスクは作成されません(黙って text にフォールバックすることはありません)。
  • 永続化:spec は単一の outcome:<base64url> タグとしてタスク既存の tags フィールドに保存されます(base64url にはカンマが含まれないため、タグの区切りと衝突しません)。データベーススキーマの変更はありませんtext は永続化されません。
  • planner との関係:outcome spec を設定すると planner_enabled によるサブタスク分解はスキップされます。構造化契約は単一の最終成果物を対象としており、分解されるはずだった各サブタスクには適用されません。
  • 最後の砦は依然としてジャッジです:タグが破損してデコードできない場合、決定的チェックはスキップされそのままジャッジに渡されます(ジャッジは通常通り関門として機能します)。観測性のギャップによってタスクが行き詰まることはありません。

グラウンディング事前チェック(v1.53)

Section titled “グラウンディング事前チェック(v1.53)”

AI従業員が完了を報告しタスクが検収に入ると、検収ジャッジを呼び出す前に LLM コストゼロのグラウンディング事前チェックが実行されます。最終返信を、このタスクで実際に実行されたツールの記録(監査ログ内のツール結果)と照合します。返信が何かを調べた・実行したと主張しているのに、実際のツール結果と重なる内容がまったく見つからない場合、ジャッジ呼び出しを消費せずそのまま修正に差し戻されます。なりすまし対策として2つの工夫があります。

  • 自己エコーは証拠にならないtasks_complete のように AI従業員自身が書いた要約をそのまま返すタイプのツールは除外リストに載っており、自分の言葉で自分を証明することはできません。
  • 自分で投入した内容もカウントされない:AI従業員自身がツール呼び出しの引数に入れたテキストは証拠から差し引かれ、ツールが実際に返した内容だけがカウントされます。

config.toml [dispatch] grounding_precheck_enabled = false でオフにできます。照合できるツール記録がまったくない場合(純粋な対話型のタスクなど)、事前チェックはスキップ(Skip)され、不当にペナルティが課されることはありません。


タスク層のフォワードモデル(task forward model、v1.53、デフォルトオフ)

Section titled “タスク層のフォワードモデル(task forward model、v1.53、デフォルトオフ)”

有効にすると、goal loop は各ディスパッチの前に、過去の同種タスクの統計に基づいて「今回はおおよそどうなりそうか」(失敗しそうか、どのツールカテゴリを使いそうか)を予測します。実行後は予測と実際の観測を比較して遷移として記録し、システムは「この種のことをするとどうなりがちか」というタスク層の世界モデルを蓄積していきます。すべてのランタイム(claude / codex / gemini / openai-compat)で共通です。

  • 段階的なフォールバック予測:同種の統計があればそれを使い、なければ全体の周辺統計、それもなければ事前分布のデフォルト値を使います。コールドスタートでは LLM 呼び出しは一切発生しません。
  • 誠実な保真度の格付け:すべての観測には証拠の保真度(ネイティブツールイベント/監査ログのみ/証拠なし)がタグ付けされ、「見えなかった」ことが「起きなかった」ことと混同されることはありません。
  • <state> ブロック:タスク実行中、プロンプトには構造化された現在の状態ブロックが注入され、AI従業員は返信中の状態更新タグでこれを更新できます。(状態、行動)の訪問グラフと組み合わせ、同じ状態から同じ行動を2回以上繰り返すと早期に人的対応へエスカレーションします(振動検知)。
  • 予兆警告:予測がこのディスパッチはおそらく失敗すると示している場合、ディスパッチのプロンプトに警告文脈が付加されますが、直接ブロックはしません(予測は補助であり、関門ではありません)。
  • タスクルールの帰納:同種の遷移が繰り返し発生すると、決定的なテンプレートによりタスクルールとして帰納され、以後のプロンプトに注入されます(上限2件)。他の学習ルールと同じ helpful/harmful ライフサイクルを共有し、成績が悪ければ自動的に引退します。
config.toml
[task_forward_model]
enabled = false # デフォルトオフ。有効にすると predict-act-verify のパイプライン全体が働きます

AI従業員が完了を報告しタスクが review に入った後、毎回フルの MAV ジャッジパネルを呼び出すわけではありません。まずはるかに安価な一次評価が実行されます。ツールなしの単発 LLM 呼び出しで、3択の JSON 判定のいずれかを出力します。

判定 意味 その後の動作
continue このラウンドではまだ完了していないが、方向性は正しい MAV をスキップし、評価器が示した次の一手をフィードバックとして即座に再ディスパッチします(イテレーション上限にカウントされます)
blocked 外部の障害(権限不足、データ不足、第三者待ちなど)で行き詰まっている ジャッジのラウンドを無理に完走したふりをせず、そのまま needs_human
candidate_complete 完了候補に見える ここで初めて完全な3方向の MAV ジャッジパネルによる慎重なチェックへ進みます

評価器がどんな状況(タイムアウト、解析失敗、呼び出し自体のエラー)に陥っても、常に MAV ジャッジへの直接実行に降格します。一次段階の障害によって自動的に合格や不合格になることは決してなく、安全性はこの層がない場合とまったく同じです。config.toml [dispatch] two_stage_judge(デフォルト true)で設定し、false にすると毎ラウンドフルパネルを実行する旧来の単段階の挙動に戻ります。

「この作業は本当に完了しているか」は、このプラットフォーム唯一のリリース権限のポイントです。デフォルトでは組み込みの3方向 MAV ジャッジパネルが判定しますが、config.toml [dispatch] judge で別の実装に差し替えることもできます。

誰が判定するか 使いどころ
mav(デフォルト) 一次評価器 → 3方向 MAV ジャッジパネル 一般的なケース
evaluator_only 一次評価器のみを実行し、candidate_complete は直接合格とする コスト削減用。検証強度は明確に弱くなります:ツールなしの単発呼び出しだけが関門で、ジャッジパネルによる複査はありません。合格フィードバックには低コストモードである旨が自己ラベル付けされます
external あなた自身のプログラム(judge_command 自前の CI、ルールエンジン、あるいは2つ目のモデルをジャッジとして組み込みたい場合
human_only 機械による判定はなく、すべての review タスクが needs_human すべての納品に人の目を必要とする高リスクなデプロイ

不正な値が静かに有効になることはありません。gateway が警告を出し mav(4つの選択肢の中で最も厳しいもの)にフォールバックします。この設定は判定のたびに再読み込みされ、two_stage_judge と同様、変更は再起動なしで即座に反映されます。

[dispatch]
judge = "external"
judge_command = ["/usr/local/bin/my-judge", "--strict"]
judge_timeout_secs = 120 # デフォルト 120

プラットフォームはこのコマンドを実行し、stdin に次の JSON を流し込みます。

{
"schema": "duduclaw.judge.v1",
"task": "タスクの説明(<tool_activity> / <risk_boundary> などのブロックをすでに含む)",
"acceptance_criteria": "凍結された検収基準のベースライン",
"result": "AI従業員の今回のラウンドの提出内容",
"tool_activity": "ツールの監査サマリー"
}

コマンドは判定として次のような JSON オブジェクトを stdout に出力します。

{"pass": true, "feedback": "検収条件をすべて逐一確認し合格"}

passtrue/false に加え、文字列の "pass"/"fail" も受け付けます。feedback は省略可能です。

事前に知っておくべきことが3つあります。

  1. 外部ジャッジがどんな状況に陥っても MAV ジャッジパネルにフォールバックします——タイムアウト、非ゼロの終了コード、stdout が有効な JSON でない、judge_command が未設定である場合を含みます。降格は常により厳しい方向へ働き、通しやすい方向に振れることはなく、降格のたびに security_audit.jsonljudge_seam_degraded)に記録されます。
  2. その出力は信頼できないデータとして扱われます。 feedback は次のディスパッチラウンドのプロンプトに流れ込むため、まずインジェクションスキャンと切り詰めを経ます。スキャンでブロックされた場合、その判定全体は破棄され、代わりに MAV ジャッジパネルが判定します。フィードバックのテキストには出所が前置されるため、タスクのタイムライン上でどの行が外部ジャッジの発言かが分かります。
  3. judge_command はファイルを編集することでのみ変更でき、ダッシュボードからは変更できません。 これは実行可能ファイルを指定するものなので、system.update_config RPC は judge(4つの列挙値)のみを受け付け、judge_commandjudge_timeout_secs は受け付けません。AI従業員自身にも ~/.duduclaw/config.toml への書き込み権限はありません。

duduclaw eval をジャッジとして使いたい場合は、judge_commandduduclaw eval をラップするスクリプトに向けるだけで済みます。別のモードは必要ありません。

サブプロセスは gateway の環境変数をすべて継承します。 judge_command はプラットフォームのプロセス spawn(tokio::process::Command)で直接実行されており、env_clear() もアローリストによるフィルタリングも行われていません。指定したジャッジプログラムは、呼び出された時点の gateway プロセスの環境変数一式を見ることができ、その中には gateway が LLM プロバイダーやチャンネル API を呼ぶために使う秘密情報も含まれます。これはデータが能動的にジャッジへ渡されているという意味ではありません(ジャッジの入力は前述の stdin JSON だけです)。ジャッジプログラム側にそれらの環境変数を読む「能力」がある、ということです(例えば悪意のある、あるいはバグのあるプログラムが std::env::vars() を読む場合)。これは脆弱性ではなく、現時点でのこの seam の設計上のトレードオフです。信頼でき、出所がはっきりしているプログラムだけを指定してください。サードパーティや未レビューの実行ファイルを指定してはいけません。より厳格な分離(ジャッジプロセスが gateway の秘密情報を本当に見られない状態)が必要な場合は、judge_command を、まず自分の環境変数をクリアしてからジャッジに実際に必要な少数の変数だけを再注入するラッパースクリプトとして包んでください。

MAV ジャッジと一次評価器のプロンプトにはいくつかの規律が組み込まれています。これは実際のテストで捕捉された失敗パターン——ジャッジが自ら偽の却下理由を作り出し、正しい成果物を永久にブロックしてしまう——に対処するためのものです。

  • 反ラチェット:検収基準が変わっていないのに、ジャッジが毎ラウンド新しい粗探しをすることはできません。これはゴールを永遠に完了させない典型的な失敗パターンです。
  • 監査のみ、証拠の自作は禁止:ジャッジは AI従業員が提出した証拠とツール監査サマリーを照合することしかできず、自分で証拠を想像したり作り出したりすることはできません。「もっと良いやり方があると思う」を基準にすることもできません。
  • 契約範囲外への拡張禁止:検収基準に書かれていない事項を却下の理由にすることはできません。これは最もよくある偽の却下であり、正しく範囲内で行われた作業が行き詰まる最大の原因です。
  • エージェント自身の「完了しました」は証拠にならない:「もう完了した」「もう対応した」といった自己申告そのものは合格の根拠にはならず、ジャッジは検収基準と実際の成果物を項目ごとに照合しなければなりません。

これらの規律には設定スイッチはなく、すべてのゴールタスクに即座に適用されます。

検収ジャッジがチェックする観点の数は、ゴールの難易度に応じてスケールし、不要なジャッジの LLM コストを節約します。

  • 簡単なゴール(短く、単一ステップで、多段階/リサーチ/比較/デプロイ/移行などのキーワードを含まない):ジャッジは正確性+安全性の2観点のみをチェックし、ディスパッチ上限には iteration_cap_simple(デフォルト 3)を使います。
  • 難しいゴール正確性+完全性+安全性の完全な3方向 MAV パネルを使い、ディスパッチ上限には iteration_cap(デフォルト 5)を使います。

安全性はどの深度でも維持されます(fail-closed の設計思想)。深度を下げても削られるのは完全性のきめ細かさだけで、安全性チェックが削られることは決してありません。難易度はローカルの LLM コストゼロなヒューリスティック(長さ+CJK対応のトークン推定+キーワード)で判定され、ジャッジの深度とディスパッチ上限は同じ判定を使うため、両者は常に一致します。

[capabilities]
autonomy_level = "approver"
irreversible_tools = ["send_email"] # 常に人による承認が必要な不可逆なツール
maybe_irreversible_tools = ["Bash", "http_post"] # エスカレーションが必要かどうかをジャッジが判定するツール

停滞検知:ギャップ指紋の照合

Section titled “停滞検知:ギャップ指紋の照合”

「2ラウンド連続で同じ場所に行き詰まっている」の判定は、もう却下フィードバックが一字一句同じかどうかだけを見るわけではありません。ジャッジは毎回まったく同じ言い回しをするとは限りません。「goal_loop.rs:120 のエラー処理が欠けている」と「goal_loop.rs の120行目でバリデーションを忘れている」は同じギャップを指していますが、文字列比較ではこれらを2つの別の事柄と判定してしまい、本当に行き詰まっているシグナルが言い回しの違いに埋もれてしまいます。

現在は却下フィードバックから path:line の参照とバッククォートで囲まれたキーワード(関数名、変数名、エラーコード)を抽出し、正規化した上で(一時/scratch パスは同じプレースホルダーに統一、大文字小文字を無視、重複排除してソート)指紋を組み立てます。同じギャップを別の言い方で表現しても同じ指紋になります。参照もキーワードもまったく抽出できない場合(純粋に説明的なフィードバックなど)は、元の一字一句比較にフォールバックし、挙動の互換性を保ちます。2ラウンド連続で同じ指紋になって初めて後述の needs_human がトリガーされ、閾値そのものは変わっていません。

早期切り上げ検知(bail detection)

Section titled “早期切り上げ検知(bail detection)”

自律ループの中で、AI従業員が「終わったように聞こえるが、実際にはジャッジによって検証されていない」言葉でそのラウンドを終えることがあります。「とりあえずここまでにしておきます」「後ほど結果をご確認ください」「レビュー提出済みです」「VERDICT: PASS」(ジャッジではなく自分で署名したもの)といった具合です。これらの言葉自体は作業に誤りがあることを意味しませんが、記録しておく価値のあるプロセス上のシグナルであり、次のラウンドでも少し注意を払う価値があります。

9個の zh+en 正規表現は、そのラウンドのエージェントの返信の最後の空でないテキストブロックだけをチェックします。いずれかに一致すると、

  • Activity Feed イベントを1件記録します(goal_loop.premature_stop_suspected
  • Prometheus カウンター goal_loop_bail_pattern_total{pattern="<一致したパターン名>"} を加算します
  • 次のラウンドの <state> ブロック、一次評価器の入力、MAV ジャッジの入力にヒントを持ち込みます。中立的な注記(「早期に切り上げた疑いがあります。タスクが本当に完了しているか確認してください」)であり、評価器やジャッジの判断を先取りすることはありません

この検知レイヤー自体は、タスクを却下したり、ブロックしたり、人的対応へエスカレーションしたりすることは一切ありません。純粋にシグナルとリマインダーであり、実際に合格とするかどうかは評価器・ジャッジが証拠をどう読むか次第です。

再起動時の挙動(resume_on_restart)

Section titled “再起動時の挙動(resume_on_restart)”

gateway の再起動やクラッシュからの復旧後、進行中のゴールタスクはデフォルトで needs_human にエスカレーションされます(resume_on_restart = "pause"デフォルト値)。gateway は起動のたびに、終端状態でないすべての goal_mode タスク(todo/pending/revising/in_progress/review/blocked)を needs_human(理由:gateway_restart)に移し、既存のチャンネル通知を経由してあなたが「再試行」を押すまで待ちます。予期しないプロセス再起動やデプロイが、誰にも安全性を再確認されていないゴールをこっそり実行し続けることはありません。

より緩い元々の挙動を維持したい場合は、[goal_loop] resume_on_restart"auto" に設定してください。進行中のゴールタスクは、プロセスが一度も中断されなかったかのようにそのまま続行されます(これはこの設定が存在する前の唯一の挙動でした)。

どちらの方向であっても、このチェックは gateway 起動時に一度だけ実行され、設定のホットリロード(system.update_config)ではトリガーされません。変更は次に実際に gateway が再起動されたときにのみ反映されます。

ダッシュボードでの切り替え:「設定 → 自動化」の「gateway 再起動時の進行中ゴールタスク」ドロップダウンから直接切り替えられ、config.toml を手動で編集する必要はありません。system.update_config"auto"/"pause" の2値のみを受け付け、それ以外は一律拒否されます。


タスクが「人的対応が必要」にエスカレーションすると(ディスパッチ上限到達/経過時間の上限超過/同じギャップ指紋で2ラウンド連続却下/リトライ予算が尽きても検収ジャッジが不合格のまま/上流の依存サブタスクが行き詰まりエスカレーションを継承した/resume_on_restart = "pause" の状態で gateway が再起動した)、4つのボタンが AI従業員のコントロールチャンネルに送信されます。

一時停止理由の分類(pause_reason)

Section titled “一時停止理由の分類(pause_reason)”

「人的対応が必要」はもう単一のバケツではありません。既存の自由記述の judge_feedback(ジャッジまたは評価器の完全なフィードバックで、複数の文にまたがることもあります)に加え、人的対応へエスカレーションするたびに6択の閉じた分類が同時に刻印されます。これは「どんな種類の行き詰まりか」を一目で判別できるようにするためのもので、judge_feedback は依然として文単位の詳細を担います。

分類トークン UI表示テキスト
no_progress 行き詰まり・進展なし
budget_exhausted 回数または時間の上限に到達
blocked_needs_decision あなたの判断待ち
infra システムの問題
restart 再起動により一時停止
unknown 人による確認が必要

この分類はトリガーが発生した現場で静的に刻印され(各エスカレーション経路がそれぞれ自分のカテゴリをタグ付けします)、judge_feedback の LLM による文章から逆算されることは決してありません。モデル自身の言い回しは信頼できず、ルーティングの根拠にすべきではないからです。分類なし、認識できない古い値、あるいはこのフィールドが導入される前からあるタスクは、すべて unknown(「人による確認が必要」)として扱われます。種類の分からない行き詰まりは、実は当て推量に過ぎない具体的な分類に誤って割り当てられるより、あいまいなものとしてあなたに提示される方がましだからです。

表示場所:/goals ボードのカードとタスク詳細ページの分類チップ、チャンネルの needs_human 承認メッセージ内の「種類」の1行(Observer の完全自動モードの通知のみのケースにも表示されます)。タスクが人によって決定される(再試行/完了とする/諦める)と、分類フィールドはクリアされ、次のエスカレーションには持ち越されません。

ボタン 動作
再試行 タスクは再試行待ち(pending)に戻り、ドライバーが次のラウンドで再びディスパッチします。
完了にする タスクを直接完了(done)にマークします。
諦める タスクをキャンセル(cancelled)します。
自分で対応 あなたが引き取り、タスクはあなたによって認領された(claimed_by)とマークされます。状態は needs_humanままなので、ドライバーはもともと自動的に再ディスパッチしません(候補クエリが見るのは todo/pending/revising だけです)。これが現時点のこの機能の範囲です。自動リトライの停止+マーク付け+カードの折りたたみです。会話のコントロールを完全にあなたへ渡す(以後のメッセージが AI の判断に届かなくなる)のは次段階の機能で、まだ実装されていません。

1つのメッセージで許容される主要アクションは最大3つで、4つのボタンはこれを超えるため、「諦める」と「自分で対応」は、二次階層をサポートするチャンネルでは二次階層に折りたたまれます。Telegram では2段目のボタン、Discord でも2段目のボタン、Slack ではネイティブの overflow メニューです。LINE には対応する二次メニューの仕組みがないため、これら2つのアクションは LINE のクイックリプライボタンには表示されず、代わりにメッセージ本文中にダッシュボードへのリンク付きで説明されます。

ボタンの判定は冪等で fail-closed です。再試行/完了にする/諦めるは、タスクを needs_human から遷移させることしかできず、2回押した場合や状態がすでに変わっている場合は no-op です。「自分で対応」は比較すべき終端状態を持たないため、もう一度押しても(たとえ別の権限を持つ人が押しても)認領を再スタンプするだけで、エラーにはなりません。collaborator/consultant のキックオフ承認も同様で、期限までに判断されなければ拒否扱いになります(fail-closed)。

v1.53 以降、人的対応へのエスカレーションにはシミュレーションプレビュー(simulate-before-act)が添付されます。タスクを続行させることを選んだ場合、次の3ステップでおおよそ何が起きるかを示します。シミュレーションには15秒の上限があり、タイムアウトした場合はシミュレーションなしで承認リクエストが通常通り送信されます(それによってブロックされることはありません)。シミュレーションが参照するナレッジは読み取り専用の namespace に限定され、シミュレーションの記述自体があるアクションが可逆かどうかを決定することはできません(自己証明の禁止)。ダッシュボードの承認カードはこのプレビューを描画します。

シミュレーションプレビューが扱うのは「続行を許可したら何が起きそうか」ですが、「変更」タブが扱うのはすでに何が起きたかです。ダッシュボードの受信箱の判断カードとタスク詳細ページの両方に、このタスクが各ラウンドで実際に触れたファイルを一覧表示する「変更」タブがあります。

項目 内容
パス 作成/変更/削除されたファイルのパス。command タイプの場合は shell コマンドそのものを表示し、どのファイルに触れたかを勝手に推測することはありません。ワンクリックでコピーできます。
操作 新規作成/上書き、編集、削除、コマンドの4種類のいずれかです。
ステータス 失敗またはブロックされた呼び出しも一覧に含まれ「失敗」とマークされます。これはまさに、リアルタイムのツール状態クエリでは見えない半分です。
要約の抜粋 書き込まれた内容やコマンドの説明の抜粋で、監査ログのマスキング済み結果をそのまま再利用します(表示のためだけに元ファイルを読み直すことはありません)。
ソース 実行時のネイティブツールイベント(Write / Edit / NotebookEdit / Bash…)か、MCP の監査記録(shared_wiki_write など)のいずれかです。

証拠は既存の2つの経路から得られます。実行時のネイティブツールイベントは各ディスパッチラウンドの後にファイル変更記録として落とし込まれ(タスク ID で帰属付けされます)、MCP の監査記録はジャッジの <tool_activity> がすでに使っているのと同じ「認領から検収までの時間窓+実行者」の帰属付けを再利用します。記録がなければ、それは記録がないということです。何もない場合、タブには「このタスクにはファイル変更の記録が残っていません」と表示され、作り話で埋め合わせることはありません。

現時点で表示されるのは「どのファイルに触れたか、操作は何か」であり、まだ行単位の before/after diff ではありません。本当の diff には書き込み前のスナップショットが必要で、それは今後の課題です。


「考えてみる」プランファーストモード(plan-first、I-1c)

Section titled “「考えてみる」プランファーストモード(plan-first、I-1c)”

ダッシュボードの割り当てパネルには「聞いてみる」「任せる」に加えて、第3のモード「考えてみる」があります。AI従業員がまず実行計画を作成してあなたに見せ、あなたが承認して初めて本当に作業を始めます。

「考えてみる」を選んで送信しても、フロントエンドは同じく tasks.goal_create を呼び出しますが、plan_first: true が追加されます。バックエンドはタスク作成のその場で(ディスパッチループの1ラウンドとしてではなく、同期的に)ユーティリティ用 LLM を呼び出し、ゴールの説明と検収基準から3〜8項目のプレーンテキストの実行計画を生成します(JSON ではなく、人が読むための文章です)。タスクは直接 needs_human として生まれ、既存の blocked_needs_decision(「あなたの判断待ち」)分類を再利用します。新しい分類は追加されません。承認前はディスパッチループの候補クエリに一切入らないため、1ラウンドすら実行されません。

計画のテキストは2箇所に書き込まれます。

  • judge_feedback(既存のフィールド):「判断待ち」カード、タスク詳細ページ、チャンネルの承認メッセージが、表示ロジックを一切変更せずに計画内容を表示できるようにするためです。
  • plan_pending(新規フィールド、I-1c専用):judge_feedback とは別に保存されます。これは、承認アクション自体(どの needs_human タスクにも共通の「再試行」ボタン)があなたの承認メモで judge_feedback を上書きしてしまうためです。もし計画も同じフィールドに存在していたら、承認した瞬間に自分自身の承認アクションによって上書きされてしまい、最初のディスパッチラウンドに永遠に届かなくなってしまいます。

承認は既存の「再試行」ボタンで行われ、新しいボタンの種類は追加されません。承認されるとタスクは pending に戻り、ドライバーの次の tick でディスパッチする際、plan_pending の内容が <execution_plan> ブロックに包まれてそのラウンドの作業プロンプトに注入され、AI従業員は計画に沿って実行を開始します。注入直後に plan_pending は即座にクリアされるため、計画が貼り付けられるのは一度きりで、以降のラウンドでプロンプトに繰り返し登場することはありません。

計画はガイダンスであり、検収を免除する保証ではありません。実行が完了しても、他のゴールタスクとまったく同じ二段階検収判定/MAV ジャッジパネルを通過する必要があり、計画に書かれたステップがジャッジによって不十分または誤りと判断されれば、同じように修正へ差し戻されます。

計画を作成するユーティリティ用 LLM の呼び出し自体が失敗することもあります(タイムアウト、通信エラー、あるいは完全に空の返信)。この場合もタスクは fail-closed のまま needs_human にとどまりますが、分類は blocked_needs_decision ではなく infra(システムの問題)に変わり、plan_pending は付与されません。このようなタスクを承認しても、常に「注入する計画がない」最初のラウンドが解放されるだけで、計画が忽然と消えたり、黙ってスキップされたりすることはありません。


ハードな境界を握っているのはモデルではなくドライバーであり、行き詰まったゴールが無限にループすることはあり得ません。完了シグナルとして認められるのは検収ジャッジの承認だけです(AI自身の「完了しました」という自己申告は決して信頼されません)。ディスパッチ上限、経過時間上限、並行数上限、進捗の振動検知、フィードバック経路のサーキットブレーカーはそれぞれ独立に働き、いずれか1つでも踏めば人的対応へのエスカレーション、またはブレーカーのトリップが発生します。