Deep Agents におけるインタープリタ:ツール呼び出しとサンドボックスの間のコード
LangChain は Deep Agents に「インタプリタ」機能を追加し、ツール呼び出しと完全なサンドボックスの中間に位置するコード実行環境を提供することで、エージェントの推論効率と状態管理を革新した。
キーポイント
中間層としてのインタプリタ機能
エージェントは一度に一つずつツールを呼ぶ従来の手法や、完全なサンドボックス環境ではなく、コードレベルでの合成能力を持つスコープ付きの環境で動作できるようになる。
第 3 のコンテキスト表面としての状態管理
モデルの推論対象であるメッセージ履歴と、永続的なアーティファクトであるファイルシステムに加え、生の実行値を保持する「インタプリタの状態」が新たなコンテキスト層として確立される。
プログラムによるツール呼び出しのミドルウェア化
許可されたツールがインタプリタ内の `tools` 名前空間に配置され、あらゆるモデルと連携可能となり、初期テストでは一部のタスクでトークン使用量を最大 35% 削減した。
影響分析・編集コメントを表示
影響分析
このアプローチは、AI エージェントの信頼性と効率性を劇的に向上させる可能性を秘めています。特に、複雑なマルチステップタスクにおいて、不要なコンテキストの膨張を防ぎつつ、中間結果を安全に管理できる点は実用化への大きな一歩です。これにより、より高度で予測可能な自律型エージェントの実装が一般化すると予想されます。
編集コメント
ツール呼び出しと完全な環境実行の狭間を埋めるこのアーキテクチャは、実務レベルのエージェント開発において非常に重要なパラダイムシフトです。トークンコスト削減と状態管理の分離という明確なメリットが、採用加速の鍵となるでしょう。
エージェントにインタプリタを付与する

主要なポイント
- インタプリタは、連続するツール呼び出しと完全なサンドボックスの間に位置します。エージェントは、スコープされた機能に対するコードレベルでの合成能力を得ながら、環境全体を継承することはありません。
- インタプリタの状態は、3 つ目のコンテキスト表面となります。メッセージ履歴はモデルが現在推論を行うためのもの、ファイルシステムは永続的な成果物のためのものであり、インタプリタの状態は、まだモデルの入力として必要とされていない生の実行値のためのものです。
- プログラムによるツール呼び出しは、ミドルウェアとして組み込まれます。許可されたツールは、インタプリタ内の tools 名前空間の下に表示され、あらゆるモデルで動作し、初期テストでは一部のタスクにおいてトークン使用量を最大 35% 削減しました。
TL;DR 私たちは Deep Agents にインタプリタを追加します。これはエージェントがループ内でコードを記述して実行できる、小さな埋め込みランタイムです。これにより、エージェントは一度に一つずつツールを呼び出すことと完全なサンドボックスの間の中間地点を得て、多段階の作業を表現し、中間状態をモデルのコンテキストから外し、コードとアクションをより予測可能な方法で実行できるようになります。
インタプリタとは何か?
インタプリタは、エージェントが作業中にコードを対象として記述できる、小さな埋め込みランタイムです。機能的には、エージェントに Python や Node の REPL(対話型実行環境)を与えるようなものです:変数の定義、値の検査、ヘルパー関数の作成、および呼び出し間での状態の再利用が可能になります。

