Chapter 3

第3章: think → act → observe ─ エージェント本体のサイクル

Claude Code に「tests/ が全部緑になるまで直して」と頼む。 画面ではこんなことが流れていく。

● Bash(pnpm test)
  ⎿  3 failing
● Read(src/parser.ts)
● Read(tests/parser.test.ts)
● Edit(src/parser.ts)
● Bash(pnpm test)
  ⎿  1 failing
● Edit(src/parser.ts)
● Bash(pnpm test)
  ⎿  all green
直しました。空配列のときに落ちる境界条件を 2 箇所修正しています。

8 ステップ。 人間が打ったコマンドは最初の 1 つだけ。 何が起きていて、なぜ止まれたのか。

この章で、その心臓部を解剖する。

3.1 そもそも、なぜ “繰り返し” が要るのか

本書冒頭で見たとおり、LLM 自体は続きを書く機械でしかない。第2章でツール呼び出しの 1 往復を見た ── LLM が tool_use を返し、ハーネスが実行し、結果を戻す。 だが、これを 1 往復で終わらせる と、エージェントとしてはまるで役に立たない。

ファイル 1 つ読んで終わり。テスト 1 回流して終わり。 これでは 「結果を見て次を考える」 ができない。

実務でやりたいことは、ほぼ全部 多段 だ。

エージェントの本質は 「1 ステップの賢さ」ではなく「次の 1 ステップを結果に応じて選び続けられること」 にある。 1 往復のツール呼び出しを、結果を踏まえて何度も繰り返す ── これがエージェントの心臓部だ。

ここから本章では、この心臓部を think → act → observe という 3 拍子で捉え直す。

3.2 think → act → observe ── 三拍子のサイクル

ハーネスが回しているループを、最小の言葉で書き下す。

拍子やっていること担当
think今の状況を読み、次に何をするか考えるLLM
act道具を呼ぶ (= tool_use ブロックを出す → ハーネスが実関数を実行)LLM + ハーネス
observe道具の実行結果 (第2章で定義した tool_result) を観測し、次の入力に積むハーネス

そして observe の結果は 次の think に渡される

think → act → observe → think → act → observe → think → …

冒頭の例で言えば、こうなっている。

#think (LLM の頭の中)act (呼ばれた道具)observe (結果)
1まずテストを流して状況把握Bash(pnpm test)3 件失敗
2失敗箇所のファイルを読むRead(src/parser.ts)中身
3テスト側も読んでおくRead(tests/parser.test.ts)中身
4パーサーの境界条件を直すEdit(src/parser.ts)編集成功
5直ったか確認Bash(pnpm test)1 件失敗
6もう一箇所修正Edit(src/parser.ts)編集成功
7再確認Bash(pnpm test)all green
8「直しました」と返事(ツール呼び出しなし)

8 ステップ目だけが特別だ。tool_use ブロックを含まない、ただのテキスト応答。 ここでハーネスはループを抜ける。次節で詳しく見る。

3.3 ReAct パターン ── 思考と行動の交互

この三拍子は、現代のエージェント設計に独自に降ってきた発明ではない。 2022 年に Yao らが発表した ReAct という論文 (“ReAsoning + ACTing”) が、この型を明示的に世に出した。

ReAct

LLM に 思考 (Reasoning) と行動 (Action) を交互に出力させる プロンプト技法。 それまで「LLM は思考だけ」「LLM はツール呼び出しだけ」の片方に寄っていたものを、思考 → 行動 → 観測 → 思考 → 行動 と織り交ぜることで、複雑な多段タスクの精度が大きく上がることを示した。

ReAct 以前の素朴な発想は、こうだった。

「最初に一気に計画を立てさせて、そのあと計画通り順に道具を呼ばせれば早い」

ところがこれは脆い。 途中で予想外の結果が出ても、最初の計画に従って暴走する。 観測した結果に応じて、次の手を即座に変える ── この柔軟さこそが、複雑なタスクで効くと分かった。

人間がデバッグするときと同じだ。 「テスト失敗 → ファイル読む → 仮説を立てる → 直す → 流す → 違った → 別の仮説 → …」 やりながら考える という人間的な進め方を、LLM のループで模倣しているのが think-act-observe である。

学術的には ReAct という名前は今や日常語ではないが、サイクルとしての思想は Claude Code を含む全エージェントの土台 として残っている。

3.4 サイクルの絵 ── 矢印で押さえる

ここまでを 1 枚にまとめる。

thinkLLM が次の手を考える入力 = これまでの全履歴acttool_use ブロックを出すハーネスが実関数を呼ぶobserve実行結果を観測tool_result を履歴に追加道具呼び出しを含む結果が次の入力になる続きを考える停止道具を呼ばない応答が出た道具呼び出しなしのテキスト応答が出るとループ脱出
図 3.1 — think-act-observe サイクル。黒矢印が通常のループ、赤い破線が停止の経路。

通常のループ (黒い矢印) と、停止経路 (赤い破線) の 二通り が think の出口にあることが、この図のいちばんの肝だ。

3.5 ループはどうやって止まるのか

第1章の最後でも触れたが、ここで正面から扱う。 ループを止める経路は、実用上 3 つ ある。

