- 澁川喜規
- 約 5,000 文字
夏休みの課題ということで、これまでの業務経験、Real World HTTP、Goならわかるシステムプログラミング、実用Go言語、お客様と一緒に勉強したReact/Next.jsの知識を全部詰め込んだ、自分史上最高のウェブアプリケーションサーバーを作ってみました。
http.ServeMuxとhttp.HandlerFuncといったいつものnet/httpに薄く載った入力のパースとバリデーションの関数、ORM、HTMLのテンプレート機構・・・なんですが縁の下のコード生成を駆使して実行時はreflectionフリーでTinyGoでもほぼ行けて標準的なnet/httpよりも高速。さらに同一コードでバックエンドをより高速なfasthttpにもできたり、OpenAPIも出せるし、Next.jsのRSCのストリーミングやそれをさらに発展させた機能とかも全部詰め込みました。セキュリティも現代的なものをどんどん積んでいます。でも開発時は内蔵OIDC IdP、オンメモリOpenTelemetryビューアで外部サービス立てずに開発開始、DBマイグレーション、シードデータ投入やDBバリデーション、VSCode拡張、AI向けskill完備です。なにせ自分史上最高なので。TinyGoで動かないライブラリは互換ライブラリを再実装。Go 1.27のジェネリックメソッドもAPIに早速活用していますが、Go 1.27相当のTinyGo 0.42が出たら1.0にしようかと思っています。
// いつものnet/http |
2ヶ月ほど前にAI時代の設計書についての記事を書きました。
それから色々試行錯誤して、これのバージョンアップ版のスキルを作ったうえでこれも含めて大きめのプロジェクトいくつかで使ってみました。
GoogleからOKFというフォーマットが発表されました。ナレッジをmarkdownで記述するためのオープンなフォーマットです。AIエージェントが読んだり書いたりしやすい、Git管理可能なWiki/軽量ナレッジグラフの共通仕様です。
と言ってもこの仕様はとてもシンプルで実際に適用するにはかなり部分を自分で考えなければなりません。最近0.2が出て、「誰が・いつ」という情報を持てるようになりましたが、基本的には
- コンセプトという小さい単位のMarkdownファイルの集合で知識を表現
- typeというfrontmatter(yaml等で表現するファイル先頭のメタデータ)で種別を表す(必須の情報はこれのみ)
OKFはオントロジーではなくそれを目指していません。そのため、どのようなタイプを定義すべきという指標はOKFにはありません。自分で考えなければならないと書いたのはそのあたりです。
前に作ったspec compilerをベースに、OKFのように「より小さなドキュメントに分割」というのも取り入れて作ったのが新しいスキルです。markdownでA4に印刷すると20ページも30ページにもなるようなドキュメントよりも、2-3ページの小さいドキュメントの集合として表現すれば一気の読み込むことが減り、コンテキスト消費も減るし、解読のリードタイムも減ることが期待できます。
OKFとの違い
タイプの種別をある程度決めました。knowledge compilerはより抽象的な小さなブロックになりました。
- objective:
- vocabulary:
- concept:
- behavior:
- interface:
- structure:
- constraint:
- external:
APIもUIもイベントもすべてinterfaceの1形態という扱いになりました。AIネイティブ時代の設計書を考えるで作ったspec compilerは人間の読み書きする設計書をベースにしていました。これと比べるとさらに抽象化されて細分化されました。↓これが前のやつですね。
- API
- UI Screen
- Data Model
- Component
- Business Rule
あと、Codexと議論しながら方針を決めましたが、構造化データとしてAIが素早く読み込んで解読できるのはMarkdownよりもYAMLの方が良いということで、コードブロックがあって本文はほぼYAMLという構成にしました。こんな感じの構成です。もはやMarkdownは形だけですね。
|
Markdownに関してはちょうど鹿野さんも書いていました。
Markdownは「構造化文書」じゃないよ、という派閥に所属しています https://t.co/sXxFQv3z5R
— 専門性・売上・原稿 (@golden_lucky) August 21, 2026
ページ間の遷移はインデックスを使ってたどります。インデックスはDuckDBが直接読み書きできるjsonlフォーマットで作っています。jsonlフォーマットを作るのはPythonスクリプトです。このあたりは前回の仕組みの踏襲です。YAMLの読み書きは組み込みライブラリではできないのでskillsフォルダにバンドルして配布するようにしています。
ちょっと大きめのシステムだとコンセプトのファイルが600、インデックスが6000とかになりますね。
ワークフロー
前回のSpec Compilerもあまり出来上がったファイルを直接読むことはありませんでしたが今回はさらに細かく分割されているので人間が直接読むことは完全にありません。
何か機能を追加したくなったら、まずはAIにknowledge compilerを使って要件を整理するようにお願いします。この時の入力はチャットの会話のこともあれば、markdownでまとめた要件だったりします。OpenAIのCodexはPlusユーザーなどでは制限は厳しめですが、ChatGPTはそちらの枠とは別枠で使えたりするので別途仕様の会話をし、まとまってきたらmarkdownで出力してから渡す、みたいな方法とかが節約になって良いですね。
たとえ入力がmarkdownでも、それがコンセプト単位に分解されて格納されます。あくまでも整形されたMarkdownはシステム間(人間やAI)のやりとりのための中間形式、という感じです。保存もしません。コードもここで貯めた知識を元に実装をお願いします。
フィードバックもknowledge側も同期を取るようにしてもらいます。この知識を元にシステムやドキュメントを作ったりします。これをみてまたknowledgeにフィードバックをするという流れです。いくらAIがソースコードを読解する能力があるからといって、少ないコンテキスト数で素早く場所を特定して解読してくれるわけではありません。ソースをSSoTにしようとするとコンテキスト消費はうなぎ登りです。既存のコードを元にする場合も一度knowledgeを書き起こしてもらってから対応します。
人間が読み書きするドキュメントやソースコードを中心としたフローやソースコードを中心とすると(繰り返しになりますが)、やはりスキャン速度が遅い、メモリも少ない人間に合わせたフォーマットなのでAIの効率も高くとはいえません。やはり今後はAI中心にしていきたいですね。
エージェント間の互換性はどうか
作ったナレッジは、現役のモデルやツールはどれも問題なく対応できました。CodexのGPT-5.6、Claude Code(Opus5, Fable5)、GitHub Copilot (Opus 4.6とか)、Grokどれも問題なく動きました。Grokで土台を作ったライブラリをCodexで完成させるとか、Codexでベースの設計をして作ったknowledgeのファイル群を読み込ませてClaude Codeで実装するとか、どれも問題なく動きました。
複数のリポジトリを分けた開発
ベースとなるライブラリ・それを利用したフレームワーク、といった2-3階層に分かれた開発もやってみました。このフレームワークは3つのリポジトリで構成されています。
| リポジトリ | コンセプト数 | 相互リンク数 | 役割 |
|---|---|---|---|
| popcornweb | 665 | 6,633 | Webアプリケーションフレームワーク |
| tinybind-go | 568 | 5,471 | 最初に作った、静的コード解析によるHTMLやJSON読み書きのコード生成ツール |
| tinygodriver | 162 | 857 | TinyGoで動かない標準ライブラリやサードパーティライブラリの互換実装集 |
AIエージェントが把握しなければならないコンテキストの量を減らすという点では大きくリポジトリを分けて開発は良かったです。今回はこのリポジトリごとにナレッジを分けて作りました。1つにまとめてからコードは別々に出す、という方法もありましたが、フォーカスを絞らせたかったので分けたところ、思いの外効果がありました。
それぞれのエージェントは自分の方が詳しいので、それぞれのコンテキストの中で知恵を練っていきます。しかし、それではリポジトリ間のギャップは広がるばかりです。そこで、リポジトリ間の連携が必要であれば提案書をAIに作らせてそれを対抗システムのナレッジに評価させるようにしました。
お互いに知っている範囲で提案するのですが、やり取りしている間により良い意見が出てきたり設計を洗練させる場として機能しました。単に詰め込んでいくだけだとどんどん詰め込んで薄くなっていく印象がありましたが対決させるのは良いですね。
この手の複数エージェントだと、トップは賢いモデルを使い、下流は安いモデルという非対称の手法を見かけます。これはあくまでもコスト削減のためだと思います。今回は作りながら「こんなこともできるんじゃないか」と新しい要件をどんどん足して作っていきました。その過程で過去の決定をひっくり返して機能追加などもなんどもやってきました。そういことでどちらかに賢いモデルを使ってあとは従属というのはやらずに両方とも同じモデル(GPT 5.6 SolやOpus 5など)を使いあえて意見の対立を作りました。
AI同士で案の合意ができて実装が進むこともあれば、設計の良し悪しでAI同士が対立することもありました。人間が間に入って、より良い設計を作って両方を説得するとかも何度もありました。リポジトリをまたいだ変更を要件化するときは全体で達成したい内容を書きますが「この範囲はこっちのリポジトリ、これはこっち」という分担も必ず先頭で明示することで無駄な対立を避けるというのは効果がありました。
なお、AIを使った開発では決定に至る決定の記録を残すという方針のブログなどもみかけますが、過去の情報はノイズにしかならなかったですね。過去の決定を引き合いに相手の決定が間違っているみたいな言い合いになりがちでした。
まとめ
A Philosophy of Software Designで説明されるのは「広い・浅い」モジュールか、「狭い・深い」モジュールか、というモジュールのスタイルです。業務開発だとどちらかというと広い・浅いモジュールを大量に並列でたくさん作る、というスタイルに寄せて、それゆえに100人で同時に実装を進めるという計画をします。そういう開発であればAIで生産性を大幅にアップというのはやりやすいのではないかと思います。
今回挑戦したのはそれとは真逆の極めて狭くて深いフレームワーク開発ですがそういうところでもAIに分業させて比較的大きな規模でも並列でコード作成ができるようになりました。まあまあ効率よくできたのではないかと思います。AI用の設計書をどうするか、というところ、自分でやりこむ場合は1つのスタイルは完成したかな、と思っています。
ただこのスタイルを他の人にも勧めるかというとちょっと悩み中です。広い・浅いモジュールであればNotionとかにAIを使って人間が読めるスタイルで整理とかでいいのかなとか、スタイルの違いもいろいろあるんだろうな、と思ったりしています。
あと、「自分史上最高」の実装をしてみるのはいろんな知識の再確認になって良いですね。今までは「ブラウザで動く検索エンジン」などニッチなものを中心にしてましたが。