多くのエージェントは現在、ホストまたはサンドボックス環境に対してコマンドを発行することでコードを実行しています。これは、タスクが環境レベルの作業(コマンドの実行、依存関係のインストール、ファイルシステムを介した操作など)である場合に非常に有効です。一方、インタプリタは異なるレイヤーを対象としています:エージェントは、委任の調整、ツール呼び出しの合成、構造化データの変換、そしてモデルに返すべき情報の決定を行うために、エージェントループ内で実行されるコードを書きます。
// エージェントは以下のようなコードを書く
const rows = [
{ team: "support", tickets: 18 },
{ team: "infra", tickets: 7 },
{ team: "sales", tickets: 11 },
];
const total = rows.reduce((sum, row) => sum + row.tickets, 0);
const busiest = rows.sort((a, b) => b.tickets - a.tickets)[0];
${busiest.team} has the most tickets. ${total} tickets total.;
これにより、エージェントはツール呼び出しの連続にきれいに収まらない振る舞いを表現するための新たな場所を得ます。エージェントは多段階ロジックのための作業スペースを手に入れつつ、その作業スペースがアクセスできる範囲についてはハッチ(制御枠)が依然として管理します。インタプリタは一時的な状態を保持し、必要な部分だけを返すことができます。
インタプリタの位置づけ
エージェントについて考えるとき、通常はツールを接続することを思い浮かべます。
エージェントの最も単純な形態では、エージェントはループの中でそれらのツールを使用します:モデルが 1 つのツールを呼び出し、観測結果を検証し、次に何をすべきかを決定します。この 1 ステップずつというスタイルはデバッグや評価が容易であり、多くのワークフローには即座の観測結果に基づいて推論する仕組みが必要です。
サンドボックスはこの上に構築され、エージェントに環境に対してコマンドを実行し、依存関係をインストールし、ファイルと作業を行うための bash ツールを提供します。
しかし、両端には欠点があります:サンドボックスはローカル手順を処理できる可能性がありますが(コードを書くことで対応可能)、プロビジョニングやスケーリングが難しい場合があります。また、中間ステップのほとんどが次のステップに引き継がれる場合、純粋な逐次ツールループは awkward になりがちです。
いくつかのエージェント作業はこの 2 つの極端な間の領域に位置し、インタープリタがそこにうまく収まります。これらは、エージェントにスコープされた機能に対するコードレベルでの合成能力を与えつつ、完全な環境を提供することはありません。モデルは既存の機能に対する制御フローを表現する小さなプログラムを書くことができ、一方ハーン(harness)はホストを通じて利用可能な機能がどれかを決定します。

More limited by design
このインタープリタは単なるコード実行環境ではなく、意図的に制限を設けた「インタープリタ」と呼んでいます。デフォルトでは、通常のプログラミング環境で期待される API は備えていません:ファイルシステムもネットワークアクセスもシェル機能もパッケージのインストールも、壁時間(wall-time)へのアクセスもありません。エージェントは基本的な制御フローとオブジェクト操作から開始します:オブジェクト、配列、マップ、JSON、そして残りの小規模言語ランタイムです。
これらの機能は、ホストランタイムへの明示的なブリッジを通じて公開されます。エージェントがツールを呼び出したり、スコープ付きのファイルシステム API から読み込んだり、URL を取得したり、サブエージェントに委任したりする必要がある場合、ハネス(harness)側でその機能を意図的に公開する必要があります。例えば、このスクリプトは、fetch、read_file、task の各ツールをインタープリタに明示的にブリッジした場合のみ動作します:
// calls the fetch tool to make a network request
const response = tools.fetch("https://docs.langchain.com");
// calls the readFile tool to fetch files from the agents filesystem
const file = tools.readFile("SPEC.md");
// calls the task tool to spawn a subagent
const subagentOutput = tools.task({
description: "Do you know the muffin man?"
});
ホストランタイム(ハネスを実行するのと同じもの)には、インタープリタを使用してエージェントが実行可能なすべてのアクションが含まれており、どのアクションをインタープリタコードから呼び出せるかを明示的に決定します。インタープリタは、その境界におけるエージェントのプログラム可能な側面です。
デフォルトでは、インタプリタは言語機能のみで起動し、サンドボックスが提供する一般的なホストアクセスのようなものは含まれません。外部世界に触れるあらゆる操作は、あなたが指定する明示的なブリッジを通過する必要があります。

