← Back to Blog

AIが「単純ではないソフトウェア」を書き始めるとき

AIが「単純ではないソフトウェア」を書き始めるとき

現在、AI支援によるソフトウェア開発は、多くの場合、次のようなシンプルな流れで進みます。

  1. タスクの説明(prompt)を書く。

  2. AIに分析と解決案を作成させる。

  3. スキル、エージェント、サブエージェントを準備する。

  4. 実装を開始する。

  5. AIにコードを書かせ、テストさせ、検出された問題を修正させる。

一見すると、これは理想的な開発ワークフローに見えます。AIは自律的に動き、エージェント間でタスクを分担し、テストを書き、要求された機能を少しずつ完成させていきます。

そして、小規模なプロジェクトであれば、この方法は非常にうまく機能します。

シンプルなECストア、5ページの企業サイト、基本的な管理システムを作るのであれば、AIは作業のかなりの部分を、非常に良い結果でこなせることが多いでしょう。

なぜでしょうか。

それは、何百万人もの開発者がすでに似たようなソフトウェアを作ってきたからです。

2つのオンラインストアを想像してみてください。一方はおもちゃを売り、もう一方はりんごを売っています。

顧客の視点では、これらは別々のビジネスです。しかしソフトウェアアーキテクチャの視点では、両者の動き方は非常によく似ています。

  • カテゴリに整理された商品。

  • 価格と在庫の管理。

  • ショッピングカート。

  • 注文処理。

  • 支払い方法。

  • 顧客管理。

変わるのは主にデータ、固有のビジネスルール、そして一部の機能だけです。中核となるアーキテクチャの原則はほとんど同じままです。

AIは、学習時に似た構造に触れているため、こうしたパターンを認識できます。

問題が始まるのは、一般的なアプリケーションを作る段階を越え、大規模なエンタープライズソフトウェアを開発し始めたときです。

システムが30万〜50万行のコードを含むとどうなるのか?

数十万行のコード、数十のモジュール、数百の依存関係、そして複雑なビジネスルールを持つエンタープライズアプリケーションを想像してみてください。

こうしたシステムは何年もかけて進化してきたはずです。その間に、アーキテクチャ上の判断、例外、歴史的な依存関係、ビジネスルールが積み重なっていきます。

そのすべてが文書化されているわけではありません。

1つのメソッドが他の10個のモジュールに影響することもあります。1つのパラメータを変更しただけで、請求処理、ユーザー権限、注文処理が壊れるかもしれません。

では、この環境にAIエージェントを導入したとします。

次のようなタスクを与えます。

「決済プロセスを変更して、新しい支払い方法に対応してください。後方互換性を維持し、すべてをテストしてください。」

エージェントは計画を立て、サブエージェントに作業を分担し、変更を実装し、テストを実行して結果を返します。

すべてが正しいように見えるかもしれません。

しかし、テストに合格したからといって、ソフトウェアが既存のビジネスルールをすべて維持しているとは限りません。

そしてこれが、6か月前にAIエージェントに対する私の考え方を完全に変えた問題につながります。

エージェント型システムに対する私の見方を変えた実験

6か月前、私はある実験を行い、その結果、単にエージェントやサブエージェントを増やすのではなく、別の道を進むことにしました。

それまでの私は、多くの開発者が共有している前提に立っていました。

タスクを専門エージェントにうまく分担し、検証の仕組みを増やすほど、結果はより信頼できるものになる。

しかし実験を重ねるうちに、根本的な問いに突き当たりました。

ビジネスルールの存在すら知らないエージェントが、どうしてそのルールを正しく実装できるのでしょうか?

そして、システムがどう振る舞うべきかについて完全な情報を持たない別のエージェントが、どうやって結果を確実に検証できるのでしょうか?

私は、エージェント間でコンテキストがどのように受け渡されているかを調べ始めました。

そこで分かったのは、情報の受け渡し方と解釈のされ方が、プロセス全体の中で最も弱い部分の1つになり得るということです。

エージェントとサブエージェントは実際にどう動くのか?

実際には、AIエージェント間の通信には2つの一般的なアプローチがあります。

1. 階層モデル – メインエージェントがサブエージェントを調整する

このアーキテクチャは一般に Hub-and-Spoke(ハブアンドスポーク)または Star Topology(スター型トポロジー)と呼ばれます。

