この記事では、プログラム可能なエージェント・オーケストレーションを実現する VM である Onyx を紹介します。そして、さらに言えば、オーケストレーションをソフトウェアエンジニアリングに変えるランタイムについても説明します。この記事を読み終える頃には、VM の構築における制約と設計上の決定、そして独自のプログラムを作成しエージェントシステムを設計する方法を理解できるでしょう。
はじめに
エージェントは本質的に非決定論的です。それが重要なポイントです。もし決定論を求めるなら、ソフトウェアを書けばいいのです。
しかし、いつしかエージェントを使う誰もが、彼らをより遠くへ押し進めたいと願うようになりました。私たちは、実行を構造化されたステップ(計画、実装、レビュー、QA など)に分割することでパフォーマンスが向上することを学びました。その後、各エージェントを誘導し、エージェント間でコンテキストを共有し、ガードレールを設定するために、スクリプト、ツール、スキルを書くことに同意したように見えます。そして、これらのスクリプトをエージェント間でテキストをパイプすることでつなぎ合わせます。単にテキストを渡しているだけなので、なんとなく機能します。
この問題に十分な時間を費やし、特に賢明であれば、特定の状態に基づいて条件付き実行ができるようにシステムから保証を引き出す方法を理解していたでしょう。そして、その状態を解析可能なマークアップファイル(またはファイル群)に保存して、bash スクリプトを制御していたでしょう。もしかすると、エージェントが使用するカスタム CLI を構築していたかもしれません。
エンジニアにとってこれはおなじみの話です。ソフトウェアエンジニアリングを行う際にスクリプトを使用します。しかし、現代のソフトウェアは bash スクリプトと CLI ツールを連鎖させることで構築されるわけではありません。その代わりに、システムをエンジニアリングするためのプログラミング言語、ランタイム、ツールチェーンがあります。標準ライブラリ、明確なセマンティクス、そして信頼できる実行モデルを備えているため、プログラミング言語でソフトウェアを書きます。これらは、私たちのあらゆるニーズに応えるツールチェーンを持つ豊かなエコシステムを備えています。
システムについて与えられる保証により、より高い抽象度で推論できるようになります。
しかし、エージェントシステムをエンジニアリングするための同等のものは存在しません。システムを構築するためには、エージェント・オーケストレーションは現代のソフトウェアとまったく同じ方法でプログラム可能である必要があります。
本日、私たちは PROGRAMS (*.program.ts)の仕様と、決定論的なエージェント・オーケストレーションのために構築された VM である Onyx を紹介します。この記事では、エージェント・オーケストレーションの歴史、プログラムを実行できる VM の静的およびランタイムセマンティクス、そしてこの分野が向かう先への影響について探求します。
高くつくように聞こえますが、実際はそうではありません。これについては後半の記事で説明します。
興味がある方のために、Andrej Karpathy の Autoresearch をプログラムとして表現すると次のようになります。

エージェント・オーケストレーションにおける未解決問題
エージェント・オーケストレーションのランタイムに何を含めるべきかを理解するには、エージェントの限界を理解する必要があります。
LLM エージェントは、パーサーに供給され、ツール呼び出しを環境にループでディスパッチする JSON ストリームジェネレーターと考えることができます。
すべてのツール呼び出しはまったく同じ外部スキーマ形状を持ちますが、この出力ストリームの内容は決定論的ではありません。
決定論と非決定論の組み合わせこそが、エージェントを非常に価値あるものにしてきました。エージェントは、独自の方法でアクションのシーケンスを連鎖させるのに十分柔軟でありながら、ツール呼び出しを通じてコンピューターと対話するのに十分決定論的です。
そのストリームの内容が型付けされているという要件を手放す気になれば、構成可能性はほぼ無料で手に入ります。モデルは、私たちが提供するレール(プロンプト、メッセージ、ツール呼び出し)内でテキストを出入力するのに十分な性能を持っています。
これにより、非常に構成可能なインターフェース、テキストが明らかになります。
テキストは普遍的なインターフェースです。コンピューター上のすべては、たとえ機械語であってもテキストにシリアライズできます。LLM がこの普遍的なインターフェースを通じてテキストを入出力できるようにすれば、テキストストリームを介した構成可能性が得られます。
つまり、エージェントの動作の信頼性は、モデルからの出力の一貫性に直接関係するということです。出力の変動性が高いと、エージェントの動作はより不安定になります。
ピースを構成するためのインターフェースを手に入れたら、次に気にするべき制約はステアラビリティ(誘導可能性)です。
エージェントに何をさせたいか、そして望むことを一貫して実行させる方法
私たちは、エージェントがサンプリングする分布をシフトさせることでエージェントを誘導します。言い換えれば、プロンプティングです。
2022 年、ReAct が登場し、エージェントのステアラビリティを実質的に開拓しました。実際、ReAct が私たちの知るエージェントを世に広めたと言っても過言ではありません。ツールの出力について、次のステップに進む前に思考し推論することが、ループの一貫性を保つのです。

