変更の計画
プランナーにリクエストとハンドブックを渡すと、バイト単位で正確な編集プランと、何に触れるかの機械可読な宣言が返ってきます。
handbook plan --source <repo> --handbook <dir> --request "<text>" --out plan.mdプランナーは 読み取り専用 のエージェントです。一覧・読み取り・grep を行います — 書き込みツールは無効化されたものすら一切持ちません — そしてその出力は、別の何かが実行する ためのプランです。
ループ
- ハンドブックでルーティングする: どのファイル、関数、状態がスコープに入るか?
- 見つけたすべてのアドレスで 実際のソース を読む。
- バイト単位で正確な
oldとnewテキストを持つ### EDIT nブロックを出力する。 - JSON の宣言ブロックで締めくくる。
2 つの成果物、2 つの役割
ハンドブック はロケーションインデックスです: テキスト検索では見逃す、散在した自明でない箇所 — ミラー実装、ある状態のすべての読み取りと書き込み、サブシステム横断の接点 — を浮かび上がらせます。 実際のソース は、何を変更すべきかについての基準となる真実です。ハンドブックがアドレスを与え、 そのアドレスにあるコードがバイト列を与えます。
良いリクエストの書き方
| 弱い | 強い |
|---|---|
| 「アップロードのバグを直して」 | 「503 で失敗したアップロードは、エラーを表面化させる前に指数バックオフで 3 回リトライすべき」 |
| 「ログを追加して」 | 「完了したすべての HTTP リクエストについて、既存のロガーを使い、リクエスト ID と所要時間を INFO でログに出す」 |
| 「速くして」 | 「resolveTenant の結果をテナント ID をキーに 60 秒間キャッシュする」 |
欲しい振る舞い を述べてください。それがあると思うファイル名ではありません。ファイルを 指名すると、プランナーの探索はあなたがすでに思いついた場所に狭められます — それでは本末転倒 です。
プランの読み方
### EDIT 1
- file: `src/upload.py`
- where: `Uploader.send (~88)` — wrap the request in the retry helper
```old
response = self._client.put(url, data)
```
```new
response = self._retry(lambda: self._client.put(url, data), attempts=3)
```
### EDIT 2
- file: `src/upload.py`
- where: `Uploader` — add the helper
```old
def send(self, url, data):
```
```new
def _retry(self, call, attempts):
last = None
for _ in range(attempts):
try:
return call()
except TransientError as exc:
last = exc
raise last
def send(self, url, data):
```
Both call sites now share one retry policy.
```json
{ "will_modify": ["Uploader.send"], "will_add": ["Uploader._retry"], "will_remove": [] }
```このフォーマットが従うルール:
oldは バイト単位で正確 に、ファイル内に ちょうど 1 回 現れなければなりません。- 空 の
oldは「このファイルを作成する」を意味します。 - 編集は番号付きで、上から下へ 昇順 に並びます。
- 末尾の
jsonブロックはresyncに消費され、リフレッシュ範囲を絞り込むのに使われます。
適用する前にプランを読んでください。 ドライランが教えてくれるのは適用 できる か どうかです。適用 すべき かどうかを判断できるのはあなただけです。
諦めたとき
plan は 非ゼロで終了します — スクリプトが apply に流し込んでしまうような謝罪文を
plan.md に書き込んだりはしません。
aborted | 何が起きたか | どうするか |
|---|---|---|
fabrication | 応答が ## Tool result セクションを 3 回捏造した — 想像上のファイル内容の上で推論していた | より強いモデルを使ってください。その実行から得られたものは何も信頼できません |
turn-limit | EDIT ブロックがないままターンを使い切った | --max-turns を上げるか、リクエストを絞ってください |
no-plan | 使えるものが何もないまま finish を呼んだ | たいていはコード変更を必要としないリクエストか、局所化するには曖昧すぎるリクエストです |
なぜ捏造は即座に拒否されるのか
観測されたある応答には、13 個の捏造されたツール結果と、ファイルに存在しない行から組み立てられたプランが 含まれていました。プランナーはその応答を丸ごと拒否します — 末尾のプランも含めてです。 そのプランはフィクションから導かれたものだからです。
チューニング
| フラグ | デフォルト | いつ変えるか |
|---|---|---|
--max-turns <n> | 30 | 大きなリポジトリや広範な変更では上げ、コストを抑えたいときは下げます |
--model <id> | gpt-4o-mini | より強いモデルの恩恵を最も受けるのがこのコマンドです |
--handbook <dir> | — | 常に渡してください。これがないとプランナーは手探りで探索します |
--out <file> | (stdout) | パイプするときは省略します |
ハンドブックなしの場合
handbook plan --source ~/code/api --request "…"動作はします — プランナーはソースを直接探索するモードにフォールバックします — が、これは 劣化モードです。ハンドブックが存在するのはまさに、ガイドなしの探索は自明な箇所を見つけて 散在した箇所を見逃すからです。
サンドボックスが許可すること
list_dir(path)
read_file(path, start_line?, end_line?)
grep(pattern, path)
finish(plan)- すべてのパスはサンドボックスルートの内側で解決されます。シンボリックリンク経由を含め、 外への脱出は拒否されます。
- ハンドブックは
__handbook__/に読み取り専用でマウントされます。これはソースとは 別個の サンドボックスです。 - 読み取りは 60,000 文字が上限、grep は 100 ヒットが上限で、5 MB を超えるファイルは スキップされます。
- 破滅的な正規表現 —
(a+)+や(.*)*のような、量指定子を含むグループへの無制限の 量指定子 — は、実行をハングさせる代わりに、穏当なツールエラーとして拒否されます。