コンテンツにスキップ

agent loop の 1 周を掴む

tier 3(必要になったら)確認 1 か月前(2026年8月23日)

どこまで来たら使えたと言えるか

面: 両方で同じ

段達成したと言える状態
1. 触る1 回の実行で流れてくるメッセージを全部そのまま出力し、SystemMessage から ResultMessage までの並びを見た
2. 入れるメッセージ型で分岐する処理を書き、ResultMessage の subtype で成功と上限到達を区別している
3. 効かせるmax_turns と max_budget_usd の効き方を踏まえて上限を設計し、ストリームを最後まで反復する実装にしている

何のためのものか

Agent SDK が回す実行ループの中身。CLI を動かしているのと同じループなので、ここが分かると CLI 側の挙動も同時に説明が付く。

触る

1 周は 5 段階。

  1. プロンプト受領 — システムプロンプト・ツール定義・会話履歴とともに Claude が受け取る → SystemMessage(subtype "init")
  2. 評価と応答 — テキスト、ツール呼び出し要求、またはその両方 → AssistantMessage
  3. ツール実行 — SDK が各ツールを実行し結果を集める。hook で実行前に介入・改変・拒否できる
  4. 反復 — 2 と 3 を繰り返す。1 周が 1 ターン
  5. 結果返却 — ツール呼び出しの無い応答が出たら終わり → ResultMessage

まずは全メッセージをそのまま出力して、この並びを目で見る。

入れる

メッセージ型で分岐する。

型いつ出るか
SystemMessageセッションのライフサイクルイベント。subtype で区別する
AssistantMessageClaude の応答ごと
UserMessageツール実行ごとに、Claude へ返すツール結果を伴って
ResultMessageループの終了。最終テキスト・トークン使用量・コスト・セッション ID
  • 判定方法が言語で違う。 Python は import したクラスへの isinstance()、TypeScript は type 文字列(message.type === "result")
  • TypeScript ではコンテンツブロックが message.message.content にある(message.content ではない)
  • subtype は "init" / "compact_boundary" / "informational" / "worker_shutting_down"。成功したか上限に当たったかは ResultMessage の subtype で判定する

効かせる

  • ターンはツール使用のぶんだけ数える。 max_turns / maxTurns はツール使用のターンのみを数える。上限を設けなければ Claude が自分で終わるまで走る
  • 金額でも止められる。 max_budget_usd / maxBudgetUsd
  • 1 ターンの間、呼び出し側のコードに制御は戻らない。 途中で何かしたいなら hook を使う
  • SessionStart / Setup hook が走る場合、その hook ライフサイクルメッセージが init より先に届く

落とし穴

  • ResultMessage で break しない。 その後にも prompt_suggestion などの後続システムイベントが少数届きうる。ストリームは最後まで反復する
  • ループの駆動に必須でないイベントも流れてくる(レート制限の状態、タスク通知など)。知らない型で落ちない実装にする
  • TypeScript では "init" 以外の subtype は SDKSystemMessage の subtype ではなく、SDKMessage union の独立した型になる

根拠と前後

このページが書ける範囲は、根拠にした facts に書かれていることだけです。 仕様そのものはそちらを読んでください。