これはいくつかの理由から行っています:
- より小さなアクション表面:bash やサンドボックスの場合、出発点は広範です。エージェントはコンピュータのようなものを持っており、そこから実行できることを制限します。一方、インタプリタの場合、出発点は狭いです。エージェントは言語ランタイムを持っており、機能は意図的に追加されます。これは、脅威モデルでプロセスや VM の分離が必要な場合にサンドボックス化を置き換えるものではありませんが、デフォルトで広範なホストアクセスを引き継ぐことがないことを意味します。
- 予測可能性:小さく固定されたランタイムにより、エージェントの挙動をより容易に予測・評価できます。インタプリタが広範なホストアクセスや豊富なライブラリ表面を持っていた場合、同じ目標を達成するために多くの異なる戦略が可能となり、出力が一貫性を欠き、テストが困難になります。デフォルト環境を最小限に保ち、追加の機能は明示的な橋渡しを介してのみ使用できるようにすることで、エージェントのアクション空間を狭め、失敗モードを明確にし、結果の再現性を高めます。
Figma、Shopify、AWS などのシステムでも同じアーキテクチャ形状が見られます:一方では制約されたコードが実行され、他方ではホストが制御された API バウンダリを公開しています。
インタープリタが解き放つもの
いくつかの最近のシステムは、類似したパターンに収束しています:モデルに対して、制御フローと中間状態を管理するために少量のコードを書ける、スコープが限定されたランタイムを提供するというものです。Cloudflare の Code Mode、Anthropic の Programmatic Tool Calling (PTC)、そして RLM スタイルのワークフローは、それぞれ異なる角度からこのアイデアを指し示しています。Deep Agents においては、インタープリタとは、モデルに依存しない方法でこのパターンを実現する手段です。すでに有用な例として、以下のような場所があります:
コンテキスト・サーフェースとしてのインタープリタ状態
エージェント・ハーネスは、すでにいくつかのコンテキスト・サーフェス(context surface)を介してコンテキストを整理しています:
- メッセージ履歴は、モデルに対して即座に利用可能なコンテキストです。これはコストが高く、アテンションの制約を受けます。モデルが 100 万トークンを受け取れるからといって、すべてのトークンを均等に推論できるわけではありません(例:コンテキストローテーション)。
- ファイルシステムは、エージェントが永続的なアーティファクト、メモ、中間ファイル、より長期間にわたる作業用メモリを保存するための場所を提供します。これは耐久性と柔軟性に富んでいますが、エージェントに対して作業状態をファイルにシリアライズし、後で再構築することを強いることになります。
- ハーネスの役割の一部は、ファイルシステムとメッセージ履歴の間でのコンテキストの流れを制御することです。
インタプリタの状態は、エージェントにもう一つの選択肢を与えます。値は、配列、オブジェクト、マップ、カウンター、キュー、およびヘルパー関数としてランタイム内に保持できます。モデルはすべての中間値をプロンプトテキストとして見る必要はありませんが、後でそれらの値を検査したり再利用したりするためにインタプリタに指示を出すことは可能です。

これは、REPL(対話型実行環境)が単発のコマンドを実行するのと異なる理由に似ています。REPL で変数を定義すると、次に送信したコマンドでもその値はそのまま残っています。それを標準出力に変換したり、ファイルに書き込んだり、次の作業を行う前に再構築したりする必要はありません。エージェントがインタプリタを複数回呼び出す場合にも、この同じ原則が適用されます。なぜなら、前の呼び出しからの値をそのまま再利用できるからです。
これにより、インタプリタはエージェントループの状態管理に有用となります。メッセージ履歴はモデルが現在推論する必要がある内容のためのものであり、ファイルシステムは永続的な成果物や環境レベルの作業のためのものです。そして、インタプリタの状態は、後で有用になる可能性のある生の実行値を扱うためのものであり、まだモデルの入力として取り込む必要はありません。
プログラムによるツール呼び出し
Anthropic の Programmatic Tool Calling (PTC) は、このパターンの別の実装です。ここでは、モデルが仲介する一連のアクションとしてではなく、エージェントが記述したコード内部からツール呼び出しが行われます。
モデルがツールを呼び出して結果を受け取り、それについて推論し、次のツールを呼び出す場合、小さなステップごとにモデルとの往復通信が発生してしまいます。しかし、エージェントがツールを直接呼び出すコードを書けるのであれば、中間出力をランタイム内に保持し、最終的な結果や選択された証拠のみを返すことができます。
Deep Agents においては、PTC はモデルプロバイダー側の機能ではなく、ミドルウェアとして実装されています。開発者は許可リスト(allowlist)を渡すことで、許可されたツールがグローバルなツール名前空間の下に表示され、各ツールはインタプリタが await を使って呼び出せる非同期関数として公開されます。これにより、*あらゆる*モデル(オープンソースのものを含む)で PTC を有効化することが可能になります。
const topics = ["retrieval", "memory", "evaluation"];
const reports = await Promise.all(
topics.map((topic) =>
tools.task({
description: Research ${topic} in Deep Agents and return three concise findings.,
subagent_type: "general-purpose",
}),
),
);
reports.join("\\n\\n");

初期のテストの一部では、このスタイルのツール呼び出しにより、特定のタスクでトークン使用量が最大 35% 削減されました(OOLONG trec-coarse データセットから収集したタスクの集合に対して評価を実施)。