原理は単純です。

  1. ユーザーがメインエージェントにタスクを渡す。

  2. メインエージェントがタスクを分析し、小さな部分に分割する。

  3. サブエージェントAを作成または起動し、指示を与える。

  4. サブエージェントAが作業を行い、結果を返す。

  5. メインエージェントが結果を評価し、必要に応じてサブエージェントBにさらに作業を委任する。

  6. すべての部分が完了したら、メインエージェントが結果を統合する。

サブエージェントAは、サブエージェントBが何をしているかを必ずしも知りません。通信と情報交換はメインエージェントを通して調整されます。

利点: 集中管理、状態の追跡のしやすさ、責任範囲の明確さ、そしてエージェント間の制御されない通信のリスクの低さ。

欠点: メインエージェントが、タスクの指示と結果を解釈する中心点になります。作業を委任する際に重要な情報が抜け落ちると、サブエージェントは不完全なコンテキストのまま動くことになります。

2. ピアツーピア – エージェント同士が直接やり取りする

2つ目のアプローチは、たとえば共有の会話スレッドやメッセージングシステム(*Message Bus*)を通じて、エージェント同士が直接、または継続的に通信する方式です。

次のようなシナリオを考えてみましょう。

  • エージェントAがコードを書き、CodeWrittenイベントを発行する。

  • テスト担当のエージェントBがそのイベントを受け取り、テストを開始する。

  • エージェントCは同時にドキュメントを準備する。

  • エージェントは継続的に結果を交換し、変化に反応する。

利点: タスクの並列実行、迅速な情報交換、単一の中央エージェントへの依存の低減。

欠点: 調整が複雑になること、古い情報で作業してしまうリスク、変更の競合、そしてエラーの根本原因をたどることの難しさ。

どちらのアプローチも非常に有用です。

しかし、どちらも単独では根本的な問題を解決しません。

エージェントがタスクを完璧に定義されていても、システムの実際のビジネスロジックを理解していなければ、技術的には正しいが、ビジネスの観点では間違った解決策を生み出す可能性があります。

エージェント型ワークフローでは、どこでエラーが生まれるのか?

AIは、指示をそのままソースコードに変換する決定論的なコンパイラではありません。言語モデルは利用可能な情報を解釈し、そのコンテキストに基づいて解決策を生成します。

情報が欠けていれば、誤った仮定を置くことがあります。利用可能な情報が不完全だったり矛盾していたりすれば、不適切なアプローチを選ぶことがあります。

より大きなタスクの典型的なワークフローを見てみましょう。

ステップ1 - ユーザーのpromptの解釈

メインエージェントがタスクを受け取り、要求された変更を理解しようとします。

この段階で、依存関係を見落としたり、制約を誤解したり、明示されていないことを前提にしたりする可能性があります。

ステップ2 - サブエージェント向けの指示の作成

メインエージェントは、より具体的なタスク記述を作成します。元のpromptから、関連があると判断した情報を選び出します。

しかし、特定のビジネスルールを知らなければ、それを正しく引き継ぐことはできません。

ステップ3 - 実装

サブエージェントは指示を受け取り、コードベースの一部を分析し、実装方針を選びます。

構文的に正しい高品質なコードを生成しながら、本来変えてはいけない振る舞いを意図せず変更してしまうことがあります。

ステップ4 - 結果の評価

メインエージェントはサブエージェントの出力を受け取り、評価して、タスクが完了したかどうかを判断します。

しかし、それでも以前と同じ不完全な前提のまま動いているかもしれません。

ステップ5 - テストとエラー修正

単体テスト、結合テスト、E2Eテスト、静的解析、ドキュメントレビューが続きます。

これらの仕組みはソフトウェアの信頼性を大きく高めますが、限界もあります。

テストは、特定の期待される振る舞いを検証するものです。重要なビジネスルールがテスト仕様から抜けていれば、実装はすべてのテストに合格しながら、そのルールに違反する可能性があります。

さらに、AIが1つの問題を修正する際に、システムの別の部分に意図せず影響を与えることもあります。

こうして、おなじみのサイクルが生まれます。

実装 → テスト → 修正 → リグレッション → さらなるテスト → また修正。

したがって、単にエージェントの数を増やしたり、テストをより頻繁に繰り返したりしても、正しい結果が自動的に保証されるわけではありません。

なぜエージェントの連鎖でエラーが蓄積し得るのか?

単純化した数学モデルで考えてみましょう。

連続する3つのステップがあり、それぞれが正しく実行される確率が仮に95%だとします。

