WippyがLuaを採用する理由

技術評価を行う人は必ずこの質問をするため、ここで直接お答えします。

ランタイム要件

Wippyはユーザー定義のロジックを隔離されたプロセス内で実行します。各プロセスは独自のメモリ空間と、利用可能なケイパビリティのセットを持ち、ランタイムが明示的に許可しない限り、その境界の外へ到達する手段を持ちません。プラットフォームは単一インスタンス上で数千のプロセスを同時に実行し、それぞれが異なるテナントの異なるコードを実行する可能性があります。

つまり、各プロセスに組み込まれる言語ランタイムは次の条件を満たす必要があります。

  • 極小であること。 各プロセスは独自の隔離環境で動作します。数千の同時プロセスにおいて、プロセスあたりのメモリは重要です。Wippyはプロセスあたり約13 KBのベースラインオーバーヘッドを目標としています。
  • 完全にサンドボックス化できること。 ランタイムは、各プロセスがアクセスできるモジュール、関数、システムコールを正確に制御しなければなりません。アンビエント権限は存在しません。プロセス間でグローバル状態が漏れることもありません。
  • 組み込み可能であること。 言語ランタイムは、Wippyのコア(Goで記述)がプロセスごとにインスタンス化し、設定し、破棄できるライブラリでなければなりません。外部プロセスや別のバイナリであってはなりません。
  • モジュールロードが決定論的であること。 プロセスが起動するとき、そのプロセスが見えるコードはランタイムが決定します。ファイルシステムへのアクセスはありません。任意のパスに到達するrequireもありません。依存関係はレジストリから来るもので、プロセスごとにスコープされます。
  • LLMに優しい構文であること。 エージェントはコードを生成し、変更します。言語は、LLMが構文を幻覚することなく確実に読み書きし推論できるほど単純でなければなりません。

評価した言語: Python、JavaScript、Go、WASM

Python

AIワークロードにおける定番の選択肢です。除外した理由は、CPythonのメモリフットプリントがインタープリタあたり10〜30 MBであり、Luaプロセスより桁違いに大きいためです。Pythonのインポートシステムは、コードにファイルシステム、ネットワーク、OSへのアンビエントアクセスを与えます。Pythonのサンドボックス化には、WASMへのコンパイル(ほとんどのライブラリが動作しなくなる)か、インタープリタへの大掛かりなパッチ適用のいずれかが必要です。Pythonの並行モデル(GIL)も、私たちのプロセス単位の隔離モデルと衝突します。エコシステムは単体スクリプトにとっては強みですが、コードがアクセスできる範囲を決定論的に制御する必要があるサンドボックスランタイムにとっては負債です。

JavaScript (V8/QuickJS)

V8は高速ですが巨大です(アイソレートあたり数十MB)。QuickJSは組み込めるほど小さいものの、JavaScriptのプロトタイプチェーンと動的モジュールシステムは、見た目以上にサンドボックス化を難しくします。importrequireはファイルシステムに到達したがります。エコシステムはnpmを前提としており、npmはネットワークアクセスと書き込み可能なファイルシステムを想定していますが、Wippyプロセス内にはそのどちらも存在しません。プロダクトを作るよりも、言語の前提と戦う時間のほうが長くなってしまうでしょう。

Go

WippyのコアはGoで書かれているため、これは魅力的な選択肢でした。しかしGoは組み込めません。別のGoプログラムの内部で、Goランタイムをライブラリとしてインスタンス化することはできません。Goプラグインは存在しますが、壊れやすく、ホストプロセスとメモリを共有し、サンドボックス化できません。Goはランタイム自体には適していますが、ユーザーコードには適していません。

WASM

サンドボックス化には本当に強力であり、私たちはこれをWippyの第2のランタイムとして構築しました(後述)。しかしWASM単体では、エージェント開発の主要言語としては不十分です。WASMを直接記述しデバッグする開発者体験はまだ荒削りであり、LLMはLuaを生成するときほど確実にWASM向けコードを生成できません。WASMは、他の言語でコンパイルされたコードをWippyサンドボックス内で実行する必要があるときの正しい選択です。Luaは、主要な開発体験およびエージェントによるコード作成体験にとっての正しい選択です。

Luaが5つの要件すべてを満たす理由

Luaはまさにこのユースケースのために作られました。プロダクションで最も多く組み込まれているスクリプト言語であり、World of Warcraft、Roblox、Redis、Nginx/OpenResty、CiscoとJuniperのネットワーク機器、Adobe Lightroom、そして数百のゲームエンジンの内部で動作しています。25年以上にわたり、敵対的な環境(ユーザーが信頼できないコードを実行するゲーム)に組み込まれてきました。

メモリ

WippyのLuaプロセスのベースラインオーバーヘッドは約13 KBです。10,000個の同時プロセスであれば、ベースラインのプロセスオーバーヘッドはおよそ130 MBです。Pythonでは同じ数で100〜300 GBを要します。これは理論上の懸念ではなく、単一マシンで動かせるか、クラスタが必要になるかの違いです。