大規模データセットの処理
文書中心のタスクを想定してください:エージェントが 10,000 件の文書から情報を分類、抽出、または合成する必要があるケースです。
標準的なツール呼び出し型エージェントでは、自然な形状はモデル仲介によるアクションの長いシーケンスとなります。モデルが検索を行い、結果をコンテキスト内で取得し、次に何を精査するかを決定し、別のツールを呼び出し、さらに多くの結果を取得し、これを繰り返します。小規模なタスクであればこのループで十分ですが、スケールが大きくなると機能しなくなりはじめます:
- エージェントが実際に意図した手順に従ったかどうかを検証するのは困難です。
- 中間コンテキストの多くがモデルを介して戻されてしまいます。
- レイテンシ、コンテキスト制限、またはツール呼び出しの上限に直面しやすくなります。
- モデルが履歴を通じて管理しなければならない作業状態が多すぎるため、応答品質が低下する可能性があります。
インタプリタ型のバージョンはこれとは異なります。モデルは、ドキュメントや検索状態をランタイム内に保持し、バッチをプログラム的に反復処理し、候補のスコア付けまたはフィルタリングを行い、選択されたスライスに対してのみサブエージェントを呼び出すコードを書くことができます。すべての中間結果をモデルに返すのではなく、インタプリタはコンパクトな証拠セットを返します:一致したドキュメント、抽出されたフィールド、未解決のケース、あるいは推論する価値のある数件の要約です。
インタプリタが 10,000 ドキュメントすべてに対して魔法のように推論しているわけではありません。それはエージェントに対し、検索空間を制御し、何を入力すべきかを決定するためのより良い方法を提供します。
const candidates = documents
.map((doc) => ({ doc, score: scoreDocument(doc, query) }))
.filter(({ score }) => score > 0.75)
.sort((a, b) => b.score - a.score)
.slice(0, 10);
const reports = await Promise.all(
candidates.map(({ doc }) =>
tools.task({
description: `Extract evidence from ${doc.id} for: ${query}`,
subagent_type: "general-purpose",
}),
),
);
reports.join("\n\n");- エージェントが実際に意図した手順に従ったかどうかを検証するのは困難です。
- 中間コンテキストの多くがモデルを介して戻されてしまいます。
- レイテンシ、コンテキスト制限、またはツール呼び出しの上限に直面しやすくなります。
- モデルが履歴を通じて管理しなければならない作業状態が多すぎるため、応答品質が低下する可能性があります。
インタプリタ型のバージョンはこれとは異なります。モデルは、ドキュメントや検索状態をランタイム内に保持し、バッチをプログラム的に反復処理し、候補のスコア付けまたはフィルタリングを行い、選択されたスライスに対してのみサブエージェントを呼び出すコードを書くことができます。すべての中間結果をモデルに返すのではなく、インタプリタはコンパクトな証拠セットを返します:一致したドキュメント、抽出されたフィールド、未解決のケース、あるいは推論する価値のある数件の要約です。
インタプリタが 10,000 ドキュメントすべてに対して魔法のように推論しているわけではありません。それはエージェントに対し、検索空間を制御し、何を入力すべきかを決定するためのより良い方法を提供します。
const candidates = documents
.map((doc) => ({ doc, score: scoreDocument(doc, query) }))
.filter(({ score }) => score > 0.75)
.sort((a, b) => b.score - a.score)
.slice(0, 10);
const reports = await Promise.all(
candidates.map(({ doc }) =>
tools.task({
description: `Extract evidence from ${doc.id} for: ${query}`,
subagent_type: "general-purpose",
}),
),
);
reports.join("\n\n");再帰的オーケストレーション
もう一つの関連するアイデアに Recursive Language Models (RLMs) があります。RLM は長いプロンプトを外部の REPL(Read-Eval-Print Loop)環境の一部として扱い、モデルがコードを書かせて、選択されたスニペットに対して検査・分解・再帰的な呼び出しを行わせるものです。
Deep Agents のインタープリタはモデル層で RLM を実装しているわけではありませんが、ハッチレベルでは依然として関連する接続があります。つまり、コードはモデルのコンテキストの外に作業状態を保持し、その状態の一部を選択して、次のモデルやサブエージェントへの呼び出しにその一部のみを渡すことができます。
Deep Agents において、tools.task はこのための橋渡し役です。インタープリタのコードは作業のスライスを選択し、それをサブエージェントに委任し、結果を既存の実行時状態と結合して、合成された出力のみをメインモデルに戻します。
Deep Agents における動作原理
ハッチレベルでは、インタープリタはエージェントループと小さなランタイムの間のミドルウェアです。このミドルウェアは以下の役割を果たします:
- エージェントに評価ツールを追加する
- QuickJS コンテキストを作成して維持する
- エージェントの TypeScript コードを実行する
- 設定された場合に
console.logの出力をキャプチャする - 最終的な式をモデルコンテキストに戻す
この評価ツールは「ホスト上で任意のコードを実行する」ものではありません。コードはインタープリタのコンテキスト内で実行されます。外部世界と通信が必要な場合は、ホストランタイムが提供するブリッジを通じて通信を行います。
プログラムによるツール呼び出しは、そのホストブリッジの一つです。開発者は PTC(Programmatic Tool Calling)の許可リストを渡しますと、許可されたツールがインタープリタ内の tools 名前空間内に表示されます(例:tools.getWeather(...))。各ツールは、await を使用してインタープリタが呼び出せる非同期関数として公開されます。ホストランタイムは依然として実際のツール呼び出しを実行します。
おおよそのフローは以下のようになります。
- モデルがコードを記述し、eval を呼び出す
- QuickJS がインタープリタコンテキスト内でコードを評価する
- インタープリタのコードがオプションで許可されたツールを呼び出す
- ホストランタイムが実際のツール呼び出しを実行する
- 結果が再びインタープリタへ戻される
- 最終的な式がモデルコンテキストへ戻される
1 つの実行内で繰り返される eval 呼び出しは、同じライブなインタープリタコンテキストを共有できます。これにより、値が REPL(Read-Eval-Print Loop)の状態のように振る舞うようになります。会話のターン間のスナップショット機能も利用可能ですが、これは生きたハンドルやホストリソースではなく、シリアライズ可能な作業データを保存するための手段として扱うべきです。
ランタイム制御もこの境界で行われます:
- メモリ制限
- 評価ごとのタイムアウト
- プログラムによるツール呼び出しの最大数
- 結果サイズの最大値
- コンソールキャプチャ
- ターン間のスナップショット