これらのステップが独立で、3つすべてが成功しなければならない場合、確率は次のようになります。

0.95 × 0.95 × 0.95 = 0.8574

つまり、**3つのステップすべてが成功する確率は約85.7%**です。

このような独立したステップが10個あれば、結果は次のようになります。

0.95¹⁰ ≈ 59.9%

これは説明のための計算であり、AIエージェントの成功率を実測したものではありません。実際のシステムでは、エラーが常に独立とは限らず、検証の仕組みが間違いを捕捉することもあり、個々のステップが以前の失敗を修正することもあります。

それでも、この例は重要な原則を示しています。

個々のエージェントの信頼性が、連鎖全体の信頼性を自動的に保証するわけではありません。

不完全な指示が各段階で繰り返し解釈されると、誤った判断が生まれる機会がさらに増えます。

だからこそ私は、単にエージェントやテスト、検証の仕組みを増やすだけでは不十分だと考えています。

AIがシステム自体についての情報を受け取る方法を変える必要があります。

決定的な違い:テストは結果を検証し、ビジネスロジックは境界を定義する

AI支援開発の従来のアプローチは、事後対応型です。

要件を渡し、AIが解決策を実装し、その後テストで何かが壊れていないかを確認します。

テストがまったくない状態よりは、確かにずっと良いでしょう。

しかし、そこにはなお根本的な弱点があります。

誰も知らず、テスト仕様にも含まれていないビジネスルールを、テストで確実に検証することはできません。

だからこそ私は、実装を始める前に、システムの実際の振る舞いに関する情報をAIに提供するほうが良いアプローチだと考えています。

何を変更すべきかだけでなく、次のような情報もです。

  • 影響を受けるコードにどのビジネスルールが適用されるか。

  • その機能に依存している他のモジュールはどれか。

  • どの入力と出力に互換性を保つ必要があるか。

  • どのシナリオを変更せずに維持する必要があるか。

  • どのような例外や歴史的な制約が存在するか。

  • 実装のどの部分をAIが変更してよく、どの部分を維持しなければならないか。

これにより、AIが自力で推測しなければならない振る舞いの量を大幅に減らせます。

同じタスクに対する2つのアプローチ

アプローチA - Prompt + テスト

AIはタスクを受け取り、利用可能なコンテキストに基づいて実装を選び、解決策を作成してテストを実行します。

テストが不完全であれば、既存のビジネスルールへの違反が見過ごされる可能性があります。

アプローチB - Prompt + ビジネスロジック + テスト

AIは、タスクとともに、関連するルール、依存関係、コントラクト、制約を受け取ります。

その結果、実装を始める前に、どの振る舞いを維持しなければならないかを把握できます。

テストは、エラーを検出するだけでなく、具体的なビジネスコントラクトも検証するようになります。

違いは、テストをやめるということではありません。

違いは、エラーが入り込む前に防ごうとすることです。

When AI starts writing software that is no longer simple — screenshot 1

実践例:新しい支払い方法の追加

ECアプリケーションで決済を扱うCheckoutService.tsというファイルがあるとします。

追加の支払い方法として、カード決済を加えたいと考えています。

オプションA:従来のprompt

タスク:

> CheckoutService.tsにカード決済を追加してください。既存の機能を維持し、すべてのテストを実行してください。

AIはコードを変更し、新しい支払いの分岐を追加し、テストを更新します。

すべてのテストに合格します。

タスクは完了としてマークされます。

しかし、このシステムには重要なビジネスルールがあります。

法人アカウントは、デフォルトの支払い方法として銀行振込を使用しなければならない。

このルールは数年前に導入されたものです。実装した開発者はすでに会社を去っています。ルールは明確に文書化されておらず、専用のリグレッションテストもありません。

AIは、すべての顧客に対してカード決済をデフォルトの方法として導入してしまいます。

実装の観点では、新機能は正しく動作しています。

ビジネスロジックの観点では、システムは壊れています。

そしてテストは、この問題を検出できませんでした。

オプションB:AITMでビジネスロジックを組み込んだ構造化タスク

では、AIが同じ依頼を受け取るものの、今回はタスクに関連するビジネスコントラクト、関連する変数、過去のタスクへのリンク、そして許可される変更の明確な境界が含まれているとします。

タスクは次のようになります。

READ CLAUDE.md first.

TASK:

Add card payment as an additional payment method

in CheckoutService.ts.