① LLM が “もう道具を呼ばない” 応答を返したとき (自然停止)

think の出力が tool_use ブロックを 含まないテキスト だったら、ハーネスはループを抜ける。 冒頭の例の 8 ステップ目「直しました。…」がこれ。 LLM 自身が「終わった」と判断する のがこの経路で、エージェントの最も基本的な停止方法。

② max_turns 上限に達したとき (強制停止)

ハーネスは普通 「最大何ターンまで回すか」 の上限を持っている。 これがないと、後述する暴走で永遠に止まらない可能性がある。Claude Code でも内部に上限がある。

max_turns (max steps)

1 つのユーザー依頼に対して、think-act-observe サイクルを最大何回まで回すかの上限。安全装置。 小さすぎると複雑なタスクが途中で打ち切られ、大きすぎると無駄に長時間動き続ける。設計判断の塊。

③ 人間が割り込んだとき (人間停止)

ユーザーが Esc キーで止める、別の指示を投げる、など。 特に Claude Code のようなインタラクティブな CLI では、これが日常的に効く停止経路だ。

「LLM が自分で終わりを判断する」 が基本路線。 だが LLM は判断を間違えるので、max_turns と人間の介入 が必ずバックアップとして要る。 完全に自走させたいなら停止条件設計、対話で使うなら割り込み導線 ── どちらの設計判断もハーネス側が握る。

3.6 1 ターンに複数の道具 ── 並列ツール呼び出し

ここまで暗に「1 ターン = 1 道具呼び出し」のように描いてきたが、実は違う。 第2章で触れた並列呼び出しを、ループの実行モデルから見直すとこうなる ── 1 つの think の出力に、複数の tool_use ブロックを並べることができる

たとえば「a.pyb.pyc.py を全部読んで」と頼まれた LLM は、こう返せる。

[
  { "type": "tool_use", "name": "Read", "input": { "path": "a.py" } },
  { "type": "tool_use", "name": "Read", "input": { "path": "b.py" } },
  { "type": "tool_use", "name": "Read", "input": { "path": "c.py" } }
]

ハーネスは 3 つを 同時に (並列で) 実行し、3 つの結果をまとめて次の think に渡す。 これが 並列ツール呼び出し (parallel tool use) だ。

順番に呼んでいたらネットワーク往復や I/O 待ちで遅くなるところを、並列で叩いて一気に観測 することで、エージェントの体感速度が大きく変わる。

Claude Code が「ファイル 5 個をパッと読んでサッと判断する」ように見えるとき、内部ではこの並列呼び出しが効いている。 2024 年以降のエージェント設計の重要な進歩のひとつ。

ただし注意がある。並列で呼べるのは 互いに独立な道具呼び出し だけだ。 「Edit してから Bash(pnpm test)」のように 順序に意味があるもの は、LLM 側で順に分けて呼ぶ。 ここの判断も LLM の責任になっている ── ハーネス側で機械的にどうにかできる話ではない。

3.7 「自分で終わりを決める」ことの危うさ

最後に、本章で何度か漂わせてきた違和感を正面から書いておく。

think-act-observe サイクルは、エージェントの強さの源だ。 しかし同時に、最大の弱点でもある

LLM 自身が「終わり」を判断する以上、判断ミスは即座にループの病として現れる。

早すぎる終了

「テストを通してから報告して」と頼んだのに、テストを 1 回流して 1 件失敗のまま「だいたい直しました」と止まってしまう。 LLM が “もういいだろう” と早めに見切る ── これは、最も日常的に出くわすエージェントの失敗だ。

終わらない無限ループ

逆に、同じ場所で何度も Editpnpm test → 失敗 → Edit → … を繰り返し、抜け出せない。 前と同じ失敗が出ているのに、それを観測として活かせず同じ手を打ち続ける

これを防ぐのが max_turns であり、ここから先の章で扱う context 管理 (第4章)Plan mode (第7章)、そして 限界としての無限ループ (第15章) の話につながる。

think-act-observe というサイクルは、見ためのシンプルさに対して、内部の “終わり方” が驚くほど難しい。 人間でも、デバッグを切り上げる判断や、同じバグに延々と取り組み続ける罠は普通にある。 LLM はこれを より大胆に やってしまう ── 自信過剰にも、頑迷にも。

エージェントを賢く使う側のコツの大半は、この弱点をどう囲い込むか に集中している。 本書の残りは、囲い込みの道具を 1 つずつ揃えていく旅でもある。

3.8 章を貫く問い ── ループはどこに住むか

最後にひとつ、視点を引いておく。

think-act-observe サイクルは どこに実装されているか? 答えは ハーネスの中 だ。 LLM 側にループの概念はない。LLM はあくまで「1 回分の think (続きを書く)」しかしない。 「もう一度呼ぶか、止めるか」を決めているのはハーネスのコード であって、LLM ではない。

第1章の主張がここでも貫かれる ── 残り半分 (道具設計とコンテキスト設計) は、第4章以降で順に明かしていく。

この章の振り返り

この章で読めるようになるツイート / ブログ

次章は、ここで何度も「履歴に積む」「次の入力に渡す」と書いてきたものの正体に踏み込む。 LLM の思考をささえる有限の “机の広さ” ── コンテキストウィンドウ の話だ。