Deep Agents での使用方法
Deep Agents でインタープリタをインストールし、create_deep_agent を使用してミドルウェアを追加できます:
uv add "deepagents[quickjs]"
from deepagents import create_deep_agent
from langchain_quickjs import CodeInterpreterMiddleware
agent = create_deep_agent(
model="openai:gpt-5.5",
middleware=[CodeInterpreterMiddleware()],
)
(and in TypeScript)
pnpm install deepagents @langchain/quickjs
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";
const agent = createDeepAgent({
model: "openai:gpt-5.5",
middleware: [createCodeInterpreterMiddleware()],
});
インタプリタコードがエージェントのツールを呼び出せるようにするには、プログラムによるツール呼び出しを許可リスト(allowlist)で有効化する必要があります。ツールは自動的にインタプリタコードに公開されるわけではなく、ホスト・ランタイム間のブリッジを越えて使用できるツールを自分で選択する必要があります。
agent = create_deep_agent(
model="openai:gpt-5.5",
middleware=[CodeInterpreterMiddleware(ptc=["task"])],
)
const agent = createDeepAgent({
model: "openai:gpt-5.5",
middleware: [createCodeInterpreterMiddleware({ ptc: ["task"] })],
});
PTC(Programmatic Tool Calling:プログラムによるツール呼び出し)を有効化すると、許可リストに登録されたツールがグローバルなツールの名前空間の下に表示されます。各ツールは非同期関数であり、モデルには中間のツール結果ではなく最終的なインタプリタの出力が渡されます。
Deep Agents は Python および TypeScript で利用可能です。インタプリタ に関する詳細情報、およびミドルウェアオプションとランタイム制御の全セットについては、ドキュメントをご覧ください。
関連コンテンツ

Deep Agents
Managed Deep Agents の紹介

Victor Moreira
2026 年 5 月 13 日
5
分

Deep Agents
Deep Agents v0.6 の新機能

Sydney Runkle
2026 年 5 月 13 日
10
分

Deep Agents
LangGraph
デルタチャネル:長期実行型エージェント向けランタイムの進化

Sydney Runkle
2026 年 5 月 12 日
7
分
エージェントが実際に何をしているかを確認する
LangSmith は、エージェントエンジニアリングプラットフォームであり、開発者がすべてのエージェントの意思決定をデバッグし、変更の評価を行い、ワンクリックでデプロイできるように支援します。
原文を表示
Give Your Agents an Interpreter