Contract:

%CHECKOUT-019 — "For company accounts
(companyId != null), bank transfer must remain
the default payment method. Card payment may
be offered only as an additional option."

Related variables:

BL.CHECKOUT.PAYMENTMETHOD_DEFAULT
BL.CHECKOUT.PAYMENTMETHOD_OPTIONS

Related task:

#<link to the task that originally implemented bank-transfer-as-default checkout>

In scope:

- Add a new "card" option to the available payment methods in CheckoutService.ts.

- Expose it as a selectable option in the existing payment method selection flow.

Out of scope:

- Changing the default payment method for any account type.

- Any UI/checkout-flow redesign beyond adding the new option.

- New payment provider integration logic beyond the method registration itself.

Must not break:

- Existing default payment selection logic (company accounts → bank transfer stays default).

- Company account checkout behavior end-to-end.

- Existing bank transfer processing path.

- Compatibility with the current payment API contract.

File path:

src/Services/CheckoutService.ts
Confirm the exact path via contractsForFile before editing.

Verify:

- Company account checkout still selects bank transfer by default
  (companyId != null).

- Card payment appears as a selectable, non-default option.

- Existing bank transfer processing is unchanged.

- Run unit and integration tests covering CheckoutService.

AITMでの仕組みの詳細はこちら:https://aitm.cz/Docs/KnowledgeSharing

なぜこの形式が重要なのか?

一見すると、単に詳しくなったpromptに見えるかもしれません。

しかし本当の違いは、タスクが、実際のプロジェクトに照らして取得・検証できる情報と結びついていることです。

1. コントラクト %CHECKOUT-019

識別子 %CHECKOUT-019 は、単なるテキストのメモではありません。

これは説明用のコントラクト識別子で、エージェントは contractsForFile や contractsForVariable といったGraphQL操作を通じて取得できます。

エージェントは、promptに書かれたルールだけに頼るのではなく、その構造化された表現を取得し、現在のコードベースに照らして検証できます。

2. ビジネスロジック変数 _BL.

_BL.CHECKOUT.PAYMENT_METHOD_DEFAULT や _BL.CHECKOUT.PAYMENT_METHOD_OPTIONS のような識別子は、AITMのコミュニケーション言語で表現されたビジネスロジックを表します。

これらは、特定の要件を、システムの振る舞いを決定するルールや変数と結びつける助けになります。

3. 関連タスクへの参照

Related taskセクションは、そのビジネスルールが実装された元のタスクを参照します。

これによりAIは、システムが現在どう振る舞っているかだけでなく、なぜ特定の決定がなされたのかについても、追加のコンテキストを得られます。

4. 明確に定義された変更の境界

In scope、Out of scope、Must not break の各セクションは、3つの異なるカテゴリを定義します。

  • AIが実装しなければならないこと。

  • AIが実装してはならないこと。

  • AIが維持しなければならない既存の振る舞い。

AIは、自由に解釈したり拡張したりできる曖昧なタスクではなく、正確に定義された実装スコープを受け取ります。

5. コードを書く前の検証

ファイルパスが自動的に正しいと見なされることはありません。

エージェントは、まず contractsForFile を使って実際のパスを確認し、関連するコントラクトを特定するよう指示されます。

その後で初めて、コードの変更を始めるべきです。

6. TPVによるpromptの検証

AITMのアプローチの重要な要素が、TPVのprompt検証機能です。

タスクが実行のために送信される前に、必須のタスクセクションをチェックします。

たとえば、スコープ定義、制約、コントラクト、ファイルパス、検証要件が存在するかどうかを確認します。

これにより、エラー防止の一部が、完成したコードのテストから、タスク自体の準備へと移ります。

注: この例に登場するコントラクト識別子とビジネスロジック変数は、説明用のプレースホルダーです。実際のプロジェクトでは、オプティマイザーが特定のプロジェクトからGraphQLを通じて取得します。検証せずに推測したり生成したりしてはいけません。

このアプローチで何が変わるのか?

従来のアプローチでは、エージェントはまず曖昧なタスクを解釈し、どのファイル、依存関係、ビジネスルールが重要かを自分で判断します。

AITMでは、タスクが具体的なコントラクト、ビジネスロジック変数、過去のタスク、そして明確に定義された変更の境界と結びついています。

そのためエージェントは、主に仮定に頼るのではなく、検証可能な情報に基づいて判断できます。