サンドボックス化

Luaのモジュールシステムは、ホストが完全に制御する単一の関数(require)です。これをカスタムローダーに置き換え、そのプロセスに付与されたものだけを解決するようにすれば、プロセスは許可したものしか見えません。import ossubprocessも、アンビエントなファイルシステムアクセスもありません。それらの関数はプロセスの環境に存在しないのです。サンドボックスは既定の状態であり、開かれたシステムの上に当てるパッチではありません。

組み込み

Luaのインターフェースは小ささで有名です。標準的なC APIは約60関数であり、純Go実装によってWippyのGoコアへの組み込みはcgoなしで容易になります。プロセスのLua環境の生成と破棄は安価であり、Wippyはプロセス起動のたびにこれを行いますが、計測可能なオーバーヘッドはありません。

決定論的なモジュール制御

Wippyでは、プロセスがロードできるコードはそのレジストリスコープによって決まります。Luaローダーはファイルシステムではなくレジストリからモジュールを解決します。プロセスにモジュールが付与されていなければ、そのプロセスの視点からはそのモジュールは存在しません。これがコードレベルでのマルチテナント隔離の仕組みです。テナントごとに利用できるモジュールを変えられ、それはアプリケーションロジックではなくランタイムによって強制されます。

LLMに優しい

Luaの構文は最小限です。クラスもデコレータも、言語に組み込まれた型注釈も、async/awaitも、複雑なモジュール解決もありません。Luaを見たことのあるLLMは、(デコレータパターン、コンテキストマネージャ、型システムを持つ)Pythonや、(プロトタイプチェーン、thisのバインディング、複数のモジュール形式を持つ)JavaScriptよりもはるかに高い確率で、一度で正しいLuaを生成できます。エージェントが自らのツールを記述し変更するプラットフォームにとって、これは重要です。WippyはLuaを型注釈システム(ジェネリクス、ユニオン、チャネル型)と組み込みリンターで拡張しているため、構文の複雑さなしに型安全性が得られます。

コルーチン

Luaはネイティブのコルーチンをサポートしており、これはWippyの並行プロセスモデルに直接対応します。各プロセスはスケジューラにyieldするコルーチン内で動作します。スレッドはありません。ロックもありません。プロセス間の競合状態もありません。数千の同時プロセスが、スレッドベースの並行性の複雑さなしに協調します。

失うもの

Luaのエコシステムは小規模です。数万のパッケージを擁するpipやnpmに相当するものはありません。これは意図的です。Wippyでは依存関係とは、宣言されたケイパビリティとセキュリティポリシーを持つレジストリエントリであり、インターネットから取得される任意のパッケージではありません。しかしそれは、Wippyプロセス内でpip install pandasができないことを意味します。重厚なライブラリのサポートを必要とするデータ処理(MLモデルの推論、複雑な数値計算)は、Wippyエージェントがツール経由で呼び出す外部サービスとして実行するか、Wippyサンドボックス内のWASM関数として実行すべきです。

Luaはまた、ほとんどの開発者にとって馴染みがありません。学習曲線は実在しますが短く、Luaの言語リファレンス全体でおよそ30ページです。何らかのプログラミング言語を知る開発者のほとんどは、1日でLuaを書けるようになります。馴染みのなさは摩擦コストですが、ユーザーコードの多くが短く、ツール志向で、ますますAIが生成するものになっているランタイムプラットフォームにとっては、アーキテクチャ上の利点(サンドボックス化、メモリ、組み込み)がそれを上回ります。

Lua + WASM: 全体像

WippyはLua専用のプラットフォームではありません。2つのランタイムを提供します。

Luaは、エージェント開発、ツール作成、アプリケーションロジックのための主要なランタイムです。Wippyのコードの大半が書かれ、エージェントがコードを生成する場所です。小さなフットプリント、完全なサンドボックス化、LLMに優しい構文が、これを正しいデフォルトにしています。

WASMは、コンパイル済みワークロードのための副次的なランタイムです。Rust、Go、C、あるいはWebAssemblyにコンパイルできる任意の言語による既存コードがあれば、Luaと同じプロセス隔離とレジストリ統合のもとでWippy内で実行できます。WASMの関数とプロセスは、クロック、I/O、ファイルシステム(マウントされたWippyファイルシステムエントリ経由)、環境へのアクセスのためにWASIと統合されます。つまり、既存のビジネスロジックをLuaに書き換えることなくWippyサンドボックスに持ち込めます。

2つのランタイムは同じプロセスモデル、同じレジストリ、同じセキュリティポリシーを共有します。LuaエージェントはWASM関数を呼び出せます。WASMプロセスはレジストリを通じてLua関数を呼び出せます。両者は同じシステム内の対等な存在です。

関連項目