Files
maestro/docs/movement-control-flow-tools.md
T
oss-sync 57702b4f34
CI / build-and-test (push) Canceled after 0s
sync: update from private repo (2336949a)
2026-09-17 11:29:31 +00:00

98 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Movement 制御フローツールと park シグナル
エンジン内部リファレンス。「ツールを呼ぶと movement が**待機/終了状態に遷移**するが、その遷移先は**ピース毎の `rules` に書かなくてよい**」という仕組みのまとめ。BrowseWeb のログイン待ち・RequestTool の承認待ち・(提案中の)WaitSubTask が同じ骨格を共有する。今後この型を流用する時の下敷き。
## 要点
movement の遷移には **2 系統**ある。
| 系統 | 遷移先の決まり方 | LLM への提示 | 例 |
|------|------------------|--------------|-----|
| **rules ベース** | ピースの `rules[].next` を `buildTransitionTool` が enum 化 | `transition` ツールの選択肢として提示 | 別 movement への中間遷移 |
| **sentinel `next`(park/terminal)** | ツールまたは engine が `MovementResult.next` に**特別な値**を直接セット | 専用ツール(`complete` 等)として提示。`rules` 不要 | 終了・各種待機 |
後者が今回の主題。**ツールが「作業をする」のではなく「movement を畳む制御シグナルを出す」**。`complete` がまさにこれで、`BrowseWeb`(ログイン待ち)・`RequestTool`(承認待ち)も同じ。`rules` に依存しないので、`rules: []` の単一 movement ピース(`chat` 等)でも一律に効く。
## 仕組みの流れ
```
agent-loop.ts
LLM が制御フローツールを呼ぶ
└─ finishMovement({ next: '<SENTINEL>', resumeMovement?, waitReason? }) を返す
│ MovementResult.next に sentinel を載せる
▼
piece-runner.ts mapMovementResult(result, ...)
└─ next の値で分岐し PieceRunResult を返す
├─ 終了系 → kind:'done' / status: completed|aborted|cancelled
└─ 待機系 → kind:'done' / status: waiting_* + resumeMovement(+waitReason)
▼
worker.ts
└─ waiting_* のジョブを停車。条件成立で resumeMovement から再開
(waiting_subtasks=子全完了 / waiting_human=ユーザー回答・ログイン・承認)
```
ポイントは、**待機系も「ジョブを停車してワーカーを解放」する**こと。ツール内で同期ブロックして待つのではない(後述の鉄則)。
## sentinel カタログ(`mapMovementResult` の分岐・origin/main 時点)
| `next` | 生成元 | 結果 status | resumeMovement | waitReason | 種別 |
|--------|--------|-------------|----------------|-----------|------|
| `COMPLETE` | `complete` ツール | `completed` | — | — | 終了 |
| `ABORT`(`cancelled` 含む) | キャンセル経路 | `cancelled` | — | — | 終了 |
| `ABORT` | `complete`/各種ガード(`text_only_limit`・`max_iterations` 等) | `aborted` | — | — | 終了 |
| `ASK` | `complete({status:'needs_user_input'})` | `waiting_human`(上限超は停車せず、同じ movement へ戻して自己判断を促す。差し戻しが `MAX_ASK_LIMIT_DEFLECTIONS` 回に達したら `ask_limit_reached` で打ち切り。#1299) | 現在の movement | — | 待機 |
| `WAIT_SUBTASKS` | `transition`(rules 経由)/**WaitSubTask ツール(提案)** | `waiting_subtasks` | `default_next`(ツール版は現在の movement に変更予定) | — | 待機 |
| `WAITING_HUMAN_BROWSER` | `BrowseWeb`(ログイン要求時) | `waiting_human` | 現在の movement | `browser_login` | 待機 |
| `WAITING_HUMAN_BROWSER` | `RequestCaptchaHelp`(検索が CAPTCHA で全滅したとき・#1199 段階 2) | `waiting_human` | 現在の movement | `captcha` | 待機 |
| `WAITING_HUMAN_TOOL_REQUEST` | `RequestTool` | `waiting_human` | 現在の movement | `tool_request` | 待機 |
| `WAITING_HUMAN_SPAWN_REQUEST` | `SpinOffTask`(別ワークスペース指定時) | `waiting_human` | 現在の movement | `spawn_request` | 待機 |
| `null` | 異常(ツール未決着) | `error` | — | — | 異常 |
> `WAITING_HUMAN_BROWSER` は**待ち理由違いで 2 つの入口**を共有する例。人が解く相手が
> 「そのタスクのブラウザ」か「ワークスペース共有の確認画面」かだけが違い、下流の分岐
> (piece-runner)は同じものを通る。再開の口は別で、captcha 側は
> `POST /captcha-pool/resolve` が**同じ画面で止まっている全ジョブ**をまとめて queued に
> 戻す(`resumeCaptchaJobsForSpace`)。どの画面を待っているかは `jobs.captcha_space_id`
> に残す——`jobs.space_id` は個人ワークスペースだと NULL のままなので代用できない。
> `WAIT_SUBTASKS` だけは現状 `rules[].next` にも書ける(rules ベース経由)。ツール版(WaitSubTask)を足すと、**同じ sentinel に「ルール不要の入口」**が増える。下流の `mapMovementResult` 分岐は共有。
## 新しい park ツールを足すレシピ
1. **ツール定義**を該当モジュール(例 `orchestration.ts`)の `TOOL_DEFS` に追加。
2. **agent-loop で制御フローツールとして識別**し、`finishMovement({ next: '<新SENTINEL>', resumeMovement, waitReason })` を返す(`WAITING_HUMAN_BROWSER` の実装が雛形)。
3. **`mapMovementResult` に分岐を追加**(piece-runner.ts)。終了なら `status: 'aborted'|'completed'`、待機なら `status: 'waiting_*'` + `resumeMovement`(+`waitReason`)。
4. **worker の再開条件**を実装/再利用。既存の `waiting_human` / `waiting_subtasks` のどちらかに寄せられるなら新 status は不要。
5. **型**(`MovementResult.next` のユニオン、`waitReason`)を更新。
既存の待機 status(`waiting_human` / `waiting_subtasks`)に相乗りできるなら、3〜4 は分岐追加だけで済むことが多い。
## 鉄則・gotcha
- **ツール内で同期ブロックして待たない。** 待機は必ず「停車=ワーカー解放」で表現する。in-tool で `await` し続けると親ワーカーを占有し、最悪ワーカー枯渇でデッドロックする(`WAIT_SUBTASKS` が遷移=停車である理由)。
- **resume 先を意識する。** `default_next` が `COMPLETE` のピース(単一 movement)で再開させると終了に飛ぶ。多くの待機ツールは**現在の movement に再入**させて、再開後に結果を読んで `complete` させる。
- **terminal と park を混同しない。** `COMPLETE`/`ABORT` はジョブを終わらせる(再開なし)。`WAITING_*`/`WAIT_SUBTASKS` は停車→再開。`resumeMovement` の有無が分かれ目。
- **停車から戻ったら同じ呼び出しが来る前提で書く。** 再開は movement を頭からやり直すので、LLM は同じ引数で同じツールをもう一度呼ぶ。決着済みの申請を掴んで短絡する経路(`RequestPackage` の `declinedPackages` / `installedPackages`、`SpinOffTask` の `findDecidedSpawnRequestForJob`)が無いと、却下で永久ループ・承認で 2 枚目のカードになる。
- **sentinel は `rules` の enum に出さない。** これらは engine-internal。`rules[].next` に書けるのは別 movement 名と `WAIT_SUBTASKS` のみ(`loadPiece`/lint が `COMPLETE`/`ABORT`/`ASK` を reject する)。
## KV キャッシュと tools 安定性(2026-07-22 実測・変更禁止事項)
transition ツールは **movement 毎ではなく piece 全体で 1 つ**(enum = `collectPieceTransitionTargets` による全 movement の遷移先の和集合、description は静的)。一見「その movement の rules だけを enum に出す」方が正しく見えるが、**戻してはいけない**。
根拠は llama-server (llama.cpp) への統制プローブと実タスク計測(`return_progress` の `prompt_cache` ログ、PR #931)で確定した以下の実測法則:
1. 追記のみのリクエスト → ほぼ全キャッシュ再利用(評価は追記分のみ)
2. 末尾付近の書き換え → 分岐点以降のみ再評価(部分再利用は健在)
3. 共通接頭辞が全体の半分未満 → 全損(`slot_prompt_similarity` 既定 0.5 の門前払い)
4. **tools 配列の変更 → 位置・大小問わず全損(description 1 文字でも)**
5. 描画順は system 本文 → tools → 会話(Qwen3 テンプレート)
movement 毎に transition ツールを作り替えると法則 4 に直撃し、**遷移のたびに会話全体 (~20k トークン / ~12 秒) を再評価**する(実測: 遷移直後の再利用率 0%、tools 安定なら 99%+)。movement 単位の遷移制約は runtime 検証(`processTransitionCalls` → `validateTransition`、無効なら有効選択肢つきエラーを tool result で返す)と、movement guidance の選択肢一覧(会話への追記なのでキャッシュ無害)が引き続き完全に担保している。
この構造を変更する場合は、開発サーバーで movement 遷移を跨ぐタスクを流し、`[llm-stream] ... prompt_cache reused=/pct=` で遷移直後の再利用率が 99% 台を維持することを必ず実測確認すること。
## 関連
- 設計: `docs/design/2026-06-26-subtask-wait-and-safety.html`(WaitSubTask ツール=この型の新規適用例)
- 実装: `src/engine/agent-loop.ts`(`finishMovement` / 制御フローツール識別)、`src/engine/piece-runner.ts`(`mapMovementResult`)、`src/worker.ts`(待機ジョブの停車・再開)