紙の上でSDKのフォルダ構成をスケッチする
エージェントが構築するコードベースは、3か月でぐちゃぐちゃになるか、何年も一貫性を保つかのどちらかだ - そしてその違いは、1行が書かれる前に決まっている。RakuAIはエージェントが常に正しい場所に着地できるよう、先に箱を描いた。
外からは見えないが内側では構造を支えている、という種類の設計作業がある。コードベースのフォルダ構成はそのひとつだ。見た目はファイル管理に過ぎない。だが実際にはコードの組織図だ。早い段階で正しく決めておけば、その後2年間のあらゆるPRが正しい場所に収まる。早い段階で間違えれば、その後2年間のあらゆるPRが「どこに属するか」を議論しなければならなくなる。
この土曜日は、それを早い段階で正しく決めることに費やした。
構成が満たすべき条件
箱を描く前に、構成が満たすべき制約のリストを作った。
各デモが自分の居場所を持つこと。 先月仕様化した8つのデモはそれぞれ、コード・アセット・ドキュメントが一緒に住むフォルダを必要とする。どれか一つのデモを読む開発者が、それを理解するためにSDKの4つの異なる部分を飛び回らなければならないような事態は避けたい。
共有モジュールがデモから分離できること。 デモをまたいで再利用されるもの(HUDレンダリング、ジェスチャー入力、視線トラッキング、マルチプレイヤー同期、音声制御、ビジュアルエフェクト)は自分専用の場所に住み、公開インターフェースを通じてデモから消費される。
アセットはデモ単位ではなく種類ごとに整理されること。 体力バーのPNGはHUDウィジェットであって、アリーナデモのアセットではない。再利用可能なビジュアルアセットは共有アセットフォルダに住み、それを必要とするデモがそこにリンクする。
ドキュメントは説明対象と同じ場所に置かれること。 SDKのインストールドキュメントはSDKの隣に住む。デモガイドはデモの隣に住む。HUD APIのドキュメントはHUDモジュールの隣に住む。ドキュメントの見つけやすさは、ほとんどの場合ドキュメントの置き場所の問題だ。
設定は独立した関心事であること。 HUDの設定ファイル、デフォルトレイアウト、マルチプレイヤーのロール設定。これらはコードでも、アセットでも、ドキュメントでもない。それぞれ自分専用の居場所を持つ。
構成の中身
朝いっぱいのホワイトボード作業を経て、レイアウトは次のような形になった。
ルートフォルダはSDKそのものだ。ルートの下には次がある。
/apps/ はデモが住む場所だ。各デモがサブフォルダを持つ。ソロアリーナ用の arena_demo/。ARコーチング用の coach_demo/。HUDオーバーレイデザイナー用の hud_designer/。配信者モード用の streamer_demo/。店頭サービスオーバーレイ用の pos_demo/。ARコンパニオンHUD用の companion_demo/。ARエイムトレーナー用の aimlab_demo/。マルチプレイヤーHUD同期用の hudsync_demo/。各デモフォルダには、そのデモ固有のソース、設定、アセット、READMEが含まれる。
/modules/ は共有コードが住む場所だ。最初は6つのモジュール。すべてのデモが利用するHUDレンダラー。ほとんどのデモが利用するジェスチャー入力検出。それを必要とするデモが使う視線トラッキング。LANマルチプレイヤーのデモが使うマルチプレイヤー同期。ビジュアルエフェクト用のオーバーレイアニメーター。音声入力を受け付けるデモ向けの音声制御インターフェース。各モジュールは、デモがリンクする小さく安定した公開インターフェースを公開する。
/assets/ は共有アセットライブラリだ。HUDウィジェット(体力バー、弾薬カウンター、コンパスオーバーレイ)をPNGとSVGで。ヒット音、アラート音、ボイスオーバー用の音響効果。ジェスチャーが検出されたことをユーザーに示すジェスチャーアイコン。デモはアセットを複製するのではなく、ここにリンクする。
/docs/ はドキュメントの本拠地だ。SDKを紹介し、インストールの手順を案内するREADME。8つのデモそれぞれの実行方法を説明するデモガイド。HUDモジュールのAPIリファレンス。音声制御モジュールのAPIリファレンス。ドキュメントは意図的に説明対象と同じ場所に置かれており、HUDの仕組みを知りたい開発者はソースとドキュメントを同じフォルダで読める。
/config/ は設定だ。新規プロジェクトの出発点として使われるデフォルトのHUDレイアウト。HUDデザイナーからエクスポートされたサンプルレイアウト。マルチプレイヤーのロール定義。設定をコードから分離しておくことで、デモをクリーンに保ちつつ、開発者は再コンパイルなしに挙動を変更できる。
スタブ
フォルダ構成それ自体は空のファイルシステムに過ぎない。今日もう一つ下書きしたのは、エージェントが(この構成に対して実際にコードを書き始めるとき)各エントリーポイントで実際の実装がどう見えるべきかを示す、一連のコードスタブだ。
スタブは今のところPythonの疑似コードだ。エンジンのリポジトリが開いたら、本番言語(ランタイム用のC++、そしてPythonをはじめとする他のエコシステムへのバインディング)に翻訳される。Python形式にしているのは、実装の細部に迷い込むことなくインターフェースについて考えられるからだ。
ジェスチャー入力モジュール用のサンプルスタブは、おおよそ次のようなものだ。
def detect_gesture(frame):
"""Detect gestures like swipe, point, raise, duck in a single sensor frame."""
if is_swipe(frame):
return "swipe"
elif is_raise(frame):
return "raise_arm"
else:
return None
これは本番コードではない。契約だ。本番コードは is_swipe や is_raise を実際のコンピュータビジョンパイプラインに置き換える。インターフェースは変わらない。シグネチャこそが、デモがそれに対してコードを書く対象だ。シグネチャを早期に固定するということは、CVパイプラインが完璧になる前にデモを出荷できるということだ。なぜならデモは実装ではなくインターフェースに依存しているからだ。
/modules/ フォルダ内のすべてのモジュールについて、この形のスタブを下書きした。6つの小さなPythonファイル。どれも何も実装していない。すべてが、いずれ実装されるものが従うべき契約を定義している。
なぜエージェントにこれが必要なのか
これは数週前のエージェント編成の話に戻ってくる。
実際のコードを書く時が来たとき、エージェントはアーキテクチャを発明するわけではない。アーキテクチャは紙の上にすでにある。フォルダ構成が、新しいコードをどこに置くべきかを教える。モジュールスタブが、どんなインターフェースを実装すべきかを教える。アセットの整理が、必要なものをどこで探すべきかを教える。ドキュメント構成が、書いたドキュメントをどこに置くべきかを教える。
それが、エージェントが構築したコードベースが3か月でぐちゃぐちゃになるか、何年も一貫性を保つかを分ける違いだ。構造こそが規律だ。エージェントは構造を通じてその規律を受け継ぐ。
この準備作業がなければ、「ジェスチャー入力を実装して」と頼まれたエージェントはフォルダを発明し、APIを発明し、コードを書き、単体では動くが孤立した何かを生み出すだろう。それが6つのモジュールにわたって繰り返されれば、互換性のない6つのミニエンジンが生まれる。構造は、エージェントが書き込むべき箱を先に描いておくことで、その失敗モードを防ぐ。
この作業の難しいところ
いくつか正直な話をしておく。
フォルダ構成は肥大化したがる。 半年後には7つ目のモジュールを追加したくなるだろう。構成は、ガラクタ入れの引き出しにならずにそれを許容できなければならない。自分に課しているルールはこうだ。新しいトップレベルのフォルダは、既存のものの下位フォルダでない理由を正当化しなければならない。新しいものの大半は、結局はサブフォルダであることが判明する。
スタブには保守が必要だ。 実際の実装が入ってくるにつれ、スタブは古くなっていく。スタブを「契約」として読むエージェントは、スタブと実際の実装のあいだのズレをそのまま受け継ぐことになる。修正策は、スタブを正とし、契約が変わるときは実際の実装がそれを更新することを義務付けることだ。
これらのフォルダの一部は仮説にすぎない。 /config/ フォルダは、ランタイム設定ファイルがこのSDKにおける本物のアーティファクトのカテゴリになる、という賭けだ。もしそうならないと判明すれば、フォルダは縮小するか、何か別のものに統合される。それでかまわない。慣習がすでにずれてしまった1年後にフォルダを発明するより、後で取り除くほうが安く済む。
次にやること
次の土曜日はAPI表面の設計だ。上記の6つのモジュールそれぞれについて、公開インターフェースを仕様化する必要がある。それは、どんなデータ型が境界を越えるか、どんなエラーコードが返るか、どんなイベントが発行されるかを決めることを意味する。フォルダ構成が終わったことで、API設計には着地する場所ができた。
その後には、各デモがどのモジュールをどの順序で消費するかというデモ単位の仕様策定が続く。それからランタイムアーキテクチャ。そして最後に、ようやくエンジンのリポジトリが開く。
特許ポートフォリオは10年待ってきた。それを正しく着地させるためのあと数週間の設計作業が、この機を逃す原因になることはない。
フォルダ構成は完了した。コードはまだ1行も入っていないが、コードベースには今、形がある。
足元が揺らがないSDK
変わることのないフォルダ構成、実装の前に固定されたAPI表面 - 長期戦のために設計された空間ランタイムの上に構築しよう。