メインコンテンツにスキップ
OpenAI

2026年1月23日

エンジニアリング

Codex エージェントループの展開

Michael Bolin(Technical Staff メンバー)

読み込んでいます...

Codex CLI(新しいウィンドウで開く) は、クロスプラットフォームのローカルソフトウェアエージェントであり、お使いのマシン上で安全かつ効率的に動作しながら、高品質で信頼性の高いソフトウェア変更を生成するように設計されています。私たちは、4月にCLIを初めてリリースして以来、世界クラスのソフトウェアエージェントを構築する方法について多くのことを学びました。本稿は、そのような洞察を解き明かすべく、Codex の仕組みや、その学びから得た貴重な教訓を探る継続的なシリーズの第1回となります。(Codex CLI の構築方法についてさらに詳しく知りたい方は、オープンソースのリポジトリを https://github.com/openai/codex(新しいウィンドウで開く) でご覧ください。設計上の判断に関する詳細の多くは、GitHub の Issue やプルリクエストに記録されておりますので、さらに詳しく知りたい場合は参照してください。)

まずは、エージェントループに焦点を当てます。これは Codex CLI の中核ロジックであり、ユーザー、モデル、およびモデルが有意義なソフトウェア作業を実行するために呼び出すツールの間のやり取りを調整する役割を担っています。この投稿が、LLM を活用する際に当社のエージェント(または「ハーネス」)が果たす役割について理解を深める一助となれば幸いです。

本題に入る前に、用語について簡単に説明します。OpenAI では、「Codex」は Codex CLI、Codex Cloud、Codex VS Code 拡張機能を含む一連のソフトウェアエージェント製品を指します。この投稿では Codex ハーネス に焦点を当てています。これは、すべての Codex 体験の基盤となる中核のエージェントループと実行ロジックを提供し、Codex CLI を通じて表示されます。ここでは便宜上、「Codex」と「Codex CLI」を同じ意味で使用します。

エージェントループ

すべてのAIエージェントの中心には、「エージェントループ」と呼ばれるものがあります。エージェントループの簡略化された図は以下の通りです。

「Agent loop」というタイトルの図は、AIシステムがユーザーのリクエストを処理し、ツールを呼び出し、結果を観察し、計画を更新し、出力を返すプロセスを示しています。矢印は、ユーザー入力、モデルの推論、ツールの操作、最終的な応答などのステップを繋いでいます。

まず、エージェントはユーザーから入力(インプット)を受け取り、それをプロンプトと呼ばれるモデル向けに用意するテキスト指示のセットに含めます。

次のステップは、指示を送信して応答を生成するようにモデルに問い合わせることです。このプロセスは 推論(インファレンス) と呼ばれます。推論中、テキストプロンプトはまず入力トークン(新しいウィンドウで開く)のシーケンスに変換されます。これらはモデルの語彙を指す整数です。これらのトークンはモデルのサンプリングに使用され、その結果、新しい出力トークンのシーケンスが生成されます。

出力トークンはテキストに変換され、モデルの応答となります。トークンは増分的に生成されるため、この変換はモデルの実行中に行うことができます。そのため、多くのLLMベースのアプリケーションではストリーミング出力が表示されます。実際には、推論は通常、テキストを処理するAPIの背後にカプセル化されており、トークン化の詳細は抽象化されています。

推論ステップの結果として、モデルは(1)ユーザーの元の入力に対する最終的な応答を生成するか、または(2)エージェントが実行することが期待されるツール呼び出しを要求します(例:「 lsを実行して出力を報告する」)。(2) の場合、エージェントはツール呼び出しを実行し、その出力を元のプロンプトに追加します。この出力は、モデルに再クエリするための新しい入力を生成する際に使用されます。その後エージェントは、この新しい情報を考慮して、再度試行することができます。

このプロセスは、モデルがツール呼び出しを停止し、代わりにユーザー向けのメッセージ(OpenAIモデルではアシスタントメッセージと呼ばれます)を生成するまで繰り返されます。多くの場合、このメッセージはユーザーの元の要求に直接答えますが、ユーザーに対するフォローアップの質問になることもあります。

エージェントはローカル環境を変更するツール呼び出しを実行できるため、その「出力」はアシスタントメッセージに限定されません。多くの場合、ソフトウェアエージェントの主な出力は、皆さんのマシン上で記述または編集されるコードです。それにもかかわらず、各ターンは常にアシスタントメッセージ(例:「要求された architecture.md を追加しました」)で終わり、エージェントループの終了状態が示されます。エージェントの視点から見ると、作業は完了しており、制御はユーザーに戻ります。

図に示されているユーザー入力からエージェント応答までの一連の流れは、会話の1回のターン(Codexではスレッド)と呼ばれます。ただし、この会話ターンには、モデル推論ツール呼び出しの間で複数回の反復が含まれることがあります。既存の会話に新しいメッセージを送信するたびに、過去のメッセージやツール呼び出しを含む会話履歴が、新しいターンのプロンプトの一部として含まれます。

「Multi-turn agent loop」というタイトルの図は、AI エージェントがユーザー入力を反復的に受け取り、アクションを生成し、ツールを参照し、状態を更新し、結果を返す方法を示しています。エージェントの推論サイクルを示すラベル付きの手順、矢印、およびツール出力の例が含まれています。

これは、会話が進むにつれて、モデルをサンプリングするために使用されるプロンプトの長さも増加することを意味します。この長さが重要なのは、すべてのモデルにコンテキストウィンドウがあり、それが1回の推論呼び出しに使用できるトークンの最大数になるからです。このウィンドウには入力トークン出力トークンの両方が含まれていることに注意してください。ご想像のとおり、エージェントは1回のターンで数百回のツール呼び出しを行うことを決定し、結果としてコンテキストウィンドウを使い果たす可能性があります。このため、コンテキストウィンドウ管理はエージェントの多くの責務の一つです。それでは、Codex がエージェントループをどのように実行するのか見てみましょう。

モデル推論

Codex CLI はモデル推論を実行するために、 Responses API(新しいウィンドウで開く) に対して HTTP リクエストを送信します。Responses API を使用してエージェントループを駆動する Codex を通じて、情報がどのように流れるかを調べます。

Codex CLI が使用する Responses API エンドポイントは設定可能(新しいウィンドウで開く)であり、Responses API を実装している(新しいウィンドウで開く)任意のエンドポイントで使用できます。

Codex が会話の最初の推論呼び出しのためにどのようにプロンプトを作成するかを探ってみましょう。

最初のプロンプトの作成

あなたがエンドユーザーとして Responses API をクエリする際に、モデルをサンプリングするためのプロンプトを逐語的に指定することはありません。その代わりに、クエリの一部としてさまざまな入力タイプを指定すると、Responses API サーバーは、この情報をモデルが使用するように設計されたプロンプトにどう構造化するかを決定します。このプロンプトは「項目のリスト」と考えることができます。このセクションでは、クエリがどのようにしてそのリストに変換されるかを説明します。

最初のプロンプトでは、リスト内の各項目がロール(役割)に関連付けられています。ロール は関連するコンテンツの重要度を示し、優先度の高い順に systemdeveloperuserassistant のいずれかの値になります。

Responses API(新しいウィンドウで開く) は、多くのパラメーターを含む JSON ペイロードを受け取ります。ここでは次のの3つに焦点を当てます。

Codex では、instructions フィールドは、指定されている場合は ~/.codex/config.toml 内の model_instructions_file(新しいウィンドウで開く) から読み込まれます。指定がない場合は、モデルに関連付けられた base_instructions(新しいウィンドウで開く) が使用されます。モデル固有の指示は Codex リポジトリに格納されており、CLI に組み込まれています(例:gpt-5.2-codex_prompt.md(新しいウィンドウで開く))。

tools フィールドは、Responses API によって定義されたスキーマに準拠したツール定義のリストです。Codex の場合、これには Codex CLI によって提供されるツール、Codex で利用可能にすべき Responses API によって提供されるツール、および通常は MCP サーバー経由でユーザーによって提供されるツールが含まれます。

JavaScript

1
[
2
// Codex's default shell tool for spawning new processes locally.
3
{
4
"type": "function",
5
"name": "shell",
6
"description": "Runs a shell command and returns its output...",
7
"strict": false,
8
"parameters": {
9
"type": "object",
10
"properties": {
11
"command": {"type": "array", "description": "The command to execute", ...},
12
"workdir": {"description": "The working directory...", ...},
13
"timeout_ms": {"description": "The timeout for the command...", ...},
14
...
15
},
16
"required": ["command"],
17
}
18
}
19

20
// Codex's built-in plan tool.
21
{
22
"type": "function",
23
"name": "update_plan",
24
"description": "Updates the task plan...",
25
"strict": false,
26
"parameters": {
27
"type": "object",
28
"properties": {"plan":..., "explanation":...},
29
"required": ["plan"]
30
}
31
},
32

33
// Web search tool provided by the Responses API.
34
{
35
"type": "web_search",
36
"external_web_access": false
37
},
38

39
// MCP server for getting weather as configured in the
40
// user's ~/.codex/config.toml.
41
{
42
"type": "function",
43
"name": "mcp__weather__get-forecast",
44
"description": "Get weather alerts for a US state",
45
"strict": false,
46
"parameters": {
47
"type": "object",
48
"properties": {"latitude": {...}, "longitude": {...}},
49
"required": ["latitude", "longitude"]
50
}
51
}
52
]

最後に、JSON ペイロードの input フィールドは項目のリストです。Codex 次の項目を(新しいウィンドウで開く) input に挿入します。その後、ユーザーメッセージを追加します。

1.toolsセクションで定義されたCodex 提供のshellツールにのみ適用されるサンドボックスを説明する、 role=developer のメッセージ。つまり、MCP サーバーから提供されるツールなどの他のツールは Codex によってサンドボックス化されておらず、独自のガードレールを適用する責任があります。

このメッセージはテンプレートから作成されており、主要なコンテンツは Codex CLI にバンドルされている Markdown のスニペット(例:workspace_write.md(新しいウィンドウで開く)on_request.md(新しいウィンドウで開く))から取得されます。

プレーンテキスト

1
<permissions instructions>
2
- description of the sandbox explaining file permissions and network access
3
- instructions for when to ask the user for permissions to run a shell command
4
- list of folders writable by Codex, if any
5
</permissions instructions>

2. (オプション)role=developer を持つメッセージで、その内容はユーザーの config.toml ファイルから読み取った developer_instructions の値です。

3. (オプション)role=userを持つメッセージで、その内容は「ユーザー指示」であり、単一のファイルから取得されるのではなく、複数のソースから集約され(新しいウィンドウで開く)ます。一般的に、次のようなより具体的な指示は後に示されます。

4. エージェントが現在動作中のローカル環境を説明する role=user のメッセージ。これは 現在の作業ディレクトリとユーザーのシェルを指定します(新しいウィンドウで開く)

プレーンテキスト

1
<environment_context>
2
<cwd>/Users/mbolin/code/codex5</cwd>
3
<shell>zsh</shell>
4
</environment_context>

Codex が上記のすべての計算を行って input を初期化した後、会話を始めるためにユーザーメッセージを追加します。

前の例では各メッセージの内容に焦点を当てましたが、input の各要素は次のように、typerole(新しいウィンドウで開く)content を含む JSON オブジェクトであることに注意してください。

JSON

1
{
2
"type": "message",
3
"role": "user",
4
"content": [
5
{
6
"type": "input_text",
7
"text": "Add an architecture diagram to the README.md"
8
}
9
]
10
}

Codex は Responses API に送信する完全な JSON ペイロードを構築した後、~/.codex/config.toml における Responses API エンドポイントの構成に応じて、Authorization ヘッダーを付けた HTTP POST リクエストを作成します(指定されている場合は、追加の HTTP ヘッダーとクエリパラメータも追加されます)。

OpenAI Responses API サーバーがこのリクエストを受信すると、JSON を使用してモデルへのプロンプトを次のように導出します(念のため、Responses API のカスタム実装では異なる選択が行われる可能性があります)。

AI エージェントループの単一ステップを示すスナップショット図。ユーザーのリクエストがモデルに入ると、そこから思考、ツール名を含むアクション、およびツール入力が生成されます。図では、ツールが呼び出される前のこの中間推論ステップが強調表示されています。

ご覧のとおり、プロンプト内の最初の3つの項目の順序は、クライアントではなくサーバーによって決定されます。ただし、これら3つの項目のうち、toolsinstructionsはクライアントによって決定され、システムメッセージの内容のみがサーバーによっても制御されます。これらの後に JSON ペイロードからの input が続き、プロンプトが完成します。

プロンプトが完成したので、モデルをサンプリングする準備ができました。

最初のターン

Responses API へのこの HTTP リクエストにより、Codex における会話の最初の「ターン」が開始されます。サーバーは Server-Sent Events(SSE(新しいウィンドウで開く))ストリームで応答します。各イベントの data は、"response" で始まる "type" を持つ JSON ペイロードで、次のようなものです(イベントの完全な一覧は API ドキュメント(新しいウィンドウで開く) にあります)。

プレーンテキスト

1
data: {"type":"response.reasoning_summary_text.delta","delta":"ah ", ...}
2
data: {"type":"response.reasoning_summary_text.delta","delta":"ha!", ...}
3
data: {"type":"response.reasoning_summary_text.done", "item_id":...}
4
data: {"type":"response.output_item.added", "item":{...}}
5
data: {"type":"response.output_text.delta", "delta":"forty-", ...}
6
data: {"type":"response.output_text.delta", "delta":"two!", ...}
7
data: {"type":"response.completed","response":{...}}

Codexは イベントのストリームを取り込み(新しいウィンドウで開く)、クライアントが利用可能な内部イベントオブジェクトとして再公開します。response.output_text.delta のようなイベントは、UI でのストリーミングをサポートするために使用されますが、response.output_item.added のような他のイベントはオブジェクトに変換され、後続の Responses API 呼び出しのために input に追加されます。

Responses API への最初のリクエストに、2 つの response.output_item.done イベント(1 つは type=reasoning、もう 1 つは type=function_call)が含まれていると仮定します。これらのイベントは、ツール呼び出しへの応答を用いて再度モデルにクエリする際に、JSONのinputフィールドにおいて表現される必要があります。

JavaScript

1
[
2
/* ... original 5 items from the input array ... */
3
{
4
"type": "reasoning",
5
"summary": [
6
"type": "summary_text",
7
"text": "**Adding an architecture diagram for README.md**\n\nI need to..."
8
],
9
"encrypted_content": "gAAAAABpaDWNMxMeLw..."
10
},
11
{
12
"type": "function_call",
13
"name": "shell",
14
"arguments": "{\"command\":\"cat README.md\",\"workdir\":\"/Users/mbolin/code/codex5\"}",
15
"call_id": "call_8675309..."
16
},
17
{
18
"type": "function_call_output",
19
"call_id": "call_8675309...",
20
"output": "<p align=\"center\"><code>npm i -g @openai/codex</code>..."
21
}
22
]

後続のクエリの一部としてモデルをサンプリングするために使用されるプロンプトは、結果として次のようになります。

ツール呼び出し後の AI エージェントを示す、「Snapshot 2」とラベル付けされた図。このモデルはツールの観察結果を受け取り、新たな思考とアクションを生成します。矢印は入力、観察、出力を繋ぎ、エージェントが推論ループをどのように反復するかを示しています。

特に、古いプロンプトが新しいプロンプトの正確な接頭辞(プレフィックス)になっていることに注意してください。これは意図的なもので、プロンプトキャッシュ(パフォーマンスに関する次のセクションで説明します)を活用できることから、後続のリクエストの効率性が大幅に向上します。

エージェントループの最初の図について振り返ると、推論とツール呼び出しの間に多くの反復が発生する可能性があることが分かります。プロンプトは、最終的にアシスタントメッセージを受信し、ターンの終了を示すまで増え続ける可能性があります。

プレーンテキスト

1
data: {"type":"response.output_text.done","text": "I added a diagram to explain...", ...}
2
data: {"type":"response.completed","response":{...}}

Codex CLI では、アシスタントメッセージをユーザーに表示し、コンポーザーに焦点を当てて、会話を続ける「順番」がユーザーにあることを示します。ユーザーが応答した場合、新しいターンを開始するために、前のターンのアシスタントメッセージとユーザーの新しいメッセージの両方を、Responses API リクエストの input に追加する必要があります。

JavaScript

1
[
2
/* ... all items from the last Responses API request ... */
3
{
4
"type": "message",
5
"role": "assistant",
6
"content": [
7
{
8
"type": "output_text",
9
"text": "I added a diagram to explain the client/server architecture."
10
}
11
]
12
},
13
{
14
"type": "message",
15
"role": "user",
16
"content": [
17
{
18
"type": "input_text",
19
"text": "That's not bad, but the diagram is missing the bike shed."
20
}
21
]
22
}
23
]

再度申し上げますが、会話を継続しているため、当社がResponses API に送信する input の長さは増加し続けます。

AI エージェントループの最終段階を示す、「Snapshot 3」とラベル付けされた図。ツールの結果を受け取った後、モデルは結論となる考えと、ユーザーに返される最終的な回答を生成します。矢印は、ツールの出力から完成した応答への移行を示します。

では、この増え続けるプロンプトがパフォーマンスにどのような影響を与えるのかを検討しましょう。

パフォーマンスに関する考慮点

「ちょっと待って、会話の過程で Responses API に送信される JSON の量という点では、エージェントループは 二次的 ではないのか?」と疑問に思うかもしれません。おっしゃるとおりです。Responses API は、この問題を軽減するためのオプションの previous_response_id(新しいウィンドウで開く) パラメーターをサポートしていますが、Codex は現在これを使用していません。主な理由は、リクエストを完全にステートレスに保ち、ゼロデータ保持(ZDR)構成をサポートするためです。

previous_response_id を回避することで、Responses API の提供者にとっては、すべてのリクエストがステートレスであることが保証されるため、物事が簡素化されます。これにより、ゼロデータ保持(ZDR)(新しいウィンドウで開く)を選択したお客様をサポートすることも容易になります。なぜなら、previous_response_id をサポートするために必要なデータを保存することは、ZDR と相反するからです。ZDR のお客様は、関連する encrypted_content をサーバーで復号化できるため、以前のターンの独自の推論メッセージから得られるメリットを失うことはありません。(OpenAI は ZDR のお客様の復号鍵を保持しますが、データは保持しません。)ZDR をサポートするための Codex への関連変更については、PR #642(新しいウィンドウで開く)#1641(新しいウィンドウで開く) を参照してください。

一般的に、モデルのサンプリングコストはネットワークトラフィックのコストの大部分を占めるため、効率化の主な対象となります。これがプロンプトキャッシュが非常に重要である理由です。以前の推論呼び出しでの計算を再利用できるからです。キャッシュヒットが発生すると、モデルのサンプリングは二次的ではなく線形的になります。当社のプロンプトキャッシング(新しいウィンドウで開く)のドキュメントでは、これについてさらに詳しく説明しています。

キャッシュヒットは、プロンプト内でプレフィックスが完全に一致する場合にのみ可能です。キャッシュの利点を活用するためには、指示や例のような静的コンテンツをプロンプトの先頭に配置し、ユーザー固有の情報などの可変コンテンツを最後に置いてください。これは画像やツールにも適用され、リクエスト間で同一である必要があります。

これを踏まえて、Codex でどのような種類の操作が「キャッシュミス」を引き起こす可能性があるかを考えてみましょう。

  • 会話の途中でモデルが使用できるtoolsを変更します。
  • Responses API リクエストの対象である モデル を変更します(実際には、元のプロンプトの3番目の項目が変更されます。そこにはモデル固有の指示が含まれています)。
  • サンドボックスの構成、承認モード、または現在の作業ディレクトリを変更します。

Codex チームは、プロンプトキャッシュを損なう可能性のある Codex CLI の新機能を導入する際に、細心の注意を払う必要があります。例として、MCP ツールの初期サポートでは、ツールを一貫した順序で列挙できないバグ(新しいウィンドウで開く)が発生し、キャッシュミスを引き起こしました。MCP ツールは、MCP サーバーがnotifications/tools/list_changed(新しいウィンドウで開く)通知を通じて提供するツールのリストをその場で変更できるため、特に扱いが難しいことに注意してください。長い会話の途中でこの通知を受け入れると、高コストなキャッシュミスが発生する可能性があります。

可能な場合は、会話の途中で発生する構成の変更について、以前のメッセージを変更するのではなく、変更を反映する新しいメッセージをinputに追加して対応します。

当社はパフォーマンス向上のため、キャッシュヒットを確実にするためのあらゆる努力をしています。管理すべき重要なリソースがもう1つあります。それはコンテキストウィンドウです。

コンテキストウィンドウが不足しないようにするための一般的な戦略は、トークン数がしきい値を超えた場合に会話をコンパクト化することです。具体的には、 inputを会話を代表する新しい、より小さな項目リストに置き換え、エージェントがこれまでに何が起こったかを理解しながら続行できるようにします。圧縮の初期実装(新しいウィンドウで開く)では、ユーザーが手動で /compact コマンドを呼び出し、既存の会話にカスタム指示を加えて 要約(新しいウィンドウで開く)を行うために Responses API をクエリする必要がありました。Codex は、サマリーを含む結果のアシスタントメッセージを、後続の会話ターンの新しい入力(新しいウィンドウで開く)として使用しました。

それ以来、Responses API は、圧縮をより効率的に行う特別な /responses/compact エンドポイント(新しいウィンドウで開く) をサポートするように進化してきました。これは、コンテキストウィンドウを解放しながら会話を続行するために、前の input(新しいウィンドウで開く) の代わりに使用できる 項目のリストを返します。このリストには、元の会話に対するモデルの潜在的な理解を保持する不透明な encrypted_content 項目を含む、特別なtype=compaction項目が含まれています。これにより、auto_compact_limit(新しいウィンドウで開く) を超えた場合に、Codex はこのエンドポイントを自動的に使用して会話を圧縮します。

次に来るもの

当社はCodex エージェントループを導入し、モデルにクエリを行う際にCodexがどのようにコンテキストを構築し、管理するかについて解説しました。その過程で、Responses API 上にエージェントループを構築するすべての方に適用される実践的な考慮事項とベストプラクティスを強調しました。

エージェントループは Codex の基盤を提供しますが、これは始まりに過ぎません。今後の投稿では、CLI のアーキテクチャを詳しく掘り下げ、ツールの使用方法の実装について探り、Codex のサンドボックスモデルをより詳細に見ていきます。

著者

Michael Bolin

謝辞

Codex CLI を構築したチーム全員に特別な感謝を捧げます。