ループなくして agent なし
Claude Code の仕組みは一行で書ける:while stop_reason == "tool_use"
agent は実際に何をしているのか
ターミナルで Claude Code を起動し、「すべての TODO コメントをリストにまとめて」と頼むと、まず grep、次にいくつかのファイルを cat、そして Markdown を出力する——そんな動きを目にする。モデルはコードを自分で実行しない——実行を依頼するだけだ。実際に「手足」のように見せているのは、その外側にある 30 行ほどのグルーコードだ。
このレッスンではそのグルーコードを解体して正体を明らかにする。これが agent loop(エージェントループ)で、構造はこうだ:
while response.stop_reason == "tool_use": response = LLM(messages, tools) # 1. ask the model execute_tools(response.tool_calls) # 2. run what it asked for messages.append(tool_results) # 3. feed results back
以上だ。それだけ。本番の Claude Code はこの上に権限管理・hook・サブエージェント・worktree 分離・メモリ圧縮を積み上げているが、核心は変わらずこの4行だ。
重要な直感:モデルの返答は常に「ツールを呼びたい」か「言い終わった」のどちらかだ。前者の状態が続く限りループは回り続け、後者に入った瞬間ループは終了する。判定基準はレスポンスの stop_reason フィールドだ。
messages[] が一歩ずつ育っていく様子
以下のループが模擬するタスクは「カレントディレクトリのファイルを一覧にして、package.json を読み込む」だ。Step を押すたびにループ内の操作が一つ進む。左側が人間向けの会話バブル、右側がモデルに渡される実際の messages[] 配列——どう増えていくかに注目してほしい。
ツール結果は必ずメッセージ履歴に戻さなければならない
初心者が最もはまる落とし穴は、「ツールを実行した」ことを副作用として扱ってしまうことだ——実行して終わり、と。しかしモデルは次のターンで messages[] しか参照できない。そこにない情報は起きていないも同然だ。append 一行を忘れるだけで、ループ全体が壊れる。
壊れ方は一種類ではない。よくある2パターン:
- append を忘れる:モデルは次のターンで結果を確認できないため、同じツールをもう一度呼ぶ——無限ループの出来上がり。
- append したが
tool_use_idを落とす:Anthropic API は直接tool_result must have tool_use_idエラーを返し、ループは API 呼び出し時点でクラッシュする。
同じタスク:「プロジェクト内の Python ファイルの数を数える」。2つのバージョンを並べて実行し、結末の違いを確認しよう。
stop_reason を読み解く
モデルが返すたびに stop_reason が付いてくる。ループを続けるかどうかはこのフィールド一つで決まる:
tool_use— モデルがツールを呼びたい。ループを継続。end_turn— モデルが言い終えた。ループを終了。max_tokens— token 上限に達して切り捨てられた。ループを終了(通常は例外として扱い、出力が不完全であることをユーザーに伝える)。stop_sequence— カスタムの停止文字列にヒットした。ループを終了。
「tool_use 以外なら継続」と書き間違えたり、max_tokens を無視して正常終了として扱ったりするのはよくあるバグだ。
手を動かしてみる
ローカルに shareAI-lab/learn-claude-code をクローン:
git clone https://github.com/shareAI-lab/learn-claude-code cd learn-claude-code pip install -r requirements.txt cp .env.example .env # fill in ANTHROPIC_API_KEY python agents/s01_agent_loop.py
そして実際に何か頼んでみよう:このリポジトリにある .py ファイルの数を数えて。ls -R を送信し、出力を確認し、次に find . -name "*.py" | wc -l を送信して答えを返す——その一連の流れがこのループそのものだ。