Skip to content

なぜ止まらずに走り続けたか ​

「一度の指示で 3 日間、人間が見ていない間も進み続けた」のは、モデルの意志ではなく 4 つの仕組みの組み合わせ です。

4 つの仕組みが噛み合って「勝手に進み続ける」ループになる

ユーザー
/goal(最初の 1 回だけ)
以降 3 日間、追加の作業指示なし
セッションに Stop フックが張られる
① Stop フック — ゴール未達の間は停止をブロックし、30/60/120 分ごとに Goal check-in で作業継続を促す
Fable 親セッション
進捗確認・判定・次の issue 起動だけを行う
実作業は下の 2 つに委譲し、自分のコンテキストを 3 日間もたせる
起動(制御はすぐ親に戻る)
② Workflow(バックグラウンド)
調査 → 実装 → E2E → レビュー → 判定 → マージ
issue 1 件を丸ごと処理。完了で task-notification が親に届く
③ Monitor / Bash bg(バックグラウンド)
journal / PR / codex ログの監視
verdict / merge などのイベントだけを通知に変換する
通知が届いたら親が次の一手を決め、再びバックグラウンドへ
④ 障害時
縮退運転に切替
親が直接運転 / resume モード / モデル・フォールバック。止まる代わりに経路を替える

1. /goal の Stop フック — 「終わり」を定義する ​

/goal を投入すると、セッションに Stop フック が張られます。私(Fable)がターンを終えようとするたびに「ゴール条件は満たされたか」が評価され、未達なら停止がブロックされ、Goal check-in として作業継続が促されます。

実際に受け取った check-in はこの形です:

Goal check-in: «…» is still active, and evaluation has been deferred for 60 min because background work is still running: wgxxpcela · workflow …, bwcqttx0x · shell …. Check on their progress. If they are progressing, say so briefly and keep waiting; if they are stuck or no longer needed, fix or stop them and continue toward the goal.

ポイントは 「バックグラウンド作業が走っている間は評価を保留する」 こと。私が 3 時間かかるワークフローを起動して待機している間、フックは 30→60→120 分と間隔を伸ばしながら「進んでいるか確認せよ」とだけ言い、進んでいれば一言報告して再び待機できました。

2. Workflow ツール — 長時間の多段処理を親から切り離す ​

ultracode 指示に基づき、issue 1 件の処理を 1 本の JavaScript ワークフロー にしました(設計はこちら)。親セッションは Workflow({scriptPath, args:{issues:[8,9]}}) を呼ぶだけで即座に戻り、完了時に task-notification が届きます。

  • ワークフロー内部で agent() を直列/並列に呼ぶため、親のコンテキストにはサブエージェントのツール出力が一切入らない(親は 3 日間で約 100 万トークンしか消費していない一方、サブエージェント合計は約 25M トークン)
  • 各エージェントは StructuredOutput(JSON スキーマ) で結果を返すので、スクリプトが機械的に分岐できる(verdict: LGTM | NEEDS_FIX, merge: true/false)
  • resumeFromRunId で、プロセス再起動や上限中断の後も 完了済みエージェントをキャッシュから復元して途中から再開できた(#8/#9、#2/#3 で実際に使用)

3. Monitor / バックグラウンド Bash — 「待つ」をイベント駆動にする ​

ポーリングでトークンを浪費しないよう、待機は次の 2 通りにしました。

用途手段例
ワークフローのラウンド進行を追うMonitor で journal.jsonl の新規行を 45〜60 秒ごとに読み、verdict= / merge= / merged= だけをイベント化「issue #1 ラウンド2 判定/マージ」
1 回だけ知りたい完了Bash run_in_background + until … sleepCodex 実行完了、15:01 のセッション上限リセット待ち

通知は <task-notification> として会話に届き、その内容だけで次の一手(次 issue 起動、差戻し対応、マージ)を決められました。

4. 障害ごとに別の前進手段を持つ ​

3 日間で 5 種類の障害が起き、そのたびに 同じ手段を繰り返さず別の経路 に切り替えました(詳細は 障害と縮退運転)。

障害(赤)ごとに、別の前進経路(緑)へ切り替えた

Claude セッション上限
7:20 / 15:00 リセット。サブエージェント全滅
親が Codex を直接運転
実装 → 検証 → Sol レビュー → マージを Bash で
Codex クォータ切れ(〜9/4)
codex exec が失敗し続ける
dev-workflow 3-6 フォールバック
Sonnet 実装 / Opus レビューに交代
Claude Code プロセス再起動
実行中のワークフローとモニタが消失
resumeFromRunId で再開
完了済みエージェントをキャッシュから復元
分類器ブロック
PNG 上書き / pad.c 故障注入が実行不能。回避策は禁止
標準経路 + 代替エビデンス
UPDATE_GOLDEN=1 の正規手順で更新し、例外は記録して報告
PCSX-Redux 間欠 exit 255
同一バイナリでも再現性なし
EMU_CRASH のみ 2 回リトライ
判定失敗とは区別し、根本原因は別 issue(#29 → #30)で追跡

「止まらない」と「暴走しない」の境界 ​

止まらなかった一方で、止まった判断 も明示しておきます。

  • ラウンド上限: 差戻しは最大 3 ラウンド。収束しなければ escalated で親に返し、親が原因を読んでから追加ラウンドを起動(#2, #6, #7, #8, #9, #15, #38 で実施)
  • ゲート緩和の検出: レビュー担当(Opus)は「テストを弱めて通した」変更を高/中として差し戻した(#9 の --allow-empty-character、#8 の DARK_ALLOWED_SHOTS、#38 の回帰ガードにならないフィクスチャ)
  • 権限の肩代わりをしない: サブエージェントが分類器にブロックされた操作を「親でやってほしい」と求めたときは断り、代替エビデンスで判定し、ユーザーに報告した
  • 外向き操作の線引き: リポジトリ作成・自動マージはユーザーが明示許可した範囲のみ。マスターの GitHub 保存は「取っておいて」の解釈として実施し報告

時間の使い方(親セッションの 1 ターン) ​

実際のイベント順。親(Fable)が手を動かすのは太字の 3 か所だけ

Fable→WorkflowWorkflow(issues: [8, 9]) を起動(制御はすぐ親に戻る)
Fable→Monitorjournal.jsonl の監視を開始

ターン終了ここで停止するかは Stop フックが評価する。バックグラウンドが動いている間は評価が保留され、親は待機できる

フック→FableGoal check-in(30 分後)「進んでいるか確認せよ」
Fable進捗確認(1 コマンド)→「進行中」と一言だけ報告し再び待機
Monitor→Fableverdict=NEEDS_FIX を検知して通知
Monitor→Fablemerge=False rationale=… を検知して通知
Workflow→Fabletask-notification: issue 9 → status: "escalated"(3 ラウンドで未収束)
FablePR の判定コメントを読み、修正指示を作る
Fable→Workflowresume モードで再起動(fix 指示付き)→ 以降くり返し

aieo-product 内部ナレッジ