そして、ここに根本的な違いがあると私は考えています。

ビジネスロジックは、もはや単なるドキュメントの一部ではありません。実装を制御するプロセスそのものの一部になります。

これがAITMの背後にある原則です

AITM(AI Task Manager)の設計を始めたとき、私たちは、エージェントを起動し、タスクを分配し、その後で結果を確認するだけの、もう1つのシステムを作りたいとは思いませんでした。

私たちが注目したのは、別の問いでした。

変更を加える前に、既存のソースコードから関連するビジネスロジックを直接AIが受け取れたらどうなるだろうか?

1年前に誰かが書いたドキュメントだけではありません。

スキルに保存された一般的な指示だけでもありません。

そして、エージェントがたまたま選んだ数ファイルだけでもありません。

そうではなく、影響を受けるシステムの部分が実際にどう振る舞うのか、どのコントラクトがそれを支配しているのか、そして他のどのコンポーネントがそれに依存しているのかを記述した、構造化された情報です。

したがって、AITMの中核となる原則は、コードベースの関連部分から特定したビジネスロジックで、作業用のpromptを充実させることです。

エージェントは、何をしなければならないかという情報だけでなく、何を維持しなければならないかという情報も受け取ります。

これは、単一のpromptに関連するすべての依存関係や制約が含まれているとは限らない、大規模なソフトウェアシステムでは特に重要です。

AIのガードレールとしてのビジネスロジック

ビジネスロジックを、道路沿いのガードレールだと考えてみてください。

ガードレールがなければ、AIはさまざまな道を進むことができます。正しい解決策にたどり着く道もあれば、システム内に潜む隠れたルールに違反する道もあります。

関連するコントラクトと制約を提供することで、望ましくない判断の余地を大幅に狭められます。

AIは依然として、実装戦略、アルゴリズム、コード構造を自分で選べます。

ただし、既知のビジネスルールは尊重しなければなりません。

その後のテストで、それらのルールが実際に維持されたかどうかを検証します。

これは、考え方の根本的な転換を意味します。

私たちは、AIが生み出したエラーをただ修正しようとしているのではありません。そもそもそのエラーが入り込む確率を下げようとしているのです。

これはエージェントもテストももう不要ということか?

決してそうではありません。

エージェントは、並列実行、専門的な作業、大規模タスクの自動化に役立ちます。

単体テスト、結合テスト、E2Eテストは、ソフトウェア品質保証に欠かせない要素であり続けます。

しかし、エージェントのオーケストレーションや自動テストは、ビジネスロジックの知識の代わりにはなりません。

システムが何を維持しなければならないのかをエージェントが理解していなければ、どれほど完璧に組織されたサブエージェントのチームでも、根本的な問題は解決できません。

そして、テストが関連するシナリオを網羅していなければ、すべてのテストに合格しても、アプリケーションはビジネスの観点では誤った動作をしたままかもしれません。

正しいアプローチは、ビジネスロジックとテストのどちらかを選ぶことではありません。両者を組み合わせることです。

まず、システムの実際の振る舞いを特定します。

次に、その振る舞いを明確なルールとコントラクトとして表現します。

続いて、これらのルールをタスクとともにAIに提供します。

そこで初めて、実装、検証、テストに進みます。

このアプローチは、不要な修正、リグレッション、そして実装サイクルの繰り返しを減らせる可能性があります。

結論:重要なのはエージェントの数ではなく、情報の質

シンプルなアプリケーションであれば、AIは比較的短い指示でも優れた結果を出せます。

しかし、50万行のコードを持つエンタープライズソフトウェアでは、エージェントがすべてを正しく理解し、あらゆる依存関係を見つけ出し、さらに自分の作業を完璧にテストしてくれると期待するだけでは不十分です。

システムが大きくなるほど、その実際のビジネス上の振る舞いを理解し、守ることが重要になります。

10体のエージェントが完璧に連携していても、そのどれもが重要なビジネスルールを知らなければ、全体として、完璧にテストされていながら根本的に間違った解決策を生み出してしまう可能性があります。

だからこそ、私たちはAITMで異なるアプローチを取っています。

まず、コンテキスト、ルール、コントラクト。

次に、実装。

そして最後に、検証とテスト。

なぜなら、目標は、AIができるだけ短時間で、できるだけ多くのコードを書くことではないからです。

目標は、AIが変更すべきものだけを正確に変更し、すでに動いているものを壊さないことです。

www.aitm.cz