それでも、エージェントにはもっと賢くなることが求められました。@OpenAI の O シリーズモデルによって製品化されたテスト時計算スケーリングの使用により、モデルラボはより優れたエージェント動作を組み込むことが可能になりました [[11]](http://localhost:5173/blog/onyx#ref-11)。ツールを呼び出す前により多くのトークンを出力することで、モデルは推論出力長に制約されていた場合に陥っていたであろう出力分布から脱出できます。モデルがその出力分布のランドスケープをどのように移動するかをトレーニングすることを選択できるため、関心のあるタスクにおいてより明確なエージェント的な動作をトレーニングする自由があります。
コンテキスト長が際限なく増大するにつれて、エージェントの誘導は困難になり、タスク完了の可能性は低くなります。推論モデルを使用したとしても、回復の保証はなく、エージェントはその場で停止します。エージェントはコンテキスト制限に達するか、早期完了を宣言するか、ループに陥るなどする可能性があります。
非決定論的システムから保証を引き出す
これに対する解決策は多岐にわたりましたが、特筆すべきものが一つあります。それは @GeoffreyHuntley による Ralph Loop です。 [[3]](http://localhost:5173/blog/onyx#ref-3
彼は、エージェントの実行を制限し、その制限を使用してタスク完了について推論できるというアイデアを導入しました。これにより、Ralph Loop は魔法のようなことを実現します。非決定論的なシステムにおいて信頼できる何かを提供するのです。
決定論の一片。
スロットマシンのレバーをもう一度引くより、故障を保証し、正しいものへと徐々にラチェットを進める方が良いのです。この定義された境界は、推論するための具体的な何かを与え、何かの境界について推論できれば、そこからシステムを作り出すことができます。
コンテキスト長の限界との戦い
しかし、問題があります。新しいエージェントは実行間で一貫性を失いますが、単一のエージェントは十分な時間が経つとコンテキストを使い果たします。
ここで登場するのが @lateinteraction と @a1zhang による RLM です。RLM は、長いコンテキスト(つまりエージェントの実行)と構造化された方法で対話する方法の概念を提供しました [[4]](http://localhost:5173/blog/onyx#ref-4)。RLM は、コードを使用して操作をオーケストレーションすることを実証した 2024 年の論文である CodeAct に触発されました [[5]](http://localhost:5173/blog/onyx#ref-5)。エージェントは REPL 内で操作をオーケストレーションするスクリプトを書き、出力を取得します。RLM も同様に動作しますが、変数を使用してコンテキストを保存し、そのコンテキストに対して操作を実行できるという追加の特徴があります。さらに、REPL 内での再帰的な LLM 呼び出しも可能です。他のループが持つ反応性のいくつかを失うかもしれませんが、プログラムによってコンテキストを操作できるようになります。ここで重要なのは、REPL 内のスクリプトは一時的なものであるということです。 スクリプト実行ランタイムとコンテキスト管理は得られますが、再利用性や構成可能性はありません。スクリプトを書き、実行し、そして消えます。システム構築の観点では、これはエージェントとマークダウンファイルを bash スクリプトで連鎖させるよりも明らかに劣ります。永続性と制限された実行を失うからです。
個々のループからオーケストレーションのスケーリングへ
OpenAI の Deep research[[6]](http://localhost:5173/blog/onyx#ref-6) は、実行ごとに小さな変動を持つ一般的な実行形状またはスキーマを持つ決定論的ワークフローの初期の例の一つでした。その仕組みは、クエリのバッチを計画し、ウェブ上で実行し、結果をレビューし、次のクエリのバッチを計画するというものです。各バッチは、問題空間をより深く掘り下げていきます。

Cursor は、@wilsonzlin がブラウザを構築するためにエージェントをオーケストレーションするハーネスを実証した際に、決定論のアイデアをさらに推し進めました。彼は、並列プランナーエージェントとタスクエージェントを使用して大量の作業を調整するための特注のハーネスを構築しました [[7]](http://localhost:5173/blog/onyx#ref-7)。ここで関連するのは、ハーネスの各部分間の関係が固定されていることです。現在のシステム状態を探索しタスクを生成するプランナーと、タスクを受け取り並列で実装する実行者がいます。エージェント間には固定されたガードレールがあり、情報を伝達するための固定されたチャネルがあります。調整をうまく行うには、インターフェースに対する保証が必要です。
終了条件を使用した制限付き実行
5 月、Codex は goal のアイデアを導入しました。これは、タスクが完了するまで、望ましい最終状態に対して検証ループを使用して山登りを行うものです。これは、Codex に組み込まれた Ralph Loop のプロダクション対応版と考えることができます。目標を指定できるようにし、実行とレビューを自動化したループが組み込まれています。

Karpathy の autoresearch[[9]](http://localhost:5173/blog/onyx#ref-9) は、Codex の /goal や Ralph Loop と似ています。goal の検証可能な終了条件と、Ralph Loop のイテレーションによる実行制限を組み合わせることで、目標に向かって継続的に推進できるようにしています。アイデア空間を探索し、時間の経過とともに反復的に改善することで進歩します。

ここまでは、エージェントの外部でオーケストレーションを外部化するすべてのソリューションは、その実行グラフの形状が固定されていました。それらは手書きのパターンを使用して実行され、操作できる形状にある種のスキーマを持っています。タスクごとに適応したり、実行グラフの形状そのものに対する強力な保証を提供したりするわけではありません。
オーケストレーションを柔軟にする
今年の 3 月、私たちは Slate を発表しました。これは、RLM のスタイルでライブサブエージェント・オーケストレーションにコードを使用した最初のコーディングエージェントです。現在でも、コードを使用してライブエージェント・オーケストレーションを行う、広く使われている唯一のコーディングエージェントです。Slate では、スレッドをリアルタイムで生成、一時停止、再開、誘導できます。メインエージェントは、実行中のすべてのサブエージェントをどのようにオーケストレーションするかを深く理解しているため、ユーザーが行う必要はありません。しかし、RLM と同様に、私たちは依然としてサブエージェント間での状態共有と一時的なスクリプト実行という課題に直面していました。これは、bash スクリプトとマークダウンファイルを使用していれば発生しなかった問題です。
それでも、モデルがオーケストレーションを行っている場合、どのようにそれを誘導すればよいのでしょうか?オーケストレーションコードを特定の方法で書くように指示しますか?どうすればいいのでしょうか?
私たちの初期の解決策(Onyx ランタイムをリリースする前のパッチとして)は、オーケストレーションスキル と呼ばれるものでした [[13]](http://localhost:5173/blog/onyx#ref-13)。アイデアは単純で、エージェントがオーケストレーションにどのようにアプローチするかを誘導するスキルをユーザーが提供できるようにするというものでした。それだけです。ある程度は機能しましたが、多くの問題がありました。
つまり、スキルは拘束力のある行動契約ではありません。テキストから保証を得ることはできません。
つまり、オーケストレーターは、それを強制する実際の方法がないため、望ましい実行パターンに従う必要はありませんでした。Onyx ランタイムの最大の利点の一つは、この問題を解決したことです。
これまでに言及されたシステムのいずれも、拘束力のある行動契約を持っていません。
では、タスクごとにエージェントがオーケストレーションコードをスクリプトに書き、実行グラフが固定されるようにしたらどうでしょうか?これが Claude dynamic workflows です。[[10]]([http://localhost:5173/blog/onyx#ref-10)[[12]](http://localhost:5173/blog/onyx#ref-12](http://localhost:5173/blog/onyx#ref-10)[[12]](http://localhost:5173/blog/onyx#ref-12) RLM や Slate と同様の方法で、サブエージェントをオーケストレーションするコードを書くことにより、dynamic workflows は Claude がワークフロー形状を書き、保存することを可能にします。これは /loop と組み合わせて、特定のパターンをループすることができます。一連のエージェントの動作に対する宣言的な契約を提供します。それでも、関数型構成などの機能が欠けているため、ソフトウェアを書くことと同じではありませんが、永続性とタスクの実行方法に対する強力な保証が得られます。これらは、特定のタスクのために临时に動的に書かれたワークフロースクリプトです。[[12]](http://localhost:5173/blog/onyx#ref-12) そして、ディスクに永続化されるため、もう一つの利点があります。再実行可能で、/loop のようなオーケストレーショングルーでラップできるということです。

お気づきかもしれませんが、上記のすべてのソリューションは同じものを目指しています。それは、エージェントが時間の経過とともにどのように実行されるかを制御する決定論的な方法です。
これは、ソフトウェアエンジニアリングという分野ですでに見られた物語です。私たちは、異種システムやスクリプトジョブを接着することから始め、その後、言語はより柔軟で強力になりました。より強力なエコシステムにより、より高い抽象度でより信頼性の高いシステムを構築できるようになり、エンジニアリングプロセスに対するレバレッジをますます得るようになりました。
現在、エージェントも同じ軌道にあり、本日、私たちはその軌道に沿った次のステップをリリースし、エージェントを実行するシステムをエンジニアリングできるようにします。プログラミング言語は、リソースを自動的にスケジュールするためにインタープリターや VM を使用することがよくあります。これにより、言語を使用するエンジニアとしてレバレッジを得ることができます。
エージェント・オーケストレーションに VM が理にかなっているなら、いくつかの要素が必要です:
- 永続的な状態管理: 状態を定義し、名前で参照し、永続化し、プログラム的に操作できる必要があります。
- 型の保証: 定義された入出力形状を尊重し、それに従い、それらに依存できる必要があります。
- 制御フロープリミティブ: できれば LLM が理解するよく知られたものであること。
- エラーハンドリングの明確な構造(例: try-catch)。
- リソース管理: エージェントの並列性、コスト、実行するモデルなど、リソースに対する定義された制御。
- 実行の分離: 特定の実行中のエージェントまたはプログラムは、状態が明示的に共有されない限り、別のものから分離されている必要があります。
- ライフサイクル制御: エージェントプログラムの外観と、実行、キャンセル、誘導のためのセマンティクス。これがなければ、クリーンアップへの明確な道筋がなく、ライフサイクル管理を制御できません。
- 構成可能性: プログラムは互いに構成可能であり、定義された入出力型で呼び出し可能である必要があります。
- 可視性: 何がいつ実行されたかを把握でき、ソース内の実行失敗をトレースバックできる必要があります。
- 耐久性: クラッシュからどのように回復し、再開するかについて明確なモデルが必要です。
これらの一つ一つは、数十年前にプログラミング言語によってすでに解決された問題です。エージェント・オーケストレーションは、それらすべてに再び初めて直面しているのです。
これを真にソフトウェアとして書くためには、"program.ts" プログラムは、上記のすべてをサポートするランタイムで作成されなければなりません。そうすることで、プログラムがうまくいかないときに何が起こるかを推論し、障害をエンジニアリングできるようになります。
これが、私たちが Onyx を構築した理由です。これは、永続的で構成可能なプログラムと解釈されるスクリプトレイヤーの両方をサポートするために設計されたエージェント・オーケストレーション VM です。ここでは、その仕組みと、"program.ts" 互換のランタイムがサポートする必要があるものについて説明します。
ランタイムの設計
言語とそのランタイムを設計する際には、推論したい制約と、簡単に表現できるようにしたいことについて考える必要があります。その後、結果として得られるセマンティクスを静的セマンティクスとランタイムセマンティクスの 2 つのカテゴリに分類できます。
静的セマンティクスは、プログラムを一目見ただけで推論できるすべてのことです。コンパイラや型チェッカーが特定のプログラムについて知っていることです。
ランタイムセマンティクスは、コードが実際に何を意味するか、そしてプログラムが実際にどのように実行されるかを定義します。これには、基盤となるリソース割り当てとスケジューリングメカニズムが含まれます。
エージェント用のランタイムの目標は、オーケストレーションの制御フローをコードに変換することであり、実行状態を永続的かつ型付けされたものにして、オーケストレーションの誘導に確実に使用できるようにすることです。
VM のいくつかの要件
通常の TypeScript 実行とは別に、私たちが気にする VM 固有のことが 3 つあります。
- エージェント・オーケストレーションランタイムとして、エージェントをオーケストレーションできる必要があります。これは、エージェントの作成、ライフサイクルの追跡などを意味します。ランタイムがエージェントをブロッキングまたは非ブロッキングで実行し、正しくスケジュールできるようにしたいと考えています。
- エージェントの出力形状を制御し、厳格な出力契約の強制を望みます。
- モデルやコストなどの外部リソースをランタイムで制御できるようにしたいと考えています。
エージェントとプログラムの実行
エージェントを実行するために、私たちは run と spawn という 2 つの基本的な動詞を選択しました。Run はブロッキングエージェントをフォアグラウンドで実行します。Spawn はエージェントをバックグラウンドで実行します。これは posix_spawn のような spawn の一般的な理解と一致しており、モデルが新しい動詞を理解しやすくなります。概念的にはトレーニングデータに含まれているからです。Spawn と run を使用すると、ディスクから読み取られたエージェントとプログラムを直接呼び出すことができ、実行ハンドルに十分な情報を返します。
Run はいくつかのこともサポートしています。zod @colinhacks を通じて直接強制される出力型をサポートし、直接モデルオーバーライドもサポートするため、タスクの異なるソリューションや異なるステップのために複数の異なるモデルにファンアウトすることが理にかなっているプログラムを簡単に作成して実行できます。
1function run<S extends z.ZodType>(2 name: string,3 options: ...4): Promise<z.infer<S>>
Run を使用すると、サブエージェントをインラインで直接連鎖させることができます。
1// プレーンなエージェント実行const out = await run({ type: "read", prompt: () => "Reply with: ok" })2// 名前付き実行(文字列 = 子ワークフローID)3const review = await run("reviewer", {4 type: "general",5 prompt: () => "Review the diff",6})7// 構造化出力(型付けされた結果)8const Verdict = z.object({ risk: z.enum(["low", "high"]), why: z.string() })9const v = await run({10 type: "general",11 prompt: () => "Assess risk",12 output: Verdict,13})
Spawn は run と似ていますが、バックグラウンドでエージェントを作成します。スポーンされたサブエージェントは待機されず、制御フローはそのまま進みます。Spawn は、いくつかの非ブロッキング実行エージェントを起動するのに非常に便利です。
1// バックグラウンドエージェント2const h = await spawn("worker", { type: "general", prompt: "Long task" })
実行中のエージェントとの対話
実行中のエージェントに対して、誘導と停止の 2 種類の操作を実行できるようにしたいと考えています。
誘導メッセージは、実行中のエージェントに送信されるメッセージで、LLM が受信して方向性を促します。これは、ワーカーを破棄することなくエージェントのタスクコンテキストを更新するのに便利です。
キャンセルも重要です。実行すべきではないサブエージェントを積極的に破棄できる必要があります。
ライブ REPL と事前作成されたプログラムの両方からこれらの操作を実行できることで、Slate はすべてをリアルタイムでオーケストレーションする能力を得ています。実行時にオーケストレーションの形状を動的に定義することも、オーケストレーションのために実際のソフトウェアを作成して反復することもできます。
Slate は、\.program.ts ファイルにプログラムを書き込むことができます。プログラムファイルにはいくつかの要素があります。その名前(これが Slate がそれが何かを知る方法です)、JSDoc 説明、そして実際のプログラム本体*です。プログラムの宣言は次のようになります。
1program(async (ctx) => {2 // 検索用の安価なモデル — ファイルを見つけるだけでよい3 const findings = await run("search", {4 type: "read",5 prompt: "Find all authentication-related files",6 model: "codex/gpt-4.1-mini", // 組み込みの codex キーを使用7 })8})
プログラムは同じ非同期実行モデルに従うため、プログラムをフォアグラウンドとバックグラウンドの両方で実行し、実行中に対話することができます。
1// バックグラウンドエージェント2const h = await spawn("worker", { type: "general", prompt: "Long task" })3await h.notify("focus on the parser first") // 実行中のエージェントへの誘導メッセージ4const result = await h.result() // 後で完了を待機// ファンアウトして、収集5const a = await spawn({ prompt: "task A" })6const b = await spawn({ prompt: "task B" })7const [ra, rb] = [await a.result(), await b.result()]// プログラムをバックグラウンドで実行8import Audit from "deep-audit"9const ah = await spawn(Audit, { input: { pr: 42 } })10const auditResult = await ah.result()
構造化出力と状態
これは、これまでの他のすべてのシステムの主要な制限です。状態は、他のすべてのシステムでは外部化が不十分であり、安全に分離されていません。システム上のファイルである場合、破損がないことを保証できません。できたとしても、解析可能性を保証できません。状態変更をサブスクライブして操作を駆動することはできず、型準拠を保証することもできません。
前に、永続的で構造化され、参照可能な状態を望んでいたことを覚えていますか?
Onyx での状態は異なります。状態名前空間は宣言され、直接名前が付けられ、時間の経過とともに永続化されます。つまり、状態ストアは何度でも再利用でき、実際のデータを使用して長時間実行されるエージェントシステムを構築できるのです。
エージェントとコードの両方が状態を読み取り、私たちがランタイムに望んでいた決定論はこれから導き出されます。エージェントは専用のツールを通じて状態を読み取り、常に安全に構造化された方法で状態と対話できます。エージェントとプログラムはどちらも、状態を変更するように誘導できるコンシューマーであり、ランタイムは状態オブジェクトに依存してオーケストレーションを駆動できます。
状態とスキーマ準拠がサブエージェントの完了をゲートします。このため、状態はプログラム全体を誘導するための統一されたサーフェスを提供します。
状態オブジェクトは、メインエージェントと共有される子セッションにランタイム変数として渡すこともできます。エージェント階層全体を通じたこの参照渡しアクセス(これ自体が初めてのことです)により、共有状態チャネルを介したエージェント間通信が可能になります。

長時間実行ループ
プログラムの中には、実行中のシステムのように機能する必要があるものがあります。例えば、openclaw を考えてみてください。適切なプリミティブがあれば、openclaw をプログラムとして表現することも実際に可能です。このために、sleep と checkpoint という 2 つのプリミティブを使用します。
Sleep は、期待通りにスリープします。
ここで重要なのは、バックグラウンドで長時間実行されるタスク管理をしたいとします。事前定義された実行グラフはスタックしたり壊れたりする可能性があるため、メインエージェントがプログラムのステータスを認識していることが重要です。
これをサポートするために、checkpoint プリミティブを導入します。
Checkpoint は何でも構いませんが、checkpoint と名付けられている理由は、固定された形状のオブジェクトでメインエージェントに通知するからです。これにより、メインエージェントはタスクの進捗状況などを追跡し、プログラムの状態変更を直接通知されるようになります。その結果、メインエージェントは実行中のプログラムをより効果的に管理できるようになります。
Onyx は、Openclaw のようなエージェントループ、つまりハートビートを持つ永続的なエージェントを作成することをサポートしています。
これは実際、とてもクールです。while ループ、スリープ、チェックポイント を使うだけで、プリミティブを組み合わせてまったく異なるタイプのエージェントを作成できます。
Openclaw は、単にプログラムファイルとして表現できます!
1// 長時間実行される自動研究ループを実行するためのプログラム2for (let i = 0; i < maxExperiments; i++) {3 const idea = await run("propose", { ... })4 const result = await run("train", { ... })5 checkpoint({ message: `実験 ${i}`, data: { idea, result } })6 await sleep(30_000) // 実験間のクールダウン7}89// Openclaw スタイルの永続エージェントを実行するためのプログラム10while(true) {11 const status = await run("status_check", { ...insert cheap model here... })if(status.pending_tasks) {checkpoint({ tasks: status.pending_tasks }) // 重要な状態を返し、メインエージェントを起動する}12 await sleep(30_000) // 実験間のクールダウン13}
合成 (Composition)
Onyx があれば、Slate はあなたのために *.program.ts を書くことができます。これは永続化され、通常のコードとまったく同じように扱うことができ、またそうすべきです。標準で型が付属しており、ランタイムグローバルが取り除かれたランタイムで動作します。そして、それは単なる TypeScript なので、その合成モデルは別のプログラムをインポートして呼び出すだけです。
TypeScript であるため、並列処理 (Promise.all) やループなどを無料で手に入れられます。
あるプログラムをインポートして別のプログラムで使用する方法は以下の通りです:
1import Audit from "deep-audit"program (() => {const ah = await spawn(Audit, { input: { pr: 42 } })2 const auditResult = await ah.result()3 const fixer = await run("fixer", ... 監査出力) // これが実行され、監査プログラムの出力を修正します。4})
エラー意味論 (Error Semantics)
理想的な VM では、エラーは大声でスローされます。これらは、ランタイムの構文問題、エージェントの障害、クラッシュなどに対してスローされるべきです。
具体的には、オーケストレーションエラーを以下のように定義します:
- エージェントがタスクでブロックされている
- エージェントがタスクの完了に失敗した
- エージェントがタスクのステップ数または予算を使い果たした
- プログラムが実行の予算を使い果たした
- オーケストレーションモデルが構文的に正しいコードを書けなかった
- 不正な状態変更が行われた
これらすべての具体的なエラーケースは、実行時意味論を定義します。つまり、「エージェントの実行失敗をコードのエラーと同じように見なすので、このランタイムがスローすることを期待できる」ということです。最初は面倒に思えるかもしれませんが、この騒々しい失敗メカニズムは、あなたに何かを返してくれます:それは、失敗に備えて計画し、プログラムするための明示的な方法です。つまり、実際には制御を減らすのではなく、増やしてくれるのです。
1// エラーは try/catch — 他の TypeScript プログラムと同じ2program(async (ctx) => {3 try {4 const result = await run("risky-refactor", {5 type: "general",6 prompt: "認証モジュールをリファクタリングしてください",7 model: "claude-sonnet",8 maxSteps: 20,9 })10 } catch (err) {11 // エージェントは失敗した — しかし、理由は正確にわかっている。12 // トレースには、ここに至るまでのすべてのツールコール、すべてのモデルリクエスト、13 // すべての状態書き込みが含まれている。1415 // 別のモデルでリトライ16 const result = await run("risky-refactor-retry", {17 type: "general",18 prompt: `前回の試行は失敗しました: ${err.message}。別のアプローチを試みてください。`,19 model: "claude-opus",20 maxSteps: 30,21 })22 }23})
モデル選択、予算執行、BYOK
モデル選択が組み込まれていることで、さらに精密な制御が可能になります。/models スキルにより、Slate は利用可能なモデルのリストに完全にアクセスでき、Slate は異なるジョブを実行する複数の異なるモデルを使用してプログラムを作成できます。Fable をプランナーにしたいけど、実装は GLM 5.2 に決定論的なハーネス内で行わせたい? もちろん可能です。質問を Gemini、GPT 5.5、DeepSeek に同時にブロードキャストしたい? それもできます。
さらに、ランタイムはプログラムに対して2種類の設定オーバーライドをサポートしています:
- エージェント実行に使用されるデフォルトのグローバルモデル
- プログラムを実行するための予算
特定のループの支出上限を設定するために、実行予算を直接設定できます。
さらに、ランタイムは既存の OpenAI および Github Copilot のサブスクリプションの使用をサポートしています。
1program(async (ctx) => {2 // 検索用の安価なモデル — ファイルを見つけるだけでよい3 const findings = await run("search", {4 type: "read",5 prompt: "認証関連のファイルをすべて見つけてください",6 model: "codex/gpt-4.1-mini", // 組み込みの Codex キーを使用7 })89 // 難しい部分には推論モデル — 考える必要がある10 const plan = await run("architect", {11 type: "general",12 prompt: `以下に基づいて修正を設計してください: ${findings.output}`,13 model: "openai/o3", // API クレジットを使用することになる14 output: z.object({15 approach: z.string(),16 files: z.array(z.string()),17 risk: z.enum(["low", "medium", "high"]),18 }),19 })2021 // 実装用の中級モデル — 編集するだけでよい22 const handles = await Promise.all(23 plan.files.map(f => spawn("fix-" + f, {24 type: "general",25 prompt: `この修正を ${f} に適用してください: ${plan.approach}`,26 model: "anthropic/claude-sonnet-5",27 maxSteps: 15,28 }))29 )30 await Promise.all(handles.map(h => h.result()))31})
オーサリングサーフェスの定義
プログラムのオーサリングサーフェスを設計する際には、主に2つの要素がありました:エージェントがそれを理解するのがどれほど簡単か、そして人間がそれを読むのがどれほど簡単か。私たちは、英語のように読める比較的単純な動詞を選び、オーケストレーションを宣言的にではなく手続き的にモデル化することを明示的に決定しました。
言語として TypeScript を選択したことも重要でした。世の中には手続き的な TypeScript コードが非常に多く存在するため、モデルは事後トレーニングがなくても TypeScript の意味論を暗黙的に理解します。

ソフトウェアファクトリーのエンジニアリング要素
次に答えるべき質問は、これらすべてがあなたに何をもたらすか、です。
それは、エージェントオーケストレーションのための実際のソフトウェアを書く能力をもたらします。エージェントオーケストレーション全体をエンドツーエンドでエンジニアリングできるようになります。
ファクトリー自体をエンジニアリングできるのです。
例えば、Github をループで監視するプログラムと、実装エージェントと QA エージェントによるレビューを実行する別のプログラムを作成できます。どちらも、世の中でよく見かける個別に有用なパターンです。そして、それらを組み合わせて、PR のコメントをリッスンし、それらのコメントに対処するための実装者を起動し、修正が有効であることを確認するための QA エージェントを起動するシステムを作ることができます。
このプログラムをタスクキューに接続して、コードベース上の作業を委任および監視し、PR コメントに自動的に応答させることもできます。
そして、これらすべてを高速なオープンウェイトモデルを使用して行うことができます。なぜなら、それは単なるコードであり、一度作成されたオーケストレーションを考えるために強力な LLM は必要ないからです。
ここからが楽しい部分です。私たちが大幅な出力増加のために使用してきたプログラムのいくつかを紹介します。
Deep Codebase Research
私たちはこのプログラムを使ってタスクの範囲を絞り込んでいます。これはモノレポの状態を深く調査し、実装者が参照するための調査パケットを準備します。私たちはこれを頻繁に使用しています。高くつくように聞こえますが、実際にはそうではありません。このプログラムを Slate 上で DeepSeek V4 Flash を使って実行でき、調査プロセスは徹底していますが、非常に安価です。

Goal-Review-PR
これは、調査が完了した後にタスクを実装するために使用するものです。幸いなことに、調査が Goal プログラムに到達する頃には、タスクの曖昧さのほとんどは解消されているため、タスクの実行がさらに高速化されます。軽量な OSS モデルで調査を前倒しすることで、本当に重要なこと、つまり実際に優れたコードを書き、システムの状態を検証することに、Opus のような高価なモデルを使用しやすくなります。プログラムを修正して、GPT 5.5 を使用して Opus 4.8 の作業を敵対的にレビューさせることさえ可能です。

プログラムとしての Autoresearch
Autoresearch[[9]](http://localhost:5173/blog/onyx#ref-9) は、もともとは完全に LLM 駆動型でした。エージェントを program.md のプロンプトに向けると、何を試すか、どのように進めるかをエージェントが決定します。
驚くことではありませんが、Autoresearch は実際には単なるプログラムです。
エージェントプログラムを使うと、これを逆転させて、制御フローをランタイムに置くことができます。プログラムが制御フローを所有し、エージェントは副作用を伴う作業(コードの編集、git の実行、リモート GPU への SSH、トレーニング)を行います。Autoresearch プログラムの場合、保持/元に戻す (keep/revert) の決定は決定論的なコードです:
1kept = status === "ok" && valBpb != null && valBpb < best
私たちの場合、プログラムはセットアップエージェントを実行して、新しいリポジトリを準備し、リモートの A100 に到達可能であることを確認します。セットアップが失敗した場合、型付けされた値に基づいてクリーンな終了コードで早期に戻ります。それ以外の場合は、実験ループに入ります。
各実験は新しいエージェントを取得します。エージェントには、現在の最良の設定と、以前のアイデアと結果の履歴が与えられるため、繰り返しを避け、保持されたものに基づいて構築できます。変更を提案し、train.py を編集し、コミットし、リモートマシンに rsync し、トレーニングし、結果を分類します。
エージェントとプログラムは状態を共有します。エージェントは状態にデータを書き込み、プログラムは制御フローのために状態を評価します。結果に基づいて、記録エージェントが results.tsv を更新し、プログラムが実験を破棄することを決定した場合は、オプションで実行をリセットします。これにより、git HEAD は常に実験ツリーの現在の最良のブランチを指すようになります。
注目すべき2つの中心的な違いがあります:1) これはプログラム内で実行されるため、実験ごとに新しいエージェントを生成できること、2) ライブなプログラム状態に基づいて、エージェントが実行すべきタスクを決定できることです。


そして、コードでは次のようになります:
1// ---------- プログラム ----------23program(async (ctx) => {4 const c = cfg(ctx.input)5 const total = ctx.input?.maxExperiments ?? 2067 const setup = await run("ar-setup", {8 prompt: setupPrompt(c),9 type: "general",10 maxSteps: 40,11 output: SetupResult,12 })13 if (!setup.ready) {14 return { aborted: true, reason: `セットアップ失敗: ${setup.note}`, setup }15 }1617 let best = c.baselineValBpb18 let bestCommit = setup.baselineCommit19 const history = []2021 for (let i = 1; i <= total; i++) {22 let exp23 try {24 exp = await run(`ar-exp-${i}`, {25 prompt: experimentPrompt(c, i, total, best, historyText(history)),26 type: "general",27 maxSteps: 80,28 output: ExperimentResult,29 })30 } catch (err) {31 // エージェントがエラー/ブロック — クラッシュとして扱い、リポジトリを最良の状態に復元して続行。32 exp = {33 description: `実験 ${i} エージェントエラー`,34 commit: "error",35 status: "crash",36 valBpb: null,37 peakVramMb: null,38 numSteps: null,39 exitCode: -1,40 retries: 0,41 note: String(err?.message ?? err).slice(0, 200),42 }43 }4445 const kept = exp.status === "ok" && exp.valBpb != null && exp.valBpb < best4647 await run(`ar-record-${i}`, {48 prompt: recordPrompt(c, exp, kept, bestCommit),49 type: "general",50 maxSteps: 20,51 output: RecordResult,52 })5354 if (kept) {55 best = exp.valBpb56 bestCommit = exp.commit57 }5859 history.push({60 idx: i,61 description: exp.description,62 status: exp.status,63 valBpb: exp.valBpb,64 kept,65 commit: exp.commit,66 retries: exp.retries,67 })6869 await checkpoint({70 name: `experiment-${i}`,71 message: `exp ${i}/${total}: ${exp.status}${kept ? " KEPT" : ""} val_bpb=${exp.valBpb ?? "n/a"} (best=${best})`,72 data: { i, total, status: exp.status, valBpb: exp.valBpb, kept, best, bestCommit },73 })74 }7576 const kepts = history.filter((h) => h.kept)77 return {78 baselineValBpb: c.baselineValBpb,79 bestValBpb: best,80 bestCommit,81 improvement: c.baselineValBpb - best,82 experimentsRun: history.length,83 kept: kepts.length,84 crashes: history.filter((h) => h.status === "crash").length,85 infraFails: history.filter((h) => h.status === "infra_fail").length,86 localRepo: c.localRepo,87 branch: c.branch,88 history,89 }90})
将来の作業
私たちがまだ定義していない残りの唯一の VM 要件は、プログラムの耐久性モデルです。プログラムを再開し、そのライフサイクルを処理するための正しいモデル、およびランタイム上でどのレベルの制御を公開すべきかは、現時点では不明です。
それ以外にも、さまざまなワークロードやタスク形状をサポートするために追加予定のエキサイティングな機能がたくさんあります。これにより、エージェントをより適切にオーケストレーションするための実際のソフトウェアを書くことができるようになります。多くのパターンは、人々が独自の創造的な方法でプログラムを使用することから自然に生まれてくると確信しています。
あなたが何を構築するのか、私たちは本当に楽しみにしています。
- RL チーム
参考文献
- Yao et al., "ReAct: Synergizing Reasoning and Acting in Language Models," 2022
- Geoffrey Huntley, "The Ralph Loop"
- Geoffrey Huntley, "everything is a ralph loop," 2026年1月
- Zhang, Kraska, Khattab, "Recursive Language Models," 2025年12月
- Wang et al., "Executable Code Actions Elicit Better LLM Agents," ICML 2024
- OpenAI, "Introducing Deep Research," 2025年2月
- Cursor, "Scaling Agents," 2026年1月
- OpenAI, "Using Goals in Codex"
- Andrej Karpathy, "autoresearch"
- Anthropic, "Introducing Dynamic Workflows in Claude Code"
- OpenAI, "Learning to Reason with LLMs," 2024年9月
- Anthropic, "A harness for every task: dynamic workflows in Claude Code"
- Random Labs, "Skill Chaining"