Key Takeaways
- Interpreters sit between serial tool calls and full sandboxes. Agents get code-level composition over scoped capabilities without inheriting a whole environment.
- Interpreter state is a third context surface. Message history is for what the model reasons over now, the filesystem is for durable artifacts, interpreter state is for live working values that don't need to be model input yet.
- Programmatic tool calling drops in as middleware. Allowlisted tools appear under a tools namespace inside the interpreter, work with any model, and used up to 35% fewer tokens on some tasks in early testing.
TL;DR We’re adding interpreters to Deep Agents: small embedded runtimes where agents can write and execute code inside the agent loop. They give agents a middle ground between one-at-a-time tool calls and full sandboxes, so agents can express multi-step work, keep intermediate state out of model context, and execute code and actions in a more predictable way.
What’s an interpreter?
An interpreter is a small embedded runtime that an agent can write code against while it is working. Functionally, it feels like giving the agent a Python or Node REPL: it can define variables, inspect values, write helper functions, and reuse state across calls.

Many agents today already execute code by issuing commands to a host or sandbox environment. This is great when the task is environment-level work: running commands, installing dependencies, or operating over a filesystem. Interpreters are aimed at a different layer: the agent writes code that runs inside the agent loop to coordinate delegation, compose tool calls, transform structured data, and decide what information should come back to the model.
// agent writes code like this
const rows = [
{ team: "support", tickets: 18 },
{ team: "infra", tickets: 7 },
{ team: "sales", tickets: 11 },
];
const total = rows.reduce((sum, row) => sum + row.tickets, 0);
const busiest = rows.sort((a, b) => b.tickets - a.tickets)[0];
${busiest.team} has the most tickets. ${total} tickets total.;
This gives agents a new place to express behavior that doesn't fit cleanly into a sequence of tool calls. The agent gets a working space for multi-step logic, while the harness still controls what that working space can touch. The interpreter can hold temporary state and return only the part that matters.
Where interpreters fit
When you think of an agent, you usually think of attaching tools.
In the simplest form of an agent, the agent uses those tools in a loop: the model calls one tool, inspects the observation, then decides what to do next. That one-step-at-a-time style is straightforward to debug and evaluate, and a lot of workflows do require a way to reason over immediate observations.
Sandboxes build on top of that by giving the agent a bash tool that works against an environment to run commands, install dependencies, and work with files.
But both ends have downsides: sandboxes *can* handle local procedure (since it can just write code to do so), but they can be harder to provision and scale; and purely serial tool loops can be awkward when those intermediate steps mostly feed the next step.
Some agent work sits between those two extremes, which interpreters slot nicely into. They give the agent code-level composition over scoped capabilities without giving it a whole environment. The model can write a small program to express control flow over existing capabilities, while the harness decides which capabilities are available through the host.

More limited by design
We call this an interpreter, not just a code runtime, because the interpreter is intentionally limited. By default it does not have the APIs you would expect from a normal programming environment: no filesystem, no network, no shell, no package installation, and no wall-time access. The agent starts with basic control flow and object manipulation: objects, arrays, maps, JSON, and the rest of the small language runtime.
Those capabilities are exposed through explicit bridges to the host runtime. If the agent needs to call a tool, read from a scoped filesystem API, fetch a URL, or delegate to a subagent, the harness has to expose that capability deliberately. For instance, this script only works when we explicitly bridge the fetch, read_file and task tools directly to the interpreter:
// calls the fetch tool to make a network request
const response = tools.fetch("https://docs.langchain.com");
// calls the readFile tool to fetch files from the agents filesystem
const file = tools.readFile("SPEC.md");
// calls the task tool to spawn a subagent
const subagentOutput = tools.task({
description: "Do you know the muffin man?"
});
The host runtime (the same one that runs the harness) contains all the actions an agent can take using the interpreter, and explicitly decides which ones the interpreter code can call. The interpreter is the agent’s programmable side of that boundary.
By default, the interpreter starts with language features only, not generic host access like a sandbox gives you. Anything that touches the outside world has to cross an explicit bridge that you specify.

