コードを書く代わりにAIエージェントを指揮してOMLAを作った話
- カテゴリ
- AIとローカルLLM
- 公開日
- 2026年7月11日
- 著者
- Jacob Lloyd — プロジェクト完了後、AIの支援を受けて執筆
- 読了時間
- 約13分で読めます
かんたんに言うと: プログラマーのチームなしで、たった一人がAIアシスタントに指示を出し、非営利団体の本格的なライセンス管理サービスを作り上げた実話です。明確な計画書、何重ものチェック、そして人間の承認なしには絶対に公開しないというルール——その進め方を具体的に紹介します。AIの力で本格的なソフトを作るための実践的な設計図です。
非営利団体のロイヤリティ・ライセンス基盤「OMLA」を、AIコーディングエージェントにほぼ全部作らせたときの実際のループを、そのまま公開します。プロセスは遠慮なく持っていってください。完成したバックエンドは「これで本当に動く」という証拠にすぎません。
tl;dr
- これは何: Claude Codeとローカルのエージェント群に本物の非営利バックエンドを作らせたワークフロー。私の役目はアーキテクトと門番で、タイピストではありません。
- 費用: 新規の出費はほぼゼロ。コーディングエージェントのサブスクと、セカンドオピニオン用に余ったGPUがあれば十分。本当のコストは自分のレビュー時間です。
- 必要なもの: コーディングエージェント、「いい感じに」ではなくちゃんと書いた仕様書、そして「人間が合図の言葉を打つまで何もデプロイされない」という鉄の掟。
- 最終的に得られるもの: 証跡つきの動くバックエンド。多層の監査、ローカル実環境でのテスト、人間の「GO」なしには出荷を拒否するデプロイスクリプト。
できあがったもの
作り方の前に、出てきた成果物から。OMLA(Open Model Licensing Association)、オープンAIモデル向けのロイヤリティ・ライセンス基盤と公開サイトです。
| 項目 | 結果 |
|---|---|
| ステータス | ワシントン州の非営利団体として組織化中、501(c)(3)は申請中。サイトには意図的に「Beta 0.9.0」バッジが付いています。 |
| 仕組み | 個人・研究・教育利用は無料。商用利用は「帰属収益」と「モデルの実行コスト」の大きい方の30%を支払う。 |
| データベース | コア12テーブル、Supabase上のPostgres 17、全面に行レベルセキュリティ、金額計算は整数セント(floatは使わない) |
| 本人確認 | ed25519 + ML-DSA-65のハイブリッド署名。古典+ポスト量子の二枚看板なので、将来量子計算機に破られてもシステムは崩れません。検証済み署名なしにロイヤリティ明細は出ない。 |
| 監査証跡 | 追記専用、SHA-256のハッシュチェーン、毎時検証、チェーン先端の「アンカー」を毎日データベースの完全に外へ保存 |
| フロントエンド | フレームワークなしの素の静的HTML約30ページ、13言語対応、普通の共用ホスティング上 |
| 作った人 | 交通整理をする人間1人、コードを書くClaude Code、レビューと雑用をこなすローカルのオープンモデル・エージェント群 |
| 現状 | ステージングとテストは完了。私が自分の手で「GO」と打つまで、デプロイはdry-runのみ。 |
ざっくりした時系列です。週末2日で済んだと思われると困るので:
- 2026年5月下旬 — 個人用チャットボットの趣味プロジェクトからライセンス非営利団体へ方向転換。最初の12テーブルのスキーマもここで
- その数日後 — ポスト量子署名の強化、送金経路のエッジファンクション、そして「非カストディ」への書き直し(後述)
- 6月上旬 — バックエンドを本番へ(データベースのみ。公開フロントエンドはまだ)
- 6月16日 — 法務まわりの書き直しが完了、License v1.0が発効
- 7月2日 — サイトとバックエンドの全面リフレッシュ+13ロケールの再翻訳。ステージング・テスト済みで、この記事の執筆時点でもまだ私の「GO」待ち
おまけの由来話:OMLAはもともと「Open Machine Learning Assistant」の略で、個人用チャットボットのプロジェクト名でした。インフラの選定はライセンス事業への転換にそのまま引き継がれ、チャットボット本人はスコープを固定されたドキュメント案内係に降格、本番では意図的に無効化されています。趣味のチャットボットがライセンス基盤になる——スコープクリープとしてはごく普通の範囲ですよね?
残り全部を可能にした一つのルール
コードを1行も書く前に、いちばんの重労働を片付けた設計判断があります:OMLAはお金を動かさない、預からない、送金しない。支払うべき金額を計算し、受取側が自分で登録したウォレットや支払い先情報を公開する。それだけです。商用利用者はクリエイターに直接、当事者間で支払います。決済処理なし、エスクローなし、「OMLA残高」なし。ウォレット識別子は口座ではなく、ただの宛先情報です。
ライセンスの文言もそのまま言い切っています:「OMLA publishes; OMLA does not pay.(OMLAは公開する。OMLAは支払わない)」
なぜそこまでするのか。資金移動業者になるということは、ライセンスの規制対応の上に、もう一つ別の規制の悪夢を積むということです。カストディを避ければ、KYC・制裁・税務の負担は実際に取引する二者の側に移ります。法的にも本来そこにあるべき負担です。趣味人が1人でこのプロジェクトに挑めたのは、この判断ひとつのおかげです。
しかも最初からこの設計だったわけでもありません。開発の途中まで、スキーマはまだカストディを前提にした形をしていて、その名もpaymentsというテーブルをroyalty_statementsに改名する羽目になりました。カラム、インデックス、トリガー、RLSポリシー、enumまで丸ごと1本のマイグレーション。データベースに自分の仕事について嘘をつかせないため、それだけのために。それでも古い文言は後からまた噛みついてきました(「ハマりどころ」参照)。
ロイヤリティの1ドルが、OMLAに一度も触られずに通る経路がこちら。
関門はステップ4です。ステップ1で検証済みの署名がなければ、ロイヤリティ明細は永遠に出ません。OMLAは金額とウォレットを公開したら、あとは当事者2人の問題——「友だちが君に20ドル借りてるよ」と私が伝えて、あとは2人で解決してもらうのと同じです。
実際のループ:人間、エージェント、検証、デプロイ
ワークフロー自体は複雑ではありません。「誰が何をやるか」に厳格なだけで、何かが変わるたびに同じ流れを繰り返します。
大事なのは箱そのものではなく、どの箱も飛ばせないことです。どの変更も、まず私が「何が存在すべきで、なぜか」を文章にするところから始まります。「いい感じにして」は仕様ではありません。エージェントはその仕様に沿って下書きし、別のエージェントが私の目に入る前に監査し、テストは本番同様に振る舞うインフラの上で走る。私がdiffを読むのはその後です。
バックエンド全体はローカルファーストで動きます。本物のSupabaseスタック、Postgres 17+エッジファンクションが、私のマシンのルートレスコンテナの中に。本番はデプロイ先であって、真実の源(source of truth)ではありません。ローカルと本番が食い違ったら、私がシップするまで間違っているのは本番の方です。
品質基準は私の頭の中ではなく、プロジェクト設定に書いてあります:「Audit-ready by default: ship work that can withstand external audit.(既定で監査対応:外部監査に耐える成果物だけを出荷する)」すべての作業は、あとでAIか人間の独立レビュアーが穴を探しに来る前提で進みます。
モックではなく実スタックでテスト
テストが嘘をついていたら上の話は全部無意味なので、ここではモックのデータベース相手には何も出荷しません。すべて本物の、使い捨てのローカルスタック相手に走ります。3層構成で、デプロイに向けて何かが動く前に全部グリーンであること:
deno test # 13 unit tests for the edge functions
./test/run.sh # isolated scratch DB per run, migrations w/ ON_ERROR_STOP,
# smoke tests (PQ signatures, wrong-key rejection, audit
# hash-chain, payout gate), RLS cross-tenant isolation,
# an adversarial suite, then rollback + reapply
./test/integration.sh # the full money path against the live local stack:
# register → tampered payload rejected → wallet verify →
# splits → usage report → statement published → replay
# (asserts zero duplicate writes) → admin gate → compliance
あの「リプレイして重複書き込みゼロをassert」の行は、見た目以上に重要です。利用報告は他人のシステムから来るので、いつか必ず誰かがうっかり再送信します。テストが確認するのは「クラッシュしなかった」ではなく、「同じ報告をリプレイしても新しい明細が0件」であること。スイートは後から、ログに書くだけでなくassertで落とす方式に固め直しました。細かすぎると思うでしょうが、「何もしていないのにパスした」テストをデバッグする日が来ると分かります。
監査を監査する
AIが自分の仕事を自分でレビューするのは、ただのゴム印です。だから監査はわざと層にしてあります。ローカルの群エージェントがまず修正パスを回し、その上でより強いモデルが独立した最終監査ゲートとして走ります。
この最終ゲートは儀式ではありません。あるパスでは、ローカルエージェントの修正を検証した上で、search_pathのバイパスに加え、カラム・署名・系譜まわりの穴という一次パスの見落としを掘り当てました。それがそのまま1本の強化マイグレーションになり、12件の番号付き指摘それぞれに、こっそり再発できないよう回帰テストを添えて出荷しています。
規模は変更に合わせます。日常的な変更は3エージェントのレビューパネル。フロントエンドとi18nの全面刷新には24エージェントの群パスを回し、そこで「サイトの記述が現実からずれている(今やライセンス基盤なのに、昔のチャットボット製品の説明のまま)」ことが発覚して、現実に合わせた作り直しを強制されました。
デプロイゲート:私が打つまでdry-run
デプロイランナーの仕事はひとつだけ。ここまでの成果物が、事故で本番に届かないようにすることです。毎回、既定でdry-run。本番に触るのは明示的なフラグ付きのときだけ:
./deploy.sh # dry run (default) — shows exactly what WOULD happen
./deploy.sh --go # only a human runs this, only when ready
ガイド付きラッパーはさらに一歩踏み込んでいて、本番を変更するステップの前に文字通り「GO」とタイプさせられます。そしてエージェント向けの常設ルールも、提案で終わらせないよう文章化してあります:人間の明示的な指示なしに--goを付けるな。
紙の上のポリシーではなく、ランナー本体に焼き込んである保証がこちら:
- フロントエンドの同期は絶対に
--deleteを使わないので、ホスト上の管理外ファイルを消しようがない - フロントエンドを上書きする前に、必ずサーバー側バックアップを取る
- バックエンドは、リンク先のプロジェクトが想定した本番プロジェクトと一致しない限り動作を拒否——間違ったデータベースへpushする事故への物理的なガード
- リモートに走るのは追加のみのマイグレーションだけ。リセットは決して走らない
- ドキュメント用チャットボットのファンクションは、主義としてすべてのデプロイから除外
- シークレットはリポジトリ外のmode 600ファイルにだけ置き、出荷物にservice-roleキーが現れないことを平文チェックで確認
正直に言うと、このプロジェクトのデプロイログは、ほぼ私がビビり続けた日記です。完全にステージング済みのリデザインが合図を待つ横で、dry-runにつぐdry-run。でもそれはバグではありません。「ゲート付き」とは、本来そういう感触のはずです。
ハマりどころ
ハイライト集には載らない部分:
- ゼロ幅のバイト。マイグレーションSQLは純粋なASCIIでなければいけません。目に見えない非ASCIIバイトが1つあるだけで、
$$のドル引用トークンが割れて、2つのSQL文が静かに合体します。対策は儀式になりました:非ASCIIバイトをgrep(0件であること)、$$トークンを数える(偶数であること)、毎回。 - パーセント記号は噛みつく。
RAISE EXCEPTIONのフォーマット文字列では、%%がリテラルのパーセントで、値ごとに%がちょうど1つ必要です。一度やられました。いまこうして書き残してある理由は、それが全部です。 - SECURITY DEFINERにはsearch_pathの固定が必須。definer関数とトリガー関数はすべてsearch_pathを固定して、誰かが
pg_tempでテーブルを偽装して偽データを食わせられないようにしてあります。敵対的テストが固定済み関数の数を数えていて、数が減ったら落ちます。 - 気まぐれなsuperuser。不正な再起動の後、ローカルPostgresのロールがたまにsuperuser権限なしで帰ってきます。対処はトラブルシューティングではなく、スタックの綺麗な停止と起動。エージェントたちが毎回ゼロから再発見しないよう、書き残してあります。
- i18nがHTMLを静かに上書きする。翻訳レイヤーはページの文言を丸ごと上書きするので、HTMLを「直して」も、ロケールファイルが直っていなければ古い文言が配信され続けます。削除したはずのカストディ時代の文言が何度も蘇った原因が、まさにこれでした。
- 監査チェーンは、わざと騙してあります——騙せると知っておくために。データベースを完全掌握した攻撃者なら、ハッシュチェーンのトリガーを落として、自己整合する偽の履歴を再構築できます。内部チェックだけでは捕まえられません。だから毎日のジョブがチェーン先端のハッシュをデータベースの完全に外へアンカーし、テストスイートは1回の実行で両面を証明します:内部チェックは騙され、外部アンカーは書き換えを検知する。
- 公開中のサイトはステージング済みのサイトではない。この記事の執筆時点で、公開サイトには方向転換前の文言(古いライセンス草案、決済レールの古いカード)がまだ一部残っています。リデザインはステージング済みなだけで、未デプロイだからです。あと、公開中の
/aboutはただのディレクトリインデックスで、AI利用の開示ページは1階層下にあります。