コンテンツにスキップ

エージェントループ:モデル呼び出しとツール使用の反復

対象読者: 学部レベルの離散数学(集合・写像・数学的帰納法)と、契約による設計(事前条件・事後条件・不変条件)の基礎を学んだ人。Claude Code の利用経験があれば十分で、API の知識は仮定しません。

前提となる記事:契約による設計:事前条件・事後条件・不変条件と Hoare 論理、LLMとプログラミング:委譲の損益分岐点と「もっともらしい誤り」の見抜き方

生 Markdown

Claude Code に「失敗しているテストを直して」と頼むと、ファイルを読み、コマンドを動かし、結果を見てまた次の手を打つ、という動きをします。この動きの裏には、AI の答えを受け取って道具を動かし、その結果をまた AI に見せる、という単純な繰り返しがあります。この記事では、その繰り返しを小さな数式の模型で表し、途中で止まるのか、どれだけ文章が膨らむのか、いくらまで使うのかを、はっきり言える形で調べます。読み終えると、エージェントが「なぜ止まるのか」「なぜ長い作業ほど重くなるのか」を、理由つきで説明できるようになります。

  • Claude Code の中心は、モデルを呼ぶ → 応答にツール呼び出しがあれば実行する → 結果をメッセージ列の末尾に足してもう一度モデルを呼ぶ、という反復です。公式ドキュメントはこれを agentic loop と呼び、モデルの周りでツールを提供し文脈を管理する層を agentic harness(ハーネス。モデルを動かす外側のプログラムのこと)と呼んでいます。
  • この記事では、公開されている仕様(Messages API のツール使用の書式、Agent SDK のループの説明)を状態遷移の模型として置き、その模型の中で性質を証明します。模型はモデルの中身を何も仮定しません(任意の写像として扱います)。
  • 模型のループは、「ツール呼び出しのある assistant メッセージの直後に、同じ id の結果がちょうど 1 つずつ先頭に並ぶ」という不変条件(処理のあいだ、ずっと成り立つ条件)を保ちます。これは API が要求する書式そのもので、ツールの実行が拒否された場合にも保たれます。
  • ツール使用ターンの上限 TT を置くと、モデルの呼び出しは高々 T+1T + 1 回で止まります。上限を置かないループの停止は、モデルの振る舞いに依存し、ハーネスの側からは保証できません。
  • 圧縮をしなければ、kk 回目の呼び出しの入力は kk について 1 次で増え、入力の合計は 2 次で増えます。コンテキストウィンドウ(モデルが一度に読める量の上限)WW のもとで続けられる呼び出しの回数には上界があります。
  • 支出の上限を「応答を受け取ったあとで比べる」規則では、最終の支出は上限を 1 応答ぶん未満だけ超えうる、という公式ドキュメントの注意が、判定の時点から導けます。

この記事の事実の記述は、2026-10-10 に確認した公式ドキュメントに基づきます。コマンド名・既定値・モデル名は版と時期で変わるので、各節の末尾の出典で最新の記述を確かめてください。

言語モデルは、与えられたトークン列の続きを 1 トークンずつ生成する関数です(推論:自己回帰生成・KV キャッシュ・サンプリング・量子化)。それだけでは、ファイルを読むことも、テストを実行することもできません。コーディングエージェントが「失敗しているテストを直す」ことができるのは、モデルの外側に、モデルの出力を読んで実際にコマンドを実行し、その結果を次の入力に入れるふつうのプログラムがあるからです。

したがって、エージェントの振る舞いを理解するには、2 つを分けて考えるのが有効です。

  1. モデルが何を出力するか。これは学習で決まり、確率的で、外から保証できることはわずかです。
  2. ハーネス(モデルの周りのプログラム)が、その出力をどう扱うか。これは決定的なコードで、不変条件や停止性を証明できます。

LLM とプログラミング では、仕事を任せるかどうかの判断を、委譲コスト(定義 2.1[LLMとプログラミング])と検証器(定義 2.2[LLMとプログラミング])で扱いました。この記事は、その先、任せたときに何が動いているかを扱います。モデルがなぜそのツールを選ぶのかは説明しません。公開資料に書かれていないからです。説明するのは 2 のほう、ハーネスが守る規則と、そこから導ける性質です。

このテーマの以降の章は、すべてこの記事のループへの操作として書かれます。ツールと権限 はループの「ツールを実行する」直前の関門を、コンテキストウィンドウとプロンプトキャッシュ は「モデルに渡すメッセージ列」の大きさと料金を扱います。

2. メッセージ・ブロック・ツール:公開されている書式

Section titled “2. メッセージ・ブロック・ツール:公開されている書式”

まず、公開されている API の書式を定義として整理します。ここでの「環境」は、ファイルの内容のように、ツールが読み書きする外側の状態のことです。また、ツールの入力の形を書き表す JSON Schema は、「どんな名前の項目がどんな型で入るか」を決める記法です。この節の定義は、Claude API のドキュメントの記述を、後の証明で使う範囲に絞って抽象化したものです。

定義 2.1(メッセージと内容ブロック)↗

Id\mathrm{Id} を識別子の集合、Name\mathrm{Name} をツール名の集合、In\mathrm{In} をツールの入力(JSON の値)の集合、Str\mathrm{Str} を文字列の集合とする。内容ブロックは次の 3 種類のいずれかである。

  • text ブロック text(s)\mathsf{text}(s)(s∈Strs \in \mathrm{Str})。
  • tool_use ブロック use(u,n,x)\mathsf{use}(u, n, x)(u∈Idu \in \mathrm{Id}、n∈Namen \in \mathrm{Name}、x∈Inx \in \mathrm{In})。モデルが「ツール nn を入力 xx で呼びたい」と表明したもの。
  • tool_result ブロック res(u,y,ϵ)\mathsf{res}(u, y, \epsilon)(u∈Idu \in \mathrm{Id}、y∈Stry \in \mathrm{Str}、ϵ∈{0,1}\epsilon \in \{0, 1\})。id が uu の呼び出しの結果 yy。ϵ=1\epsilon = 1 は実行が失敗したこと(is_error)を表す。

メッセージは、役割 ρ∈{user,assistant}\rho \in \{\mathtt{user}, \mathtt{assistant}\} と内容ブロックの有限列の組 (ρ,(b1,…,bℓ))(\rho, (b_1, \ldots, b_\ell)) である。メッセージ mm に現れる tool_use ブロックの id を、現れる順に並べた列を U(m)U(m) と書く。

API の実物では、ブロックには画像や文書などの種類もあり、tool_result の内容は文字列のほかブロックの列でもかまいません。ここでは 3 種類に絞ります。役割についての注意が 1 つあります。Claude API には tool や function といった役割はなく、ツール呼び出しは assistant メッセージの中のブロック、結果は user メッセージの中のブロックとして表されます。「user メッセージ」は、人間が書いた文章とは限りません。

定義 2.2(ツールの定義とツールの実行)↗

ツールの定義は、名前 name、説明 description、入力の形を JSON Schema で書いた input_schema の 3 つ組である。モデルには定義が渡され、モデルは説明を読んで呼ぶかどうかを決める。

環境の状態の集合を EE とする(ファイルシステムの内容、外部のサービスの状態など)。名前 nn のツールの実行を、写像

execn:In×E→(Str×{0,1})×E\mathrm{exec}_n : \mathrm{In} \times E \to (\mathrm{Str} \times \{0, 1\}) \times E