We do this for a few reasons:
- Smaller action surface: With bash or a sandbox, the starting point is broad: the agent has something shaped like a computer, and you restrict what it can do from there. With an interpreter, the starting point is narrow: the agent has a language runtime, and capabilities are added back deliberately. That does not replace sandboxing when your threat model requires process or VM isolation, but it does mean the agent is not inheriting broad host access by default.
- Predictability: A small, fixed runtime makes agent behavior easier to anticipate and evaluate. If the interpreter had broad host access or a rich library surface, the same goal could be achieved through many different strategies, which makes outputs less consistent and harder to test. By keeping the default environment minimal and forcing extra capabilities to cross explicit bridges, you make the agent’s action space narrower, the failure modes clearer, and the results more repeatable.
You see the same architectural shape in systems from Figma, Shopify, AWS, and others: constrained code runs on one side, while the host exposes a controlled API boundary on the other.
What interpreters unlock
A few recent systems have converged on similar patterns: give the model a small, scoped runtime where it can write a bit of code to manage control flow and intermediate state. Cloudflare’s Code Mode, Anthropic’s Programmatic Tool Calling (PTC), and RLM-style workflows each point at that idea from different angles. In Deep Agents, an interpreter is how you get that pattern in a model-agnostic way. Here are a few places it’s already been useful:
Interpreter state as a context surface
Agent harnesses already organize context across a few surfaces:
- Message history is the context immediately available to the model.It is expensive and attention-constrained: just because a model can accept a million tokens does not mean it will reason over every token equally well. (e.g. context rot)
- A filesystem gives the agent somewhere to store durable artifacts, notes, intermediate files, and longer-lived working memory.It is durable and flexible, but it forces the agent to serialize working state into files and then reconstruct it later.
- Part of the job of the harness is to control the flow of context between the filesystem and the message history.
Interpreter state gives the agent another option. Values can stay in the runtime as arrays, objects, maps, counters, queues, and helper functions. The model does not need to see every intermediate value as prompt text, but it can still ask the interpreter to inspect or reuse those values later.

This is similar to why a REPL feels different from running a one-off command. If you define a variable in a REPL, it is still there on the next command you submit. You do not have to turn it into stdout, write it to a file, or reconstruct it before doing the next thing. The same principle applies when an agent calls the interpreter multiple times, since it can just reuse the value from a previous call.
That makes interpreters useful for agent-loop state. Message history is for what the model needs to reason over now, the filesystem is for durable artifacts and environment-level work, and interpreter state is for live working values that may be useful later but do not need to become model input yet.
Programmatic tool calling
Anthropic’s Programmatic Tool Calling (PTC) is another version of this pattern: tool calls happen from inside code the agent writes, rather than as a sequence of model-mediated actions.
If the model calls a tool, receives the full result, reasons over it, and calls the next tool, every small step becomes another model round trip. If the agent can write code that calls tools directly, it can keep intermediate outputs in the runtime and return only the final result or selected evidence.
In Deep Agents, PTC is implemented as middleware rather than as a model-provider behavior. The developer passes an allowlist, allowlisted tools appear under the global tools namespace, and each tool is exposed as an async function the interpreter can call with await. This means that you can enable PTC for *any* model (including open source ones).
const topics = ["retrieval", "memory", "evaluation"];
const reports = await Promise.all(
topics.map((topic) =>
tools.task({
description: Research ${topic} in Deep Agents and return three concise findings.,
subagent_type: "general-purpose",
}),
),
);
reports.join("\\n\\n");

In some of our early testing, this style of tool calling used up to 35% fewer tokens on some tasks. (we evaluated this on a collected set of tasks from the OOLONG trec-coarse dataset)

Working over large datasets
Take a document-heavy task: an agent needs to classify, extract, or synthesize information from 10,000 documents.
With a standard tool-calling agent, the natural shape is a long sequence of model-mediated actions. The model searches, gets results back in context, decides what to inspect next, calls another tool, gets more results back, and repeats. For small tasks, that loop is sufficient. But at scale it starts to break down:
- It is hard to verify that the agent actually followed the intended procedure.
- Too much intermediate context gets routed back through the model.
- It is easy to run into latency, context, or tool-call limits.
- The response can degrade because the model is forced to manage too much working state through history.
An interpreter-shaped version looks different. The model can write code that keeps document and search state in the runtime, iterates through batches programmatically, scores or filters candidates, and calls subagents only on selected slices. Instead of returning every intermediate result to the model, the interpreter returns a compact evidence set: the documents that matched, the fields that were extracted, the unresolved cases, or the few summaries worth reasoning over.
The interpreter is not magically reasoning over all 10,000 documents. It gives the agent a better way to control the search space and decide what should enter model context.
const candidates = documents
.map((doc) => ({ doc, score: scoreDocument(doc, query) }))
.filter(({ score }) => score > 0.75)
.sort((a, b) => b.score - a.score)
.slice(0, 10);
const reports = await Promise.all(
candidates.map(({ doc }) =>
tools.task({
description: Extract evidence from ${doc.id} for: ${query},
subagent_type: "general-purpose",
}),
),
);
reports.join("\n\n");
Recursive orchestration
Another related idea is Recursive Language Models (RLMs). RLMs treat long prompts as part of an external REPL environment, then let the model write code to inspect, decompose, and recursively call models over selected snippets.
Deep Agents interpreters are not implementing RLMs at the model layer, but there is still a relevant connection at the harness level: code can hold working state outside the model context, select a slice of that state, and pass only that slice into the next model or subagent call.
In Deep Agents, tools.task is the bridge for this. Interpreter code can select a slice of work, delegate that slice to a subagent, combine the result with existing runtime state, and return only the synthesized output to the main model.
How it works in Deep Agents
At the harness level, the interpreter is middleware between the agent loop and a small runtime. The middleware:
- adds an eval tool to the agent
- creates and maintains a QuickJS context
- executes the agent’s TypeScript code
- captures console.log output when configured
- returns the final expression back into model context
The eval tool is not “run arbitrary code on the host.” The code runs inside the interpreter context. If it needs to communicate with the outside world, it does so through bridges the host runtime exposes.
Programmatic tool calling is one of those host bridges. The developer passes a ptc allowlist, allowlisted tools appear inside the interpreter under the tools namespace (e.g. tools.getWeather(...)), and each tool is exposed as an async function the interpreter can call with await. The host runtime still executes the real tool call.
The rough flow looks like this:
- the model writes code and calls eval
- QuickJS evaluates the code inside the interpreter context
- interpreter code optionally calls allowlisted tools
- the host runtime executes the real tool calls
- results cross back into the interpreter
- the final expression crosses back into model context
Repeated eval calls in a run can share the same live interpreter context, which is what lets values behave like REPL state. Snapshotting between conversation turns is also available, but it should be treated as a way to preserve serializable working data rather than live handles or host resources.
Runtime controls live at this boundary too:
- memory limits
- per-eval timeouts
- maximum programmatic tool calls
- maximum result size
- console capture
- snapshotting between turns

How to use it in Deep Agents
You can install the interpreter and add the middleware using create_deep_agent:
uv add "deepagents[quickjs]"
from deepagents import create_deep_agent
from langchain_quickjs import CodeInterpreterMiddleware
agent = create_deep_agent(
model="openai:gpt-5.5",
middleware=[CodeInterpreterMiddleware()],
)
(and in TypeScript)
pnpm install deepagents @langchain/quickjs
import { createDeepAgent } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";
const agent = createDeepAgent({
model: "openai:gpt-5.5",
middleware: [createCodeInterpreterMiddleware()],
});
`
`
To let interpreter code call agent tools, enable programmatic tool calling with an allowlist. Tools are not automatically exposed to interpreter code; you must choose which tools can cross the host-runtime bridge.
agent = create_deep_agent(
model="openai:gpt-5.5",
middleware=[CodeInterpreterMiddleware(ptc=["task"])],
)
const agent = createDeepAgent({
model: "openai:gpt-5.5",
middleware: [createCodeInterpreterMiddleware({ ptc: ["task"] })],
});
`
`
Once PTC is enabled, allowlisted tools appear under the global tools namespace. Each tool is an async function, and the model receives the final interpreter output rather than every intermediate tool result.
Deep Agents is available in Python and TypeScript. See the docs for more information on interpreters, as well as the full set of middleware options and runtime controls.
Related content

Deep Agents
Introducing Managed Deep Agents

Victor Moreira
May 13, 2026
5
min

Deep Agents
New in Deep Agents v0.6

Sydney Runkle
May 13, 2026
10
min

Deep Agents
LangGraph
Delta Channels: Evolving our Runtime for Long-Running Agents

Sydney Runkle
May 12, 2026
7
min
See what your agent is really doing
LangSmith, our agent engineering platform, helps developers debug every agent decision, eval changes, and deploy in one click.
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み