として置く。execn(x,e)=((y,ϵ),e′)\mathrm{exec}_n(x, e) = ((y, \epsilon), e') は、「状態 ee で入力 xx を与えると、結果 yy と失敗の印 ϵ\epsilon を返し、環境が e′e' に変わる」ことを表す。

ツールは、実行される場所で 2 種類に分かれます。クライアント側のツール(client tools)は、API を呼ぶアプリケーションの側で実行されます。モデルの応答は tool_use ブロックを含んで止まり、アプリケーションが実行して結果を送り返します。サーバ側のツール(server tools。ウェブ検索、コード実行など)は API の提供者の基盤で実行され、アプリケーションは実行を扱いません。Claude Code の組み込みツール(ファイルの読み書き、シェルコマンドの実行など)は、利用者の機械の上で動くので、前者の形です。この記事の模型はクライアント側のツールだけを扱います。

定義 2.3(stopreasonstop_reason)↗

API の応答は、生成が止まった理由 stop_reason を持つ。2026-10 時点の公式ドキュメントが挙げる値は次の 7 つである。

値止まった理由
end_turnモデルが応答を自然に終えた
tool_useモデルが(クライアント側の)ツールを呼ぼうとしている
max_tokens出力が要求の max_tokens の上限に達した
stop_sequence指定した停止文字列が出力された
pause_turnサーバ側のツールの反復が、その回数の上限に達した
refusalモデルが応答を断った
model_context_window_exceeded出力の途中でコンテキストウィンドウの上限に達した

ループを書くうえで本質的なのは tool_use とそれ以外の区別です。pause_turn はサーバ側のツールを使うときにだけ現れ、公式ドキュメントは、その反復の上限の既定を「1 回の要求あたり 10 回」と述べ、応答をそのまま送り返して続きを求めるよう指示しています。この記事の模型には入れません。

定義 2.4(ハーネス)↗

モデルの周りで、(i) ツールの定義をモデルに渡し、(ii) モデルが出力した tool_use ブロックを読んでツールを実行し、(iii) モデルに渡すメッセージ列(文脈)を組み立てて管理するプログラムを、ハーネスという。

公式ドキュメントは、ループを動かすものを「推論するモデル」と「行為するツール」の 2 つとし、Claude Code を「ツールを提供し、モデルが見る文脈を管理する、モデルの周りの層」と説明して、これを agentic harness と呼んでいます。定義 2.4 はその説明を定義の形にしたものです。同じドキュメントは、組み込みツールを、ファイル操作・検索・実行・ウェブ・コード解析の 5 つに分類し、このほかにサブエージェントの起動や利用者への質問などの調整用のツールがあると述べています。

書式には、後で不変条件として証明する規則があります。tool_result ブロックは、対応する tool_use ブロックを含む assistant メッセージの直後のメッセージに置き、あいだに別のメッセージを挟めません。その user メッセージの中では tool_result ブロックを先頭に並べ、文章はそのあとに置きます。 公式ドキュメントは、文章を先に置いた例を「400 エラーになる」例として示しています。

出典(確認日 2026-10-10): Tool use with Claude、Handle tool calls、Stop reasons and fallback、How Claude Code works。

Agent SDK のドキュメントは、ループを 5 段階で説明しています。(1) プロンプトを受け取る。(2) モデルが評価して、文章・ツール呼び出しの要求・その両方のいずれかで応答する。(3) ハーネスが要求されたツールを実行し、結果をモデルに返す。(4) 2 と 3 を繰り返す。(5) ツール呼び出しのない応答が出たら、それを最終の結果として返す。これを状態遷移系として書きます。次の定義は、ループを「これまでのメッセージ列・環境・済んだターン数」という 3 つ組が 1 段ごとに更新されていく仕組みとして書いたものです。1 段では、モデルを 1 回呼び、ツール呼び出しが無ければ終わり、あれば実行して結果を足します。4 に出てくる e(j)e^{(j)} は、jj 番目の呼び出しを実行した直後の環境です。

定義 3.1(エージェントループの模型)↗

次のものが与えられているとする。

  • モデル MM: メッセージの有限列を受け取り、assistant メッセージを 1 つ返す写像。内容は text ブロックと tool_use ブロックだけからなり、1 つのメッセージの中の tool_use ブロックの id は互いに異なるとする。それ以外の性質は何も仮定しない。
  • 各ツール名 nn に対する実行 execn\mathrm{exec}_n(定義 2.2)。
  • 関門 g:Name×In→{run,refuse}g : \mathrm{Name} \times \mathrm{In} \to \{\mathrm{run}, \mathrm{refuse}\}。呼び出しを実行するか拒否するかを決める写像。
  • 拒否を伝える文字列 yref∈Stry_{\mathrm{ref}} \in \mathrm{Str}。
  • ツール使用ターンの上限 T∈{0,1,2,…}∪{∞}T \in \{0, 1, 2, \ldots\} \cup \{\infty\}。

状態は、メッセージ列 hh、環境 e∈Ee \in E、済んだツール使用ターンの数 tt の組 (h,e,t)(h, e, t) である。初期状態は、利用者のプロンプト pp と環境 e0e_0 に対して ((user,(text(p))), e0, 0)((\mathtt{user}, (\mathsf{text}(p))),\ e_0,\ 0) とする。状態 (h,e,t)(h, e, t) からの 1 段を次のように定める。

  1. a=M(h)a = M(h) を求める(モデルの呼び出し 1 回)。

  2. U(a)U(a) が空なら、完了として停止する。最終のメッセージ列は h⋅ah \cdot a である。

  3. U(a)U(a) が空でなく t=Tt = T なら、ターン上限として停止する。aa のツール呼び出しは実行せず、メッセージ列は hh のままとする。

  4. それ以外のとき、aa の tool_use ブロックを現れる順に use(u1,n1,x1),…,use(uq,nq,xq)\mathsf{use}(u_1, n_1, x_1), \ldots, \mathsf{use}(u_q, n_q, x_q) とし、e(0)=ee^{(0)} = e から順に、j=1,…,qj = 1, \ldots, q について

    • g(nj,xj)=rung(n_j, x_j) = \mathrm{run} なら ((yj,ϵj),e(j))=execnj(xj,e(j−1))((y_j, \epsilon_j), e^{(j)}) = \mathrm{exec}_{n_j}(x_j, e^{(j-1)})、
    • g(nj,xj)=refuseg(n_j, x_j) = \mathrm{refuse} なら (yj,ϵj)=(yref,1)(y_j, \epsilon_j) = (y_{\mathrm{ref}}, 1)、e(j)=e(j−1)e^{(j)} = e^{(j-1)}

    と定める。user メッセージ b=(user,(res(u1,y1,ϵ1),…,res(uq,yq,ϵq),c1,…,cr))b = (\mathtt{user}, (\mathsf{res}(u_1, y_1, \epsilon_1), \ldots, \mathsf{res}(u_q, y_q, \epsilon_q), c_1, \ldots, c_r)) を作る。ここで c1,…,crc_1, \ldots, c_r は 0 個以上の text ブロック(実行中に利用者が送った追加の指示など)である。次の状態は (h⋅a⋅b, e(q), t+1)(h \cdot a \cdot b,\ e^{(q)},\ t + 1) である。

4 の 1 回を 1 ターン(ツール使用ターン)という。

注意 3.2(模型と実物の対応)

この模型は、公式ドキュメントが述べる仕様を定式化したもので、Claude Code の実装そのものではありません。模型に入れた仮定を挙げます。

  • モデルの呼び出しとツールの実行は、必ず値を返して終わると仮定しています(写像として置いたため)。実物では通信の失敗や利用者による中断があり、Agent SDK はそれを結果の種別 error_during_execution で表します。
  • 1 つの応答の中のツール呼び出しを順に実行するとしています。実物が並行に実行する場合については 命題 6.2 で扱います。
  • 1 つのメッセージの中の tool_use の id が互いに異なることは、モデルの出力についての仮定です。API のドキュメントは id を「この tool_use ブロックの一意な識別子」と説明しています。
  • 文脈の圧縮、サブエージェント、フックは入れていません。圧縮とサブエージェントは 文脈を保つ・減らす・分ける で、フックは 拡張の仕組み で扱います。
メッセージ列 h(末尾に足すだけ)モデル Ma = M(h)a に tool_useはあるか関門 g → 実行(拒否も結果にする)結果 b を作るh ← h · a · b完了ターン上限(t = T)無いある(t が T 未満)
エージェントループの模型。モデルの応答に tool_use が無ければ完了、あれば関門を通して実行し、結果を末尾に足してモデルを呼び直す。上限 T に達していれば実行せずに止まる。

定義 3.3(対応の不変条件)↗

メッセージ列 h=(m1,…,mN)h = (m_1, \ldots, m_N) が次の 3 条件を満たすとき、hh は対応の不変条件 Inv\mathrm{Inv} を満たすという。

  • (P1) m1m_1 の役割は user\mathtt{user} で、mNm_N の役割も user\mathtt{user} である。
  • (P2) 役割が assistant\mathtt{assistant} で U(mi)=(u1,…,uq)U(m_i) = (u_1, \ldots, u_q) が空でないすべての ii について、i<Ni \lt N であり、mi+1m_{i+1} は役割 user\mathtt{user} のメッセージで、その内容の最初の qq 個のブロックは、この順に id が u1,…,uqu_1, \ldots, u_q の tool_result ブロックである。
  • (P3) (P2) の mi+1m_{i+1} の q+1q + 1 個目以降のブロックに tool_result ブロックは無い。また、tool_result ブロックは (P2) の形で現れるものに限る。

(P2) と (P3) を合わせると、「assistant メッセージの各 tool_use の id に対し、直後の user メッセージに同じ id の tool_result がちょうど 1 つあり、それらが内容の先頭に並ぶ」となります。前節の末尾で見た API の書式の規則です。(P1) の後半は、モデルに渡すメッセージ列が user メッセージで終わることを言っています。

命題 3.4(ループは対応の不変条件を保つ)✓

定義 3.1 の模型で、モデル MM・ツールの実行・関門 gg・上限 TT が何であっても、初期状態から到達できるすべての状態 (h,e,t)(h, e, t) で、hh は Inv\mathrm{Inv} を満たす。特に、モデルの呼び出し M(h)M(h) に渡されるメッセージ列は、つねに Inv\mathrm{Inv} を満たす。

次の証明のねらいは、「最初に成り立ち、1 段ごとに保たれる条件は、いつまでも成り立つ」ことを、帰納法の形で確かめることです。確かめるのは、新しく足す 2 つのメッセージ aa と bb が、(P1)〜(P3) を壊さないことだけです。

証明(命題 3.4)

ループを、表現が状態 (h,e,t)(h, e, t)、初期状態の集合が Init={((user,(text(p))),e0,0)}\mathrm{Init} = \{((\mathtt{user}, (\mathsf{text}(p))), e_0, 0)\}、公開操作が「1 段」だけのデータ型(定義 6.1[契約による設計])とみます。状態を変えるのは 1 段の 4 だけなので、実行は表現を漏らしません。表現不変条件を「hh が Inv\mathrm{Inv} を満たす」と置き、定理 6.2[契約による設計] の 2 つの仮定(成立・保存)を確かめます。

成立。 初期状態の hh は 1 つの user メッセージ (user,(text(p)))(\mathtt{user}, (\mathsf{text}(p))) です。(P1) は N=1N = 1 で成り立ちます。assistant メッセージも tool_result ブロックも無いので、(P2) と (P3) は空虚に成り立ちます。

保存。 h=(m1,…,mN)h = (m_1, \ldots, m_N) が Inv\mathrm{Inv} を満たすとし、1 段が 4 に進んで h′=h⋅a⋅bh' = h \cdot a \cdot b になったとします(2 と 3 は停止で、状態を変えません)。U(a)=(u1,…,uq)U(a) = (u_1, \ldots, u_q)、q≥1q \ge 1 です。

(P1): h′h' の先頭は m1m_1 で役割は user\mathtt{user}、末尾は bb で役割は user\mathtt{user} です。

(P2): h′h' の中の、tool_use を持つ assistant メッセージは、hh の中のものか aa です。hh の中の mim_i については、仮定の (P1) から mNm_N は user メッセージなので i<Ni \lt N で、直後のメッセージ mi+1m_{i+1} は h′h' でも同じです。したがって仮定の (P2) がそのまま成り立ちます。aa については、直後のメッセージは bb で、定義から bb の最初の qq 個のブロックは res(u1,y1,ϵ1),…,res(uq,yq,ϵq)\mathsf{res}(u_1, y_1, \epsilon_1), \ldots, \mathsf{res}(u_q, y_q, \epsilon_q) です。jj 番目の結果が実行によるものか拒否によるものかに関係なく、id は uju_j です。

(P3): bb の q+1q + 1 個目以降は text ブロック c1,…,crc_1, \ldots, c_r だけです。aa は text と tool_use だけからなるので tool_result を含みません。したがって h′h' の tool_result ブロックは、hh にあったものと bb の最初の qq 個だけで、どれも (P2) の形です。

以上で成立と保存が確かめられたので、定理 6.2[契約による設計] により、到達できるすべての状態で hh は Inv\mathrm{Inv} を満たします。

この命題で大事なのは、モデルについて何も仮定していないことです(1 メッセージの中の id が異なることだけを使い、それも (P2) の「ちょうど 1 つ」を言うためだけです)。モデルがどんな順序で何個のツールを呼ぼうと、ハーネスが定義どおりに結果を並べる限り、次の要求は書式の規則を満たします。逆に、ハーネスが結果を 1 つ落としたり、結果の前に文章を置いたりすれば、次の要求は API に拒否されます。公式ドキュメントは、そのときのエラーメッセージの例として「tool_use の id に対して、直後に tool_result ブロックが見つからない」という趣旨の文を挙げています。

命題 3.5(拒否された呼び出しもループを止めない)✓

定義 3.1 の 1 段の 4 で、ある jj について g(nj,xj)=refuseg(n_j, x_j) = \mathrm{refuse} だったとする。このとき次が成り立つ。

  1. jj 番目の呼び出しは環境を変えない(e(j)=e(j−1)e^{(j)} = e^{(j-1)})。
  2. 次の状態のメッセージ列 h⋅a⋅bh \cdot a \cdot b は Inv\mathrm{Inv} を満たし、ループは停止せずに次の段へ進める。
  3. 一方、拒否された呼び出しの tool_result ブロックを bb から省く方式では、h⋅a⋅bh \cdot a \cdot b は (P2) を満たさない。
証明(命題 3.5)

(1) 定義の 4 の refuse\mathrm{refuse} の場合の式 e(j)=e(j−1)e^{(j)} = e^{(j-1)} そのものです。execnj\mathrm{exec}_{n_j} は適用されません。

(2) 命題 3.4 は任意の関門 gg について成り立つので、h⋅a⋅bh \cdot a \cdot b は Inv\mathrm{Inv} を満たします。次の状態 (h⋅a⋅b,e(q),t+1)(h \cdot a \cdot b, e^{(q)}, t + 1) は停止の状態ではなく、次の段の 1 で M(h⋅a⋅b)M(h \cdot a \cdot b) が計算されます。そのメッセージ列には res(uj,yref,1)\mathsf{res}(u_j, y_{\mathrm{ref}}, 1) が含まれるので、モデルは拒否されたことを入力として受け取ります。

(3) bb から res(uj,⋅,⋅)\mathsf{res}(u_j, \cdot, \cdot) を省くと、aa の直後のメッセージの最初の qq 個のブロックの中に id が uju_j の tool_result がありません(残りの tool_result の id は uju_j と異なり、省いたあとの bb に tool_result は q−1q - 1 個しかありません)。これは (P2) に反します。

Agent SDK のドキュメントは、ツールが拒否されたとき、モデルは拒否のメッセージをツール結果として受け取り、ふつうは別の方法を試すか、進められなかったことを報告する、と述べています。命題 3.5 は、その設計が書式の規則と両立する唯一の素直な形であることを示しています。拒否は「何も返さない」のではなく、「失敗という結果を返す」ことで表されます。関門 gg の中身(どの呼び出しを拒否するか)は次章で定義します。

出典(確認日 2026-10-10): How the agent loop works、Handle tool calls。

公式ドキュメントの説明では、ループは「モデルがツール呼び出しのない応答を出すまで」続きます。これはモデルの振る舞いについての条件です。ハーネスの側から停止を保証するには、別の仕掛けが要ります。

Agent SDK は 2 つの上限を用意しています。max_turns(TypeScript では maxTurns)は、ドキュメントの言葉で「ツール使用ターンだけを数える」上限、max_budget_usd(maxBudgetUsd)は見積もりの支出の上限です。2026-10 時点の公式ドキュメントでは、既定はどちらも「上限なし」です。上限に達すると、結果を表すメッセージの種別が error_max_turns または error_max_budget_usd になります。

定理 4.1(ターン上限つきループの停止と呼び出し回数の上界)✓

定義 3.1 の模型について次が成り立つ。

  1. TT が有限のとき、モデル MM・ツールの実行・関門 gg が何であっても、ループはモデルの呼び出しが高々 T+1T + 1 回、ツール使用ターンが高々 TT 回で停止する。
  2. 1 の上界は最良である。すなわち、モデルの呼び出しがちょうど T+1T + 1 回になるモデルが存在する。
  3. T=∞T = \infty のとき、停止しないモデルが存在する。

証明の方針は、残りのターン数 v=T−tv = T - t に注目することです。vv は 0 以上の整数で、ツールを使う段のたびにちょうど 1 減るので、いつかは使い切ります。T=∞T = \infty のときは vv が使えず、止まらない例が作れます。

証明(定理 4.1)

(1) 状態 (h,e,t)(h, e, t) に対して v=T−tv = T - t と置きます。まず、到達できるすべての状態で 0≤t≤T0 \le t \le T であることを、段の数についての帰納法(定理 3.2[証明の技術])で示します。初期状態では t=0t = 0 です。状態 (h,e,t)(h, e, t) で 0≤t≤T0 \le t \le T とし、1 段が停止しなかったとすると、それは定義の 4 に進んだ場合で、4 に進むのは 3 の条件 t=Tt = T が成り立たないとき、つまり t<Tt \lt T のときだけです。次の状態の値は t+1≤Tt + 1 \le T です。

したがって vv は到達できる状態でつねに 00 以上の整数で、停止しない段のたびにちょうど 11 減ります。これは vv が 定義 5.1[契約による設計] の 2 条件(下に有界・真に減る)を、不変条件 0≤t≤T0 \le t \le T のもとで満たすことを意味します(各段の中のモデルの呼び出しとツールの実行は、写像として置いたので必ず終わります)。vv の初期値は TT なので、停止しない段は高々 TT 回です。これは 定理 5.2[契約による設計] (1) の停止の議論と同じ型で、ここでは回数まで数えています。

停止しない段が kk 回続いたあとの状態では t=kt = k です。k≤Tk \le T なので、ツール使用ターンは高々 TT 回です。モデルの呼び出しは各段にちょうど 1 回なので、停止しない段 kk 回と停止する段 1 回で、合計 k+1≤T+1k + 1 \le T + 1 回です。あとは、停止しない段が TT 回続いたあとの段が必ず停止することを確かめます。そのときの状態は t=Tt = T で、a=M(h)a = M(h) について U(a)U(a) が空なら 2 で停止し、空でなければ 3 の条件 t=Tt = T が成り立つので停止します。

(2) M∞M_\infty を、どんなメッセージ列に対しても tool_use ブロックをちょうど 1 つ含む assistant メッセージを返すモデルとします。このモデルでは 2 による停止は起こりません。3 による停止は t=Tt = T のときだけ起こり、それまでの各段は 4 に進んで tt を 11 増やします。したがって停止しない段がちょうど TT 回、停止する段が 1 回で、呼び出しは T+1T + 1 回です。

(3) T=∞T = \infty のとき、3 の条件 t=Tt = T はどの有限の tt でも成り立ちません。M∞M_\infty では 2 の条件も成り立たないので、どの段も 4 に進みます。段の数についての帰納法で、すべての kk について kk 段後の状態は停止の状態でないことが分かります。

注意 4.2(停止条件はだれの性質か)

定理 4.1 の 3 は、「実物のモデルが止まらない」と言っているのではありません。「上限のないループの停止は、ハーネスのコードだけからは証明できない」と言っています。停止するかどうかは MM の性質で、MM は学習で得られた関数です。実物では、利用者がいつでも割り込めること(公式ドキュメントによれば、Esc キーで実行中のツール呼び出しを取り消して止められます)と、次章以降で扱う関門や文脈の上限が、別の経路で歯止めになっています。Anthropic のブログ記事 “Building effective agents” も、エージェントには制御を保つための停止条件(反復回数の上限など)を入れるのが一般的だと述べています。

例 4.3(「失敗しているテストを直す」の 3 ターン)

課題「失敗しているテストを直して」に対するループを、メッセージ列の形で書き下します。内容は説明のために作ったもので、実際のモデルの応答の再現ではありません。ツール名は Claude Code の組み込みツールの名前(Bash・Read・Edit)を借りています。

番号役割内容ブロック
1usertext「失敗しているテストを直して」
2assistanttext「まずテストを実行します」、tool_use(id u1、Bash、テストの実行)
3usertool_result(u1、失敗 1 件の出力)
4assistanttool_use(u2、Read、実装のファイル)、tool_use(u3、Read、テストのファイル)
5usertool_result(u2、ファイルの内容)、tool_result(u3、ファイルの内容)
6assistanttool_use(u4、Edit、実装の修正)、tool_use(u5、Bash、テストの再実行)
7usertool_result(u4、成功)、tool_result(u5、全件成功の出力)
8assistanttext「原因は境界の判定で、修正後は全件通りました」

ツール使用ターンは 3 回(2–3、4–5、6–7)、モデルの呼び出しは 4 回(2・4・6・8 を生成)です。(P2) は、2 と 3、4 と 5、6 と 7 の組で確かめられます。

T=2T = 2 と置くと、3 回目の呼び出しが返した 6 番のメッセージで t=2=Tt = 2 = T となり、ループはターン上限で止まります。6 番の Edit は実行されず、ファイルは変わりません。呼び出しは 3=T+13 = T + 1 回で、定理 4.1 の上界に一致します。公式ドキュメントも、同じ形の例で上限を 2 にすると編集の手前で止まると説明しています。

出典(確認日 2026-10-10): How the agent loop works、How Claude Code works、Building effective agents(2024-12-19 公開)。

5.1. 入力トークン数は 1 次で、合計は 2 次で増える

Section titled “5.1. 入力トークン数は 1 次で、合計は 2 次で増える”

ループは、過去のメッセージを消さずに末尾へ足していきます。公式ドキュメントも、コンテキストウィンドウはセッションの中でターンをまたいでリセットされず、システムプロンプト・ツール定義・会話の履歴・ツールの入力と出力がすべて積み上がる、と述べています。その帰結を数えます。

定義 5.1(トークン数の勘定)↗

各メッセージ mm にトークン数 τ(m)∈{1,2,…}\tau(m) \in \{1, 2, \ldots\} が定まり、メッセージ列のトークン数は各メッセージのトークン数の和であるとする。メッセージ列の外にある固定の部分(システムプロンプトとツール定義)のトークン数を s0≥0s_0 \ge 0 とする。kk 回目のモデルの呼び出しの入力トークン数 nkn_k を、s0s_0 とそのとき渡すメッセージ列のトークン数の和と定める。s=s0+τ(m1)s = s_0 + \tau(m_1)(m1m_1 は最初の user メッセージ)、ai=τ(i 回目の応答)a_i = \tau(i\text{ 回目の応答})、ri=τ(i 回目の応答に対する結果のメッセージ)r_i = \tau(i\text{ 回目の応答に対する結果のメッセージ}) と書く。

トークン数がメッセージごとの和になるという仮定は、区切りのトークンなどの細部を無視した近似です。モデルが文章をトークン列に分ける仕組みは トークン化 を参照してください。

命題 5.2(圧縮のないループの入力の成長)✓

定義 3.1 の模型(圧縮なし)と 定義 5.1 の勘定のもとで、次が成り立つ。

  1. kk 回目の呼び出しの入力トークン数は nk=s+∑i=1k−1(ai+ri)n_k = s + \sum_{i=1}^{k-1} (a_i + r_i) である。
  2. ある c>0c \gt 0 があってすべての ii で ai+ri≥ca_i + r_i \ge c ならば、KK 回の呼び出しの入力トークン数の合計は
∑k=1Knk ≥ Ks+c K(K−1)2\sum_{k=1}^{K} n_k \ \ge\ K s + \frac{c\,K(K-1)}{2}

を満たし、KK の関数として Ω(K2)\Omega(K^2) である。 3. 2 の仮定のもとで、各呼び出しの入力がコンテキストウィンドウの大きさ WW 以下でなければならないとすると、呼び出しの回数 KK は K≤W−sc+1K \le \dfrac{W - s}{c} + 1 を満たす。

証明(命題 5.2)

(1) kk についての帰納法で示します。k=1k = 1 のとき、渡すメッセージ列は (m1)(m_1) で、n1=s0+τ(m1)=sn_1 = s_0 + \tau(m_1) = s です。和は空なので等式が成り立ちます。kk 回目の呼び出しに渡すメッセージ列を hh とし、nk=s+∑i=1k−1(ai+ri)n_k = s + \sum_{i=1}^{k-1}(a_i + r_i) と仮定します。k+1k + 1 回目の呼び出しがあるのは、kk 回目の段が定義の 4 に進んだときで、渡すメッセージ列は h⋅a⋅bh \cdot a \cdot b です。τ(a)=ak\tau(a) = a_k、τ(b)=rk\tau(b) = r_k なので、

nk+1=nk+ak+rk=s+∑i=1k(ai+ri)n_{k+1} = n_k + a_k + r_k = s + \sum_{i=1}^{k} (a_i + r_i)

です。

(2) 1 と仮定から nk≥s+(k−1)cn_k \ge s + (k-1)c です。k=1,…,Kk = 1, \ldots, K について足すと

∑k=1Knk ≥ ∑k=1K(s+(k−1)c)=Ks+c∑j=0K−1j=Ks+c K(K−1)2\sum_{k=1}^{K} n_k \ \ge\ \sum_{k=1}^{K} \bigl(s + (k-1)c\bigr) = Ks + c \sum_{j=0}^{K-1} j = Ks + \frac{c\,K(K-1)}{2}

です。K≥2K \ge 2 のとき K−1≥K/2K - 1 \ge K/2 なので、右辺は c4K2\frac{c}{4} K^2 以上です。したがって 定義 3.1[計算量と O 記法] の意味で Ω(K2)\Omega(K^2) です。

(3) KK 回目の呼び出しについて nK≤Wn_K \le W が必要です。2 の最初の不等式から s+(K−1)c≤nK≤Ws + (K-1)c \le n_K \le W なので、K−1≤(W−s)/cK - 1 \le (W - s)/c です。

2 次で増えるのは合計であることに注意してください。1 回の入力は 1 次でしか増えませんが、同じ履歴を毎回送り直すので、合計は三角数になります。この「送り直し」の料金と遅延を下げる仕組みがプロンプトキャッシュで、ウィンドウの上限 WW そのものへの対処が圧縮とサブエージェントです。前者は 次々章、後者は その次の章 で扱います。

123456789105,00023,000呼び出しの番号 k入力トークン数 n_k
入力トークン数 n_k の棒グラフ(次の例と同じ仮の数値。s = 5,000 で、1 ターンごとに 2,000 ずつ増える)。棒の高さは k について 1 次で増え、棒の面積の合計が 2 次で増える。

例 5.3(仮の数値での成長)

s=5,000s = 5{,}000、すべての ii で ai+ri=2,000a_i + r_i = 2{,}000 と仮に置きます(実測値ではなく、式の形を見るための数です)。命題 5.2 の 1 から nk=5,000+2,000(k−1)n_k = 5{,}000 + 2{,}000(k - 1) で、n1=5,000n_1 = 5{,}000、n2=7,000n_2 = 7{,}000、n10=23,000n_{10} = 23{,}000 です。10 回の合計は 10×5,000+2,000×45=140,00010 \times 5{,}000 + 2{,}000 \times 45 = 140{,}000 で、最後の 1 回の入力の約 6 倍です。ウィンドウを仮に W=200,000W = 200{,}000 とすると、3 から K≤195,000/2,000+1=98.5K \le 195{,}000 / 2{,}000 + 1 = 98.5、つまり 98 回までです。ファイルを丸ごと読むツール結果が 1 回で数千トークンになることを考えると、長い作業で上限に達するのは不自然ではありません。

命題 5.4(応答のあとで比べる予算の超過幅)✓

定義 3.1 の模型に、支出の上限 B>0B \gt 0 を次の規則で加える。ii 回目のモデルの応答のコストを ci≥0c_i \ge 0、累計を Sk=∑i=1kciS_k = \sum_{i=1}^{k} c_i とし、kk 回目の応答を受け取ったあとで Sk≥BS_k \ge B ならば、ツールを実行せずに予算上限として停止する(サブエージェントは無いとする)。すべての ii で ci≤cmax⁡c_i \le c_{\max} とする。このとき次が成り立つ。

  1. ループが k∗k^\ast 回目の応答のあとに予算上限で停止したならば、B≤Sk∗<B+ck∗≤B+cmax⁡B \le S_{k^\ast} \lt B + c_{k^\ast} \le B + c_{\max} である。
  2. どの時点で停止しても(完了・ターン上限を含めて)、最終の累計は B+cmax⁡B + c_{\max} 未満である。
  3. さらに、ある cmin⁡>0c_{\min} \gt 0 があってすべての ii で ci≥cmin⁡c_i \ge c_{\min} ならば、モデルの呼び出しは高々 ⌈B/cmin⁡⌉\lceil B / c_{\min} \rceil 回である。

次の証明では、k∗k^\ast は予算上限で止まった回の番号、KK は最後に行われた呼び出しの番号です。3 つの主張はどれも、「kk 回目の呼び出しが行われるなら、その直前までの累計は BB 未満」という 1 つの事実から出ます。1 はその事実を予算上限で止まった回に使い、2 は止まった理由によらず最後の回に使い、3 は回数を数えるのに使います。

証明(命題 5.4)

kk 回目の呼び出しが行われるのは、k=1k = 1 であるか、k−1k - 1 回目の応答のあとで停止しなかったときです。後者では、予算の判定を通過しているので Sk−1<BS_{k-1} \lt B です。S0=0<BS_0 = 0 \lt B と合わせて、kk 回目の呼び出しが行われるならば Sk−1<BS_{k-1} \lt B です。

(1) 予算上限で停止したので Sk∗≥BS_{k^\ast} \ge B です。k∗k^\ast 回目の呼び出しが行われたので Sk∗−1<BS_{k^\ast - 1} \lt B で、Sk∗=Sk∗−1+ck∗<B+ck∗≤B+cmax⁡S_{k^\ast} = S_{k^\ast - 1} + c_{k^\ast} \lt B + c_{k^\ast} \le B + c_{\max} です。

(2) 最後の呼び出しを KK 回目とします。SK−1<BS_{K-1} \lt B なので、停止の理由が何であっても SK=SK−1+cK<B+cmax⁡S_K = S_{K-1} + c_K \lt B + c_{\max} です。

(3) KK 回目の呼び出しが行われるならば SK−1<BS_{K-1} \lt B で、SK−1≥(K−1)cmin⁡S_{K-1} \ge (K-1)c_{\min} なので K−1<B/cmin⁡K - 1 \lt B / c_{\min} です。K−1K - 1 は整数なので K−1≤⌈B/cmin⁡⌉−1K - 1 \le \lceil B / c_{\min} \rceil - 1、すなわち K≤⌈B/cmin⁡⌉K \le \lceil B / c_{\min} \rceil です。

上限を超えうる理由は、判定の時点にあります。応答のコストは、API が応答と一緒に返すトークン使用量から決まるので、応答が届く前には比べられません。公式ドキュメントは、上限に達した応答は完了して合計に入るので、支出は「その 1 応答のコスト」と、その時点で動いているサブエージェントが止まるまでに使う分だけ上限を超えうる、と述べ、上限には余裕を持たせるよう勧めています。命題 5.4 の 1 は、その前半を模型の上で導いたものです。3 は、ターンの上限が無くても、1 回の応答に正の最小コストがあれば予算の上限が停止を保証することを言っています。

出典(確認日 2026-10-10): How the agent loop works(“Turns and budget”・“Budget headroom”・“The context window” の節)。

1 つの応答に複数のツール呼び出しがあるとき、公式ドキュメントによれば、読み取り専用のツール(Read・Glob・Grep、読み取り専用と印の付いた MCP ツール)は並行に実行でき、状態を変えるツール(Edit・Write・Bash)は衝突を避けるために順に実行されます。なぜこの区別で足りるのかを、模型で確かめます。

定義 6.1(読み取り専用の呼び出し)↗

ツール呼び出し (n,x)(n, x) が読み取り専用であるとは、すべての e∈Ee \in E について execn(x,e)\mathrm{exec}_n(x, e) の第 2 成分が ee であることをいう。このとき execn(x,e)=(ρn,x(e),e)\mathrm{exec}_n(x, e) = (\rho_{n,x}(e), e) と書ける。

読み取り専用の呼び出しの結果は環境の状態だけで決まり、呼び出しは環境に跡を残しません。これは、式の値がその式の部分式の値だけで決まるという参照透過性(定義 6.3[プログラミング言語論])の、環境つきの版です。

命題 6.2(読み取り専用の呼び出しは順序に依らない)✓

(n1,x1),…,(nq,xq)(n_1, x_1), \ldots, (n_q, x_q) を読み取り専用の呼び出しとし、π\pi を {1,…,q}\{1, \ldots, q\} の置換とする。環境 ee から、π(1),π(2),…,π(q)\pi(1), \pi(2), \ldots, \pi(q) の順に 1 つずつ実行すると、jj 番目の呼び出しの結果は π\pi に依らず ρnj,xj(e)\rho_{n_j, x_j}(e) で、最終の環境は ee である。

証明(命題 6.2)

ii 個の呼び出しを実行したあとの環境を e(i)e^{(i)} とします。e(i)=ee^{(i)} = e を ii についての帰納法で示します。e(0)=ee^{(0)} = e です。e(i−1)=ee^{(i-1)} = e とすると、ii 番目に実行する呼び出し (nπ(i),xπ(i))(n_{\pi(i)}, x_{\pi(i)}) は読み取り専用なので、execnπ(i)(xπ(i),e)=(ρnπ(i),xπ(i)(e),e)\mathrm{exec}_{n_{\pi(i)}}(x_{\pi(i)}, e) = (\rho_{n_{\pi(i)}, x_{\pi(i)}}(e), e) で、e(i)=ee^{(i)} = e です。同じ式から、この呼び出しの結果は ρnπ(i),xπ(i)(e)\rho_{n_{\pi(i)}, x_{\pi(i)}}(e) です。j=π(i)j = \pi(i) と書けば、jj 番目の呼び出しの結果は ρnj,xj(e)\rho_{n_j, x_j}(e) で、π\pi に依りません。

並行な実行を「個々の呼び出しが不可分に、ある順序で起こる」ものとみなせるなら、どの順序でも結果の組と環境が同じなので、並行に実行しても逐次に実行した場合と区別がつきません。ハーネスは、結果を u1,…,uqu_1, \ldots, u_q の順に並べ直して (P2) を保つだけで済みます。

例 6.3(書き込みを含むと順序で結果が変わる)

環境をファイル a.txt の内容とし、いまの内容を「x = 1」とします。次の 2 つの呼び出しを考えます。どちらも「置換前の文字列がファイルに無ければ、何も変えずに失敗を返す」とします。

  • 編集 A: 文字列 x = 1 を x = 2 に置き換える。
  • 編集 B: 文字列 x = 1 を x = 3 に置き換える。

A、B の順に実行すると、A は成功して内容は「x = 2」になり、B は置換前の文字列が無いので失敗します。B、A の順では、B が成功して内容は「x = 3」になり、A が失敗します。結果の組(成功・失敗の並び)も最終の環境も、順序で変わります。命題 6.2 の仮定(読み取り専用)を落とすと結論が壊れる例です。状態を変えるツールを、モデルが応答に書いた順に 1 つずつ実行するのは、実行の結果を「応答の順」という 1 つの順序に固定するためです。

出典(確認日 2026-10-10): How the agent loop works(“Parallel tool execution” の節)。

7. ワークフローとエージェント

Section titled “7. ワークフローとエージェント”

Anthropic のブログ記事 “Building effective agents”(2024-12-19 公開)は、LLM を使う系を 2 つに分けています。ワークフローは、LLM とツールが、あらかじめ決めたコードの経路で編成される系です。エージェントは、LLM が自分の手順とツールの使い方を動的に決める系です。同じ記事は、ワークフローの型として、プロンプトの連鎖(prompt chaining)、振り分け(routing)、並列化(parallelization)、指揮者と作業者(orchestrator-workers)、評価者と最適化者(evaluator-optimizer)を挙げ、エージェントを、環境からのフィードバックに基づいてループの中でツールを使う LLM と説明しています。この区別を、呼び出し回数の言葉で定式化します。次の定義の節点は、流れ図の中の「モデルを呼ぶ箱」のことです。ワークフローは、その箱を戻らずにつないだ経路で、たとえば「要約してから翻訳する」なら節点が 2 つで、それぞれを 1 回ずつ通ります。エージェントは、同じ箱の輪を何度でも回ります。

定義 7.1(ワークフローとエージェント(呼び出し回数による模型))↗

ワークフローを、有限個のモデル呼び出しの箇所(節点)VV を持ち、どの入力とどのモデルに対しても、1 回の実行で各節点を高々 1 回しか通らないプログラムと定める。どの節点を通るかは、入力やモデルの出力に依ってよい。エージェントを、定義 3.1 のループと定める。

この定義では、連鎖・振り分け・並列化は、節点が有限個で後戻りのない経路なのでワークフローです。評価者と最適化者のように繰り返しを含む型は、繰り返しの回数をコードで固定した場合(繰り返しを展開して有限個の節点にできる場合)に限りワークフローです。これは、ブログ記事の「あらかじめ決めた経路」を数えられる形にした、この記事の模型です。

命題 7.2(呼び出し回数:ワークフローは定数で、エージェントは上限なしには有界でない)✓

  1. 節点の集合が VV のワークフローの、1 回の実行でのモデルの呼び出し回数は、入力にもモデルにも依らず ∣V∣|V| 以下である。
  2. 上限のない(T=∞T = \infty)エージェントでは、任意の K≥1K \ge 1 に対して、モデルの呼び出しがちょうど KK 回になるモデルが存在する。したがって、呼び出し回数を入力にもモデルにも依らずに抑える定数は存在しない。
  3. 上限 TT が有限のエージェントの呼び出し回数は T+1T + 1 以下で、モデルをわたる上限はちょうど T+1T + 1 である。
証明(命題 7.2)

(1) 定義から、1 回の実行は各節点を高々 1 回しか通らず、モデルの呼び出しは節点を通るときにだけ起こります。したがって呼び出し回数は ∣V∣|V| 以下です。

(2) MKM_K を、渡されたメッセージ列の中の assistant メッセージの数が K−1K - 1 未満なら tool_use ブロックを 1 つ含むメッセージを返し、K−1K - 1 以上なら text ブロックだけのメッセージを返すモデルとします。jj 回目の呼び出しに渡されるメッセージ列は、assistant メッセージをちょうど j−1j - 1 個含みます(各段の 4 が assistant メッセージを 1 つずつ足すので)。したがって j≤K−1j \le K - 1 の呼び出しは tool_use を返して 4 に進み(T=∞T = \infty なので 3 では止まりません)、KK 回目の呼び出しは text だけを返して 2 で完了します。呼び出しはちょうど KK 回です。KK は任意なので、どんな定数 CC に対しても MC+1M_{C+1} が CC を超える呼び出しをします。

(3) 定理 4.1 の 1 と 2 です。

2 は、エージェントが悪いと言っているのではありません。回数が入力とモデルの出力で決まることは、やってみるまで手順の数が分からない課題(どのファイルに原因があるか、テストが何回目で通るか)に対応できる、という利点の裏面です。その代わり、コストと時間の上界は、ループの外から上限として与えるしかありません。ワークフローは、上界がコードの形から読める代わりに、あらかじめ決めた経路に収まる課題にしか使えません。

出典(確認日 2026-10-10): Building effective agents。

8. 模型に入れなかった公式の説明

Section titled “8. 模型に入れなかった公式の説明”

ここまでの命題は模型の上のものです。公式ドキュメントには、模型に入れなかった、しかしループを使ううえで知っておくべき説明があります。定理にはせず、事実として整理します(2026-10 時点の公式ドキュメントによる)。

3 つの段階。 公式ドキュメントは、課題を受けたモデルが「文脈を集める」「行為する」「結果を確かめる」の 3 つの段階を進むと説明し、段階は混ざり合い、全体を通してツールを使うと述べています。これはループの中でモデルが何をしているかの説明で、ハーネスが段階を切り替えているわけではありません。模型の上では、どの段階も「tool_use を含む応答 → 実行 → 結果」という同じ 1 ターンです。「確かめる」が入っている点は、生成と検証を組にするという考え方(定義 4.1[AI時代のITエンジニアの生存戦略])と対応します。

割り込み。 利用者はいつでもループに割り込めます。Esc キーは実行中のツール呼び出しを取り消して止め、文章を入力して Enter を押すと、止めずに訂正を待ち行列に入れられます。待ち行列のメッセージは、実行中のツール呼び出しが終わりしだい、同じターンの中でモデルに読まれます。模型では、4 の c1,…,crc_1, \ldots, c_r(結果のあとに置く text ブロック)がこれに当たります。

セッションの保存・再開・分岐。 会話はローカルに保存されます(各メッセージ・ツール使用・結果が ~/.claude/projects/ 以下の JSONL ファイルに書かれます)。セッションは互いに独立で、新しいセッションは新しいコンテキストウィンドウで始まります。再開(--continue・--resume)は同じセッション ID に追記し、分岐(--fork-session・/branch)は履歴を新しい ID に複製して元を変えません。模型の言葉では、再開は保存した hh から段を続けること、分岐は hh の複製から別の列を伸ばすことです。

checkpoint が戻せるもの。 ファイルを編集する前に、その内容のスナップショットが取られ、あとで巻き戻せます。ただし対象はファイルの変更だけで、データベース・API・デプロイのような外部への作用は戻せない、と明記されています。模型の言葉では、環境 EE のうち「ローカルのファイル」の成分だけが復元でき、ほかの成分への exec\mathrm{exec} の作用は取り消せません。取り消せない作用を持つツールほど、実行の前の関門(次章)が重要になります。

メッセージの型と結果の種別。 Agent SDK は、ループの進行をメッセージの列として返します。中心になる型は、セッションの出来事を表す SystemMessage(開始時の init、圧縮のあとの compact_boundary など)、モデルの応答を表す AssistantMessage、ツール結果を表す UserMessage、部分的な出力を表す StreamEvent、ループの終わりを表す ResultMessage の 5 つです。ResultMessage の種別(subtype)は次のとおりで、模型の 3 通りの停止に、実行中のエラーなどが加わります。

種別意味模型での対応
success課題を通常どおり終えた2 の完了
error_max_turnsターンの上限に達した3 のターン上限
error_max_budget_usd支出の上限に達した命題 5.4 の予算上限
error_during_executionエラーがループを中断した模型に無い(注意 3.2)
error_max_structured_output_retries構造化出力の再試行の上限に達した模型に無い

出典(確認日 2026-10-10): How Claude Code works、How the agent loop works(“Message types”・“Handle the result” の節)。

9.1. 模型のシミュレーション(実行済み)

Section titled “9.1. 模型のシミュレーション(実行済み)”

定義 3.1 をそのまま Python で書き、モデルを「台本どおりに応答する関数」で置き換えて実行します。API は呼びません。これは仕組みを確かめるための模型で、Claude Code の実装の再現ではありません。

# Python 3.9(標準ライブラリのみ)。エージェントループの模型。API は呼ばない。
def use(u, name, x): return {"type": "tool_use", "id": u, "name": name, "input": x}
def text(s): return {"type": "text", "text": s}
def run_loop(model, tools, gate, prompt, env, T=None, B=None, cost=lambda a: 0):
h = [{"role": "user", "content": [text(prompt)]}]
turns = calls = 0
spent = 0
while True:
a = model(h); calls += 1; spent += cost(a)
uses = [b for b in a["content"] if b["type"] == "tool_use"]
if not uses:
return "success", h + [a], env, calls, turns, spent
if B is not None and spent >= B:
return "error_max_budget", h, env, calls, turns, spent
if T is not None and turns == T:
return "error_max_turns", h, env, calls, turns, spent
results = []
for b in uses: # 応答に現れた順に実行する
if gate(b["name"], b["input"]):
y, err, env = tools[b["name"]](b["input"], env)
else:
y, err = "denied by permission rule", True
results.append({"type": "tool_result", "tool_use_id": b["id"],
"content": y, "is_error": err})
h = h + [a, {"role": "user", "content": results}]
turns += 1
def invariant(h):
for i, m in enumerate(h):
ids = [b["id"] for b in m["content"] if b["type"] == "tool_use"]
if m["role"] == "assistant" and ids:
if i + 1 >= len(h) or h[i + 1]["role"] != "user": return False
nxt = h[i + 1]["content"]
got = [b["tool_use_id"] for b in nxt if b["type"] == "tool_result"]
head = [b["type"] for b in nxt[:len(ids)]]
if got != ids or head != ["tool_result"] * len(ids): return False
return h[0]["role"] == "user"
def read(x, env): return env.get(x["path"], "(no such file)"), x["path"] not in env, env
def write(x, env): return "ok", False, {**env, x["path"]: x["text"]}
TOOLS = {"read": read, "write": write}
def scripted(h): # 3 回ツールを使って終わるモデル
k = sum(1 for m in h if m["role"] == "assistant")
plan = [[use("u1", "read", {"path": "a.py"}), use("u2", "read", {"path": "t.py"})],
[use("u3", "write", {"path": "a.py", "text": "fixed"})],
[use("u4", "write", {"path": "/etc/x", "text": "!"})]]
return {"role": "assistant", "content": plan[k] if k < 3 else [text("done")]}
def forever(h): # 常にツールを呼ぶモデル
k = sum(1 for m in h if m["role"] == "assistant")
return {"role": "assistant", "content": [use("v%d" % k, "read", {"path": "a.py"})]}
gate = lambda name, x: not x["path"].startswith("/etc/")
env0 = {"a.py": "bug", "t.py": "test"}
for label, model, T in [("scripted, T=None", scripted, None), ("scripted, T=2", scripted, 2),
("forever, T=2", forever, 2), ("forever, T=5", forever, 5)]:
status, h, env, calls, turns, _ = run_loop(model, TOOLS, gate, "fix", dict(env0), T=T)
print("%-17s %-15s calls=%d turns=%d len(h)=%d inv=%s a.py=%s"
% (label, status, calls, turns, len(h), invariant(h), env["a.py"]))
status, h, env, calls, turns, _ = run_loop(scripted, TOOLS, gate, "fix", dict(env0))
print("denied result:", h[6]["content"][0]["content"], "| /etc/x in env:", "/etc/x" in env)
status, h, env, calls, turns, spent = run_loop(forever, TOOLS, gate, "fix", dict(env0),
B=10, cost=lambda a: 3)
print("budget B=10, cost 3/call:", status, "calls=%d spent=%d" % (calls, spent))
s, c = 5000, 2000
n = [s + c * (k - 1) for k in range(1, 11)]
print("n_k:", n[0], n[1], n[2], "...", n[9], "| sum_{k<=10} =", sum(n),
"| max k for W=200000:", (200000 - s) // c + 1)

実行結果(Python 3.9.6、2026-10-10):

scripted, T=None success calls=4 turns=3 len(h)=8 inv=True a.py=fixed
scripted, T=2 error_max_turns calls=3 turns=2 len(h)=5 inv=True a.py=fixed
forever, T=2 error_max_turns calls=3 turns=2 len(h)=5 inv=True a.py=bug
forever, T=5 error_max_turns calls=6 turns=5 len(h)=11 inv=True a.py=bug
denied result: denied by permission rule | /etc/x in env: False
budget B=10, cost 3/call: error_max_budget calls=4 spent=12
n_k: 5000 7000 9000 ... 23000 | sum_{k<=10} = 140000 | max k for W=200000: 98

読み方を確かめます。常にツールを呼ぶモデル forever は、T=2T = 2 で 3 回、T=5T = 5 で 6 回の呼び出しで止まり、定理 4.1 の T+1T + 1 に一致します。どの実行でも inv=True で、命題 3.4 のとおりです。3 ターン目の /etc/x への書き込みは関門に拒否され、結果は失敗として返り、環境は変わっていません(命題 3.5)。予算 B=10B = 10、1 応答のコスト 33 では、4 回目の応答のあとで累計が 1212 になって止まり、10≤12<10+310 \le 12 \lt 10 + 3 です(命題 5.4)。最後の行は 例 5.3 の数値です。

9.2. Messages API で書く最小のループ(未実行)

Section titled “9.2. Messages API で書く最小のループ(未実行)”

同じループを、Claude API の Python SDK(anthropic パッケージ)で書くと次の形になります。このコードは執筆時に実行していません(API を呼んでいないので、出力は載せません)。ツール使用の書式は 2 節の出典のページの例に従っています。API キーは環境変数 ANTHROPIC_API_KEY から SDK が読みます。モデル名は環境変数で与え、既定値は 2026-10 時点の公式ドキュメントの例に現れる値です(執筆時点の例で、変わりえます)。

# 未実行。pip install anthropic が必要。API キーは環境変数 ANTHROPIC_API_KEY から読まれる。
import os
import anthropic
client = anthropic.Anthropic()
MODEL = os.environ.get("ANTHROPIC_MODEL", "claude-opus-5-5") # 執筆時点の公式の例の値
MAX_TURNS = 5 # ターンの上限 T を必ず置く
TOOLS = [{
"name": "list_files",
"description": "List the file names in a directory. Read-only.",
"input_schema": {"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"]},
}]
def list_files(path): # 副作用のないツール
try:
return "\n".join(sorted(os.listdir(path))), False
except OSError as error:
return str(error), True # 失敗は is_error で返す
messages = [{"role": "user", "content": "このディレクトリにある Python ファイルを数えて。"}]
turns = 0
while True:
response = client.messages.create(model=MODEL, max_tokens=1024,
tools=TOOLS, messages=messages)
if response.stop_reason != "tool_use": # ツール呼び出しが無ければ完了
print(response.stop_reason, [b.text for b in response.content if b.type == "text"])
break
if turns == MAX_TURNS: # 実行せずに止まる
print("turn limit reached")
break
results = []
for block in response.content:
if block.type == "tool_use": # 各 tool_use に結果をちょうど 1 つ
content, is_error = list_files(block.input.get("path", "."))
results.append({"type": "tool_result", "tool_use_id": block.id,
"content": content, "is_error": is_error})
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": results}) # 結果だけを先頭に並べる
turns += 1

while の本体は 定義 3.1 の 1 段と 1 対 1 に対応します。messages には足すだけで、過去の要素を書き換えません。

Agent SDK の query() は、上のループ(と、権限・文脈の管理)を内側で実行し、進行をメッセージの列として返します。このコードも執筆時に実行していません。 形は公式ドキュメントの例に従っています。

# 未実行。pip install claude-agent-sdk が必要。認証は SDK の手順に従う(キーの値はコードに書かない)。
import asyncio
from claude_agent_sdk import (query, ClaudeAgentOptions, AssistantMessage,
ResultMessage, TextBlock, ToolUseBlock)
async def main():
options = ClaudeAgentOptions(allowed_tools=["Read", "Glob", "Grep"], # 読み取りだけを自動承認
max_turns=5) # ターンの上限
try:
async for message in query(prompt="このディレクトリにある Python ファイルを数えて。",
options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ToolUseBlock):
print("tool call:", block.name)
elif isinstance(block, TextBlock):
print("text:", block.text)
if isinstance(message, ResultMessage):
print("result subtype:", message.subtype, "turns:", message.num_turns)
except Exception as error: # 公式ドキュメントによれば、上限などで終わると結果のあとに例外が出る
print("ended with an error:", error)
asyncio.run(main())

モデルの応答は実行のたびに変わるので、どちらのコードも、呼ばれるツールの順序や回数は実行ごとに違いえます。変わらないのは、9.1 で確かめた性質(書式の不変条件、上限による停止)のほうです。

出典(確認日 2026-10-10): Tool use with Claude、How the agent loop works(“Handle messages”・“Put it all together” の節の例)。

演習 10.1易

定義 3.1 の模型で T=0T = 0 と置く。モデルの呼び出しは何回行われるか。ツールは実行されるか。最終のメッセージ列はどうなるか。

解答

定理 4.1 の 1 から、呼び出しは高々 0+1=10 + 1 = 1 回で、ループは最初の段で必ず 1 回呼ぶので、ちょうど 1 回です。その応答 aa に tool_use が無ければ完了で、最終のメッセージ列は (m1,a)(m_1, a) です。tool_use があれば t=0=Tt = 0 = T なのでターン上限で止まり、ツールは 1 つも実行されず、メッセージ列は (m1)(m_1) のままです。T=0T = 0 は「ツールを使わせない 1 回の問い合わせ」に当たります。

演習 10.2標準

ハーネスの実装を誤って、1 段の 4 で作る user メッセージを b′=(user,(c1,res(u1,y1,ϵ1),…,res(uq,yq,ϵq)))b' = (\mathtt{user}, (c_1, \mathsf{res}(u_1, y_1, \epsilon_1), \ldots, \mathsf{res}(u_q, y_q, \epsilon_q)))(text ブロック c1c_1 を先頭に置く)としたとする。h⋅a⋅b′h \cdot a \cdot b' が Inv\mathrm{Inv} のどの条件に反するかを示せ。命題 3.4 の証明のどこが通らなくなるか。

解答

U(a)=(u1,…,uq)U(a) = (u_1, \ldots, u_q)、q≥1q \ge 1 です。(P2) は「b′b' の最初の qq 個のブロックが、この順に id が u1,…,uqu_1, \ldots, u_q の tool_result である」ことを求めますが、b′b' の 1 個目のブロックは text ブロック c1c_1 なので成り立ちません。証明では、保存の (P2) の「定義から bb の最初の qq 個のブロックは res(u1,…),…,res(uq,…)\mathsf{res}(u_1, \ldots), \ldots, \mathsf{res}(u_q, \ldots) です」という 1 文が偽になります。2 節で見たとおり、API はこの形の要求を 400 エラーで拒否します。不変条件は、API の規則をハーネスの側の言葉に言い換えたものです。

演習 10.3標準

s=8,000s = 8{,}000 とし、各ターンで ai+ri=1,500a_i + r_i = 1{,}500 だが、3 ターン目だけ大きなファイルを読んで a3+r3=40,000a_3 + r_3 = 40{,}000 だったとする。(1) n3n_3、n4n_4、n10n_{10} を求めよ。(2) 10 回の呼び出しの入力の合計を求め、3 ターン目も 1,5001{,}500 だった場合の合計と比べよ。

解答

(1) 命題 5.2 の 1 から、n3=8,000+2×1,500=11,000n_3 = 8{,}000 + 2 \times 1{,}500 = 11{,}000、n4=8,000+1,500+1,500+40,000=51,000n_4 = 8{,}000 + 1{,}500 + 1{,}500 + 40{,}000 = 51{,}000、n10=8,000+8×1,500+40,000=60,000n_{10} = 8{,}000 + 8 \times 1{,}500 + 40{,}000 = 60{,}000 です。

(2) 3 ターン目も 1,5001{,}500 の場合、nk=8,000+1,500(k−1)n_k = 8{,}000 + 1{,}500(k-1) で、合計は 10×8,000+1,500×45=147,50010 \times 8{,}000 + 1{,}500 \times 45 = 147{,}500 です。実際には、k≥4k \ge 4 の 7 回の呼び出しのそれぞれで入力が 40,000−1,500=38,50040{,}000 - 1{,}500 = 38{,}500 多いので、合計は 147,500+7×38,500=417,000147{,}500 + 7 \times 38{,}500 = 417{,}000 です。1 回の大きな読み取りの 38,50038{,}500 トークンは、そのあとの呼び出しの回数だけ繰り返し数えられます。早い時点で大きな結果を文脈に入れるほど、合計への寄与が大きくなります。

演習 10.4難

命題 5.4 の 1 の上界 Sk∗<B+cmax⁡S_{k^\ast} \lt B + c_{\max} が改良できないことを示せ。すなわち、任意の B>0B \gt 0、cmax⁡>0c_{\max} \gt 0、0<δ<min⁡(B,cmax⁡)0 \lt \delta \lt \min(B, c_{\max}) に対して、すべての ci≤cmax⁡c_i \le c_{\max} で、予算上限で停止したときの累計が B+cmax⁡−δB + c_{\max} - \delta になるコストの列を作れ。

解答

c1=B−δc_1 = B - \delta、c2=cmax⁡c_2 = c_{\max} と置きます。0<δ<B0 \lt \delta \lt B なので c1>0c_1 \gt 0 です。c1≤cmax⁡c_1 \le c_{\max} が必要なので、B−δ>cmax⁡B - \delta \gt c_{\max} の場合は c1c_1 を、和が B−δB - \delta になる cmax⁡c_{\max} 以下の正の数いくつかに分けます(B−δ=ℓcmax⁡+θB - \delta = \ell c_{\max} + \theta、0≤θ<cmax⁡0 \le \theta \lt c_{\max} と書き、cmax⁡c_{\max} を ℓ\ell 個と、θ>0\theta \gt 0 なら θ\theta を 1 個並べます)。この前半の応答はすべてツールを呼ぶとし、前半の途中と終わりの累計は高々 B−δ<BB - \delta \lt B なので、予算の判定を通過します。次の応答のコストを cmax⁡c_{\max} とし、ツールを呼ぶとすると、累計は B−δ+cmax⁡≥BB - \delta + c_{\max} \ge B(δ<cmax⁡\delta \lt c_{\max} より)となって予算上限で停止します。そのときの累計は B+cmax⁡−δB + c_{\max} - \delta です。δ\delta はいくらでも小さく取れるので、上界 B+cmax⁡B + c_{\max} にいくらでも近づけます。

  • Anthropic, How Claude Code works, Claude Code ドキュメント — agentic loop、ハーネス、ツールの分類、セッション、checkpoint(確認日 2026-10-10)。
  • Anthropic, How the agent loop works, Claude Agent SDK ドキュメント — ループの手順、ターンと予算の上限、メッセージの型、並行実行(確認日 2026-10-10)。
  • Anthropic, Tool use with Claude および Handle tool calls, Claude API ドキュメント — ツールの定義、tool_use と tool_result の書式(確認日 2026-10-10)。
  • Anthropic, Stop reasons and fallback, Claude API ドキュメント — stop_reason の値(確認日 2026-10-10)。
  • Erik S., Barry Zhang, Building effective agents, Anthropic Engineering, 2024-12-19 — ワークフローとエージェントの区別。
  • C. A. R. Hoare, “An Axiomatic Basis for Computer Programming”, Communications of the ACM 12 (1969), 576–580. doi:10.1145/363235.363259 — 不変条件と停止性の証明の型。

このテーマの 100 本ノック(40 問)

執筆: Claude Code(Opus 5.5)、Claude Code(Sonnet 5.5)・監査 2 回(Claude Code)・最終 2026-10-12

この記事の誤りを報告する ・運営: 夢現技研合同会社 ・料金 ・利用条件 ・特定商取引法に基づく表記

© 2026 夢現技研合同会社 ・コード例は MIT ライセンスです。