Content last updated 2026-08-06

AI Catalog バックエンドアーキテクチャ

This page contains information related to upcoming products, features, and functionality. It is important to note that the information presented is for informational purposes only. Please do not rely on this information for purchasing or planning purposes. The development, release, and timing of any products, features, or functionality may be subject to change or delay and remain at the sole discretion of GitLab Inc.
StatusAuthorsCoachDRIsOwning StageCreated
implemented.lukedevops ai_powered2026-02-12

概要

このドキュメントでは、GitLab Rails モノリス内の AI Catalog バックエンドの現在のアーキテクチャを記録します。データモデル、基盤的(foundational)アイテムとカスタムアイテムの異なる実装パターンを記録し、システムが進化する過程で生じたアーキテクチャの不整合を特定します。

このドキュメンテーションにより、以下が可能になります:

  • アーキテクチャレビュー
  • パターン統一のための改善ロードマップの作成
  • カタログに取り組むエンジニアのオンボーディングの改善

問題の声明

AI カタログのバックエンドアーキテクチャは、以下をサポートするため段階的に進化してきました(用語集 参照):

  • カスタムエージェント
  • カスタムフロー
  • カスタム外部エージェント(third-party フロー)
  • 基盤エージェント
  • 基盤フロー
  • 基盤外部エージェント
  • MCP サーバー

この進化は統一されたアーキテクチャパターンなしで行われ、類似の概念に対して異なる実装アプローチが生まれました。このドキュメントは現在の状況を捉えることを目指します。

コアデータモデル

データベーステーブル

テーブル目的
ai_catalog_itemsコアカタログアイテム(エージェント、フロー、外部エージェント)
ai_catalog_item_versionsアイテムのバージョン付き定義(定義は jsonb の definition カラムに格納)
ai_catalog_item_consumersアイテムを使用するグループ / プロジェクトへリンク
ai_catalog_item_version_dependenciesフローバージョンが依存するエージェントを追跡(未使用)
enabled_foundational_flows名前空間 / プロジェクトごとに選択された基盤フローを追跡
namespace_foundational_agent_statuses基盤エージェントのエージェントごとの有効化オーバーライド(名前空間レベル)
organization_foundational_agent_statuses基盤エージェントのエージェントごとの有効化オーバーライド(組織レベル)
ai_flow_triggersフローと外部エージェント向けのイベントベーストリガー(assign、mention、pipeline hook)
ai_catalog_mcp_servers組織スコープの MCP サーバー定義(ai_catalog_items とは別)
ai_catalog_mcp_servers_usersMCP サーバー接続用のユーザーごとの OAuth トークンストア
ai_catalog_mcp_server_blocksMCP サーバー用の名前空間ごとのオプトアウトキルスイッチ

インメモリモデル(FixedItemsModel

これらは FixedItemsModel パターンを使用する Ruby クラスで、データベースには格納されません

モデル目的定義場所
Ai::Catalog::FoundationalFlowGitLab 提供のフロー(Code Review、Developer など)ee/app/models/ai/catalog/foundational_flow.rb
Ai::Catalog::BuiltInToolエージェントが利用可能な事前定義ツールee/lib/ai/catalog/built_in_tool_definitions.rb
Ai::FoundationalChatAgentGitLab 提供のチャットエージェント(Duo Chat など)ee/lib/ai/foundational_chat_agents_definitions.rb

エンティティ関連図

erDiagram
    organizations ||--o{ ai_catalog_items : "has many"
    organizations ||--o{ organization_foundational_agent_statuses : "has many"
    projects ||--o{ ai_catalog_items : "has many (optional)"

    ai_catalog_items ||--o{ ai_catalog_item_versions : "has many"
    ai_catalog_items ||--o{ ai_catalog_item_consumers : "has many"
    ai_catalog_items ||--|| ai_catalog_item_versions : "latest_version"
    ai_catalog_items ||--o| ai_catalog_item_versions : "latest_released_version"

    ai_catalog_item_versions ||--o{ ai_catalog_item_version_dependencies : "has many (unused)"
    ai_catalog_item_version_dependencies }o--|| ai_catalog_items : "dependency - agent (unused)"

    ai_catalog_item_consumers }o--|| ai_catalog_items : "item"
    ai_catalog_item_consumers ||--o| ai_flow_triggers : "has one"
    ai_catalog_item_consumers }o--o| organizations : "container (sharding key)"
    ai_catalog_item_consumers }o--o| namespaces : "container (sharding key)"
    ai_catalog_item_consumers }o--o| projects : "container (sharding key)"

    namespaces ||--o{ enabled_foundational_flows : "has many"
    namespaces ||--o{ namespace_foundational_agent_statuses : "has many"
    projects ||--o{ enabled_foundational_flows : "has many"
    ai_catalog_items {
        bigint id PK
        bigint organization_id FK
        bigint project_id FK
        smallint item_type
        text name
        text description
        boolean public
        smallint verification_level
        text foundational_flow_reference
        bigint latest_version_id FK
        bigint latest_released_version_id FK
        timestamp deleted_at
    }
    ai_catalog_item_versions {
        bigint id PK
        bigint ai_catalog_item_id FK
        bigint organization_id FK
        smallint schema_version
        text version
        jsonb definition
        timestamp release_date
        bigint created_by_id FK
    }
    ai_catalog_item_consumers {
        bigint id PK
        bigint ai_catalog_item_id FK
        bigint organization_id FK
        bigint group_id FK
        bigint project_id FK
        boolean enabled
        boolean locked
        text pinned_version_prefix
        bigint service_account_id FK
        bigint parent_item_consumer_id FK
    }
    ai_catalog_item_version_dependencies {
        bigint id PK
        bigint ai_catalog_item_version_id FK
        bigint ai_catalog_item_id FK
        text note "(table exists but unused)"
    }

アイテムタイプ

ai_catalog_items.item_type enum は 3 つのタイプを定義します:

タイプ説明
1:agentシステム / ユーザープロンプトとツール選択を持つカスタムエージェント。ユーザープロンプトは未使用。
2:flowエージェントから構成される多段階ワークフロー(エージェントはフロー内にインラインで定義される)
3:third_party_flowCI/CD を通じて実行される外部エージェント(Docker イメージ + コマンド)

定義スキーマ

アイテム定義は ai_catalog_item_versions.definition に JSONB として格納され、JSON スキーマに対して検証されます:

スキーマアイテムタイプ主要フィールド
agent_v1.jsonエージェントsystem_promptuser_prompt(未使用)、tools(BuiltInTool ID の配列)
flow_v1.jsonフロー(レガシー)agent_id 参照を持つ steps
flow_v2.jsonフロー(現行)componentsroutersflowpromptsyaml_definition
third_party_flow_v1.json外部エージェントimagecommandsvariablesyaml_definition

基盤アイテム vs カスタムアイテム

概要

カタログは、ユーザー作成(カスタム)アイテムと GitLab 保守(基盤)アイテムの両方をサポートします。ただし、実装パターンは基盤エージェント、フロー、外部エージェント間で大きく異なります。

比較表

観点基盤エージェント基盤フロー基盤外部エージェント
定義場所Ai::FoundationalChatAgentsDefinitions::ITEMSAi::Catalog::FoundationalFlow::ITEMSGitlab::Ai::Catalog::ThirdPartyFlows::Seeder::AGENTS
格納パターンAi::FoundationalChatAgent(インメモリ、FixedItemsModel 使用)ai_catalog_items テーブル(foundational_flow_referenceai_catalog_items テーブル(verification_level: gitlab_maintained
シーディングメカニズムなし(純粋なフィクスチャ)Ai::Catalog::Flows::SeedFoundationalFlowsServiceGitlab::Ai::Catalog::ThirdPartyFlows::Seeder.run!
Consumer レコードなしAi::Catalog::Flows::SyncFoundationalFlowsService により自動作成手動作成
有効化追跡namespace_foundational_agent_statuses / organization_foundational_agent_statuses テーブルenabled_foundational_flows + ai_catalog_item_consumersai_catalog_item_consumers のみ
トリガーサポートN/A(チャットベース)ai_flow_triggers(自動作成)ai_flow_triggers(手動作成)

注: 基盤エージェントは オプションで ai_catalog_items テーブルに表現可能であり、カタログで可視となり複製も可能です。ただし機能的には、基盤エージェントのデータソースは常に Ai::FoundationalChatAgent です。

アーキテクチャ図

flowchart TB
    subgraph "Definition Layer"
        direction LR
        subgraph "Foundational Agents"
            FA_DEF["Ai::FoundationalChatAgentsDefinitions<br/>(Ruby ITEMS constant)"]
        end
        subgraph "Foundational Flows"
            FF_DEF["Ai::Catalog::FoundationalFlow::ITEMS<br/>(Ruby constant)"]
        end
        subgraph "Foundational External Agents"
            FEA_DEF["Gitlab::Ai::Catalog::ThirdPartyFlows::Seeder::AGENTS<br/>(Ruby constant)"]
        end
    end
    subgraph "Storage Layer"
        subgraph "In-Memory"
            FA_MEM["Ai::FoundationalChatAgent<br/>(uses FixedItemsModel)"]
        end
        subgraph "Database"
            FF_DB["ai_catalog_items<br/>(foundational_flow_reference)"]
            FEA_DB["ai_catalog_items<br/>(verification_level: gitlab_maintained)"]
        end
    end
    subgraph "Enablement Layer (database tables)"
        FA_STATUS["namespace/organization<br/>_foundational_agent_statuses"]
        FF_ENABLED["enabled_foundational_flows"]
        FF_CONSUMER["ai_catalog_item_consumers<br/>(auto-created)"]
        FEA_CONSUMER["ai_catalog_item_consumers<br/>(manually created)"]
    end
    subgraph "Trigger Layer (database tables)"
        FF_TRIGGER["ai_flow_triggers<br/>(auto-created)"]
        FEA_TRIGGER["ai_flow_triggers<br/>(manually created)"]
    end
    FA_DEF --> FA_MEM
    FF_DEF -->|Ai::Catalog::Flows::SeedFoundationalFlowsService| FF_DB
    FEA_DEF -->|Gitlab::Ai::Catalog::ThirdPartyFlows::Seeder.run!| FEA_DB
    FA_MEM -.->|"global_catalog_id<br/>(optional link)"| FF_DB

    FA_MEM --> FA_STATUS
    FF_DB --> FF_ENABLED
    FF_DB -->|Ai::Catalog::Flows::SyncFoundationalFlowsService| FF_CONSUMER
    FEA_DB --> FEA_CONSUMER
    FF_CONSUMER --> FF_TRIGGER
    FEA_CONSUMER --> FEA_TRIGGER
    style FA_MEM fill:#ffcccc

有効化メカニズム

基盤エージェント

基盤エージェントは、標準の ItemConsumer パターンとは別の専用ステータステーブルシステムを使用します。

テーブル:

  • namespace_foundational_agent_statuses
  • organization_foundational_agent_statuses

スキーマ:

├── namespace_id / organization_id (FK)
├── reference (string) - e.g., "duo_planner", "security_analyst_agent"
├── enabled (boolean)
└── timestamps

ロジックAi::FoundationalAgentsStatusable concern 内):

  1. Duo Chat(reference: 'chat')は 常に有効(ハードコードされた例外)
  2. 明示的なステータスレコードが存在する場合 -> その enabled 値を使用
  3. それ以外 -> foundational_agents_default_enabled 設定にフォールバック

主な特徴:

  • ItemConsumer レコードを作成しない

フロー図

sequenceDiagram
    participant Admin as Admin UI
    participant Concern as Namespace/Organization<br/>(FoundationalAgentsStatusable)
    participant StatusTable as *_foundational_agent_statuses
    participant Default as Ai::Setting
    rect rgb(240, 240, 240)
        Note over Admin,StatusTable: Write Path
        Admin->>Concern: foundational_agents_statuses=[...]
        Concern->>StatusTable: DELETE existing, CREATE new records
    end
    rect rgb(240, 248, 255)
        Note over Concern,Default: Read Path (enabled_foundational_agents)
        Concern->>StatusTable: Fetch status records
        Concern->>Default: foundational_agents_default_enabled
        Note over Concern: Per agent:<br/>1. Duo Chat → always enabled<br/>2. Status record exists → use it<br/>3. Else → use default
    end

基盤フロー

基盤フローは 2 つのテーブルアプローチを使用します:

Stage 1: 選択(enabled_foundational_flows

どのフローを有効化するかという管理者の 選択 を記録します。

スキーマ:

├── namespace_id OR project_id (exactly one)
├── catalog_item_id (FK to ai_catalog_items)
└── timestamps

書き込み元: CascadeDuoSettingsService による sync_enabled_foundational_flows!

カスケード: 階層を下に伝播(グループ -> サブグループ -> プロジェクト)

Stage 2: アクティベーション(ai_catalog_item_consumers

実行のための 運用上の構成 を記録します。

書き込み元: SyncFoundationalFlowsService(worker を通じて非同期)

含まれるもの: サービスアカウントセットアップ、トリガー作成、バージョンピン留め

主な特徴:

  • グループ階層内のすべてのプロジェクトに対して ItemConsumer レコードを自動作成
  • プロジェクト作成 後と、有効な基盤フローオプションへの変更後のフックを通じて ItemConsumer レコードを同期し続ける必要がある

フロー図

sequenceDiagram
    participant Admin as Admin UI
    participant Cascade as CascadeDuoSettingsService
    participant EFF as enabled_foundational_flows
    participant Sync as SyncFoundationalFlowsService
    participant Consumer as ai_catalog_item_consumers
    participant Trigger as ai_flow_triggers
    rect rgb(240, 240, 240)
        Note over Admin,EFF: Stage 1: Selection
        Admin->>Cascade: Update foundational flow settings
        Cascade->>EFF: sync_enabled_foundational_flows!
        Note over EFF: Cascades to descendants
    end
    rect rgb(240, 248, 255)
        Note over Cascade,Trigger: Stage 2: Activation (async)
        Cascade->>Sync: schedule worker
        Sync->>EFF: Read enabled_flow_catalog_item_ids
        Sync->>Consumer: Create ItemConsumer records
        Sync->>Trigger: Create FlowTrigger records
    end

基盤外部エージェント

標準の ItemConsumer パターンを直接使用します。

基盤外部エージェントは 一回限りのシーディング アプローチを使用します。

定義場所: Gitlab::Ai::Catalog::ThirdPartyFlows::Seeder::AGENTS 定数

書き込み元: Gitlab::Ai::Catalog::ThirdPartyFlows::Seeder.run!

トリガー: Admin API(POST /admin/ai_catalog/seed_external_agents)、Rake タスク、または Admin UI

主な特徴:

  • ItemConsumer レコードを自動作成しない、手動有効化が必要

フロー図

sequenceDiagram
    participant Entry as Admin UI / API / Rake Task
    participant Seeder as Gitlab::Ai::Catalog::<br/>ThirdPartyFlows::Seeder
    participant Item as ai_catalog_items
    participant Version as ai_catalog_item_versions
    rect rgb(240, 240, 240)
        Note over Entry,Seeder: Entry Points
        Entry->>Seeder: POST /admin/ai_catalog/seed_external_agents<br/>OR rake gitlab:ai_catalog:seed_external_agents
    end
    rect rgb(240, 248, 255)
        Note over Seeder,Version: Seeding (Self-managed only)
        Seeder->>Seeder: Validate: not SaaS, not already seeded,<br/>feature flags enabled
        loop For each agent in AGENTS constant
            Seeder->>Item: Create Item (item_type: third_party_flow,<br/>verification_level: gitlab_maintained, public: true)
            Seeder->>Version: Create ItemVersion with YAML definition
            Seeder->>Item: Set latest_released_version
        end
    end
    Note over Item: No ItemConsumer or FlowTrigger created<br/>(users enable manually)

バージョンピン留め

概要

バージョンピン留めにより、消費者は常に最新を使用するのではなく、カタログアイテムの特定のバージョンにロックできます。

主な特徴

  1. ピン留めは semver 形式に従う - MAJOR.MINOR.PATCH(例: 1.2.3)
  2. ピン留めルールは保守的 - コードはプレフィックスマッチングを使った "1" または "1.2" ピンの解決と、最新リリース版に解決する nil をサポートしているが、現在の作成・更新の検証は厳密な semver 形式へのピン留めを要求している
  3. ピンからバージョンを解決するコードは Ai::Catalog::ItemConsumer#pinned_versionAi::Catalog::Item#resolve_version にある

格納

カラム: ai_catalog_item_consumers.pinned_version_prefix

作成

作成は aiCatalogItemConsumerCreate GraphQL ミューテーションと Ai::Catalog::ItemConsumers::CreateService サービスクラスを通じて行われます。

flowchart LR
    CREATE_START["Create ItemConsumer"]
    HAS_PARENT{Has parent<br/>consumer?}
    INHERIT["Inherit parent's<br/>pinned_version_prefix"]
    GET_LATEST["Get item.latest_released_version"]
    SET_PIN["Set pinned_version_prefix<br/>= latest version (e.g., '1.2.3')"]
    CONSUMER_CREATED["ItemConsumer created<br/>with pinned version"]
    CREATE_START --> HAS_PARENT
    HAS_PARENT -->|Yes| INHERIT
    HAS_PARENT -->|No| GET_LATEST
    GET_LATEST --> SET_PIN
    INHERIT --> CONSUMER_CREATED
    SET_PIN --> CONSUMER_CREATED

更新

更新は aiCatalogItemConsumerUpdate GraphQL ミューテーションと Ai::Catalog::ItemConsumers::UpdateService サービスクラスを通じて行われます。

flowchart LR
    UPDATE_START["Update pinned_version_prefix"]
    VALIDATE_FORMAT{Valid semver<br/>format?<br/>e.g., '1.2.3'}
    RESOLVE["Resolve version from prefix"]
    VALIDATE_LATEST{Resolves to<br/>latest released<br/>version?}
    UPDATE_ERROR_FORMAT["Error: 'pinned_version_prefix<br/>is not a valid version string'"]
    UPDATE_ERROR_LATEST["Error: 'pinned_version_prefix must<br/>resolve to the latest released<br/>version of the item'"]
    UPDATE_SUCCESS["Update successful"]
    UPDATE_START --> VALIDATE_FORMAT
    VALIDATE_FORMAT -->|No| UPDATE_ERROR_FORMAT
    VALIDATE_FORMAT -->|Yes| RESOLVE
    RESOLVE --> VALIDATE_LATEST
    VALIDATE_LATEST -->|No| UPDATE_ERROR_LATEST
    VALIDATE_LATEST -->|Yes| UPDATE_SUCCESS

ピンからアイテムバージョンを解決する

この図は、バージョンピン留めがどのバージョンを使用するかをどう解決するかを示しています。

解決ロジックは 3 つのモードすべてをサポートしますが、現在の作成・更新検証ルールは厳密な semver 形式のみを許可することに注意してください。nil とプレフィックスマッチングのパスは通常の ItemConsumer フローでは到達できません。

flowchart LR
    RESOLVE["Ai::Catalog::ItemConsumer#pinned_version"]
    CHECK_NIL{pinned_version_prefix<br/>is nil?}
    CHECK_EXACT{Exact semver?<br/>2 dots}
    FIND_EXACT["Find version by exact match<br/>(e.g., '1.2.3')"]
    FIND_PREFIX["Find latest matching prefix<br/>(e.g., '1.2' → latest '1.2.x')"]
    FIND_LATEST["Return latest_version"]
    EXECUTE["Return resolved version"]
    RESOLVE --> CHECK_NIL
    CHECK_NIL -->|Yes| FIND_LATEST
    CHECK_NIL -->|No| CHECK_EXACT
    CHECK_EXACT -->|"Yes (e.g., '1.2.3')"| FIND_EXACT
    CHECK_EXACT -->|"No (e.g., '1.2')"| FIND_PREFIX
    FIND_EXACT --> EXECUTE
    FIND_PREFIX --> EXECUTE
    FIND_LATEST --> EXECUTE

ビルトインツール

概要

ビルトインツールは、カスタムエージェントに割り当てられる事前定義された能力です。Duo Workflow Service が実行できるアクションを表します。

真実の情報源

Duo Workflow Service、Rails に同期されます。

格納

FixedItemsModel パターン(データベースには無い)。Ai::Catalog::BuiltInToolDefinitions::ITEMS 内のフィクスチャとして定義されます。

{
  id: 1,                      # Stable ID (referenced in agent definitions)
  name: "gitlab_blob_search", # Machine-readable name
  title: "Gitlab Blob Search", # Human-readable title
  description: "..."          # Description for UI
}

ツールデータの同期

スクリプトが Duo Workflow Service の定義から ee/lib/ai/catalog/built_in_tool_definitions.rb を生成します。

同期フロー

flowchart LR
    subgraph "Duo Workflow Service"
        DWS_TOOLS["Tool Definitions<br/>(Python)"]
    end

    subgraph "Sync Process"
        SCRIPT["Sync Script"]
    end

    subgraph "Rails Monolith"
        DEFS["BuiltInToolDefinitions<br/>(ITEMS constant)"]
        MODEL["BuiltInTool<br/>(FixedItemsModel)"]

    end

    DWS_TOOLS --> SCRIPT
    SCRIPT -->|"generates"| DEFS
    DEFS --> MODEL

制限

  • データソースは同期を通じて手動で更新する必要がある。
  • 現在、ツールを削除するプロセスはありません(Issue:!584050)。

エージェントとの関連付け

ツールはエージェント定義(jsonb の ai_catalog_items.definition)を通じてエージェントに関連付けられます。

エージェント定義は、Ai::Catalog::BuiltInToolDefinitions::ITEMS で定義されたツール ID を保存します。

{
  "tools": [1, 3, 10, 39],
  "system_prompt": "...",
  "user_prompt": "..."
}

Duo Workflow Service へのマッピング

ツールは Duo Workflow Service に渡される時、Duo Workflow Service の名前にマッピングし直されます。

Flow トリガー

Flow トリガーにより、GitLab イベントに基づいてカタログフローの自動実行が可能になります。

モデル: Ai::FlowTrigger

ai_flow_triggers テーブルに格納されます。プロジェクトを以下のいずれかにリンクします:

  • カタログアイテム消費者(ai_catalog_item_consumer_id)、または
  • 設定ファイルパス(config_path

イベントタイプ:

タイプ説明
0mentionユーザーがサービスアカウントをメンション
1assignIssue/MR がサービスアカウントにアサインされる
2assign_reviewerサービスアカウントがレビュアーとして追加される
3pipeline_hooksパイプラインイベント

主な検証:

  • config_path または ai_catalog_item_consumer のうち厳密に 1 つを持つ必要がある
  • ユーザーはサービスアカウントである必要がある
  • 消費者にリンクされている場合、消費者のアイテムはフローまたは third-party フローである必要がある
  • 消費者のプロジェクトはトリガーのプロジェクトと一致する必要がある

実行: Ai::FlowTriggers::RunService

アイテムタイプに基づいて実行をルーティング:

アイテムタイプ実行パス
flow(基盤 / カスタム)Ai::Catalog::Flows::ExecuteService → Duo Workflow Service
third_party_flowCi::Workloads::RunWorkloadService → Docker イメージで CI パイプライン

カタログフローの場合、サービスは:

  1. 消費者からピン留めバージョンを解決
  2. 入力とリソースコンテキストからユーザープロンプトを構築
  3. Flows::ExecuteService に委譲

外部エージェント(third-party フロー)の場合、サービスは:

  1. アイテムバージョンからフロー定義を取得
  2. Ai::DuoWorkflows::Workflow レコードを作成
  3. 定義のイメージ / コマンドで CI ワークロードを実行
  4. AI_FLOW_* 環境変数を通じてコンテキストを渡す

実行と統合ポイント

実行コンテキスト

実行はアイテムタイプによって異なります。エージェントはインタラクティブで、チャットインターフェースを通じてユーザーによって起動されます。フローと外部エージェントはイベント駆動で、設定された GitLab イベントが発生した時に自動的にトリガーされます。一部の基盤フローは GitLab Web UI から直接起動することもできます。

アイテムタイプ起動元実行場所
エージェントWeb UI(Agentic Chat)、IDE、Duo CLIDuo Workflow Service
フローFlow Triggers、Web UI(基盤フローのみ)Duo Workflow Service
外部エージェントFlow TriggersCI パイプライン(Docker ワークロード)

Duo Workflow Service とのエージェントおよびフローの統合

Duo Workflow Service は、エージェントとフローの実行エンジンです。LangGraph 上に構築された Python ベースのサービスで、gRPC API を持ちます。

Rails からの統合パス:

  1. Web UI(Agentic Chat)Workhorse 経由の WebSocket 接続が gRPC を使って Duo Workflow Service にプロキシされます。aiCatalogAgentFlowConfig GraphQL クエリがフロー設定を提供します。

  2. IDEGitLab Language Serverには Duo Agent Platform クライアント(別名 executor)が含まれており、Workhorse プロキシを通じて Duo Workflow Service に接続し、ワークフローアクションをローカルで実行します。

  3. Flow TriggersAi::FlowTriggers::RunServiceAi::Catalog::Flows::ExecuteService に委譲し、それが Ai::DuoWorkflows::StartWorkflowService を使って CI パイプラインを通じて実行をオーケストレーションします。

詳細なアーキテクチャ図については、Duo Workflow Architecture ドキュメンテーションを参照してください。

外部エージェントの実行

外部エージェント(third-party フロー)は Duo Workflow Service を 使用しません。CI ワークロードとして直接実行されます:

  1. Ai::FlowTriggers::RunService がトリガーイベントを受信
  2. フロー定義(Docker イメージ、コマンド)が ItemVersion#definition から読み取られる
  3. Ci::Workloads::RunWorkloadService が CI ジョブを作成
  4. コンテキストが AI_FLOW_* 環境変数で渡される

エージェントアイデンティティ

フローと外部エージェントが Flow Triggersを通じてランナー上で実行される時、エージェントの権限は 複合アイデンティティを通じて付与されます。

複合アイデンティティ(Composite Identity)は、サービスアカウント(アクションを実行するマシンユーザー)と人間のユーザー(リクエストを開始した人)を 1 つの OAuth トークンに結合する認証メカニズムです。これにより、アクションがサービスアカウントに帰属させられつつ、権限昇格を防ぎます - トークンはサービスアカウントと人間のユーザーの両方がアクセスできるリソースへのアクセスのみを付与します。

フローと外部エージェントに使用されるサービスアカウントは、Ai::Catalog::ItemConsumer#service_account のユーザーレコードです(関連する flow trigger レコード Ai::FlowTrigger#user 内で複製されます)。

さらなる読み物:

サービスアカウント管理

サービスアカウントは、フローまたは外部エージェントが グループレベル で有効化された時に自動的に作成されます。これらは 複合アイデンティティ 認証のためのマシンアイデンティティを提供します。

作成

Ai::Catalog::ItemConsumers::CreateService がフローまたは外部エージェントのグループレベル消費者を作成すると:

  1. ユーザー名生成"{prefix}-{item_name}-{group_name}".parameterize

    • 基盤フロー: prefix は duo(例: duo-code-review-my-group
    • カスタムフロー / 外部エージェント: prefix は ai(例: ai-my-flow-my-group
  2. 名前生成: アイテム名。基盤フローの場合は “Duo " のプレフィックス付き

  3. Namespaces::ServiceAccounts::CreateService を通じたサービスアカウント作成

    • namespace_id: グループ ID
    • composite_identity_enforced: true(複合アイデンティティに必要)
    • organization_id: グループから継承
  4. 再利用ロジック: 同じユーザー名のサービスアカウントが存在し、まだ ItemConsumer にリンクされていない場合は、新規作成ではなく再利用される。

階層

  • グループレベル消費者group_id を持つ ItemConsumer): サービスアカウントを所有(service_account_id カラム)
  • プロジェクトレベル消費者project_id を持つ ItemConsumer): parent_item_consumer_id を通じて親グループ消費者を参照し、そのサービスアカウントを継承

プロジェクトレベル消費者が作成される時:

  • 親のサービスアカウントが Developer ロールでプロジェクトに追加される
  • FlowTrigger レコードもサービスアカウントを user カラムに保存する

クリーンアップ

ItemConsumer が破棄される時:

  • プロジェクト消費者: サービスアカウントがプロジェクトメンバーシップから削除される
  • グループ消費者: サービスアカウントがグループ階層内のすべてのプロジェクトから削除される

ソフト削除

アイテムは消費者が存在する場合にソフト削除をサポートします。これにより、アイテムが公開されている時に消費者の有効化を保持します。

ソフト削除の動作

ソフト削除されたアイテム:

  • デフォルトで finder の結果から除外される
  • showSoftDeleted: true で直接クエリ可能
  • 消費者とバージョンレコードを保持
  • 組織管理者以外は管理できない(ItemPolicy により防止)

モデルサポート

スコープ: not_deleteddeleted_at が null のアイテムにフィルタする。

削除ロジック

すべてのアイテムタイプを破棄する時に使用される基底サービスクラスである Ai::Catalog::Items::BaseDestroyService は、ソフト削除とハード削除の間で選択します:

flowchart LR
    START[Delete Request] --> FORCE{force_hard_delete<br/>param = true?}
    FORCE -->|Yes| HARD_ADMIN[Hard Delete<br/>admin only]
    FORCE -->|No| HAS_REFS{Has consumers<br/>or dependents?}
    HAS_REFS -->|Yes| SOFT[Soft Delete<br/>set deleted_at]
    HAS_REFS -->|No| HARD[Hard Delete<br/>destroy record]

    HARD_ADMIN --> DONE[Done]
    SOFT --> DONE
    HARD --> DONE

認可

  • delete_ai_catalog_item: 任意の削除に必須(maintainer+ が必須)
  • force_hard_delete_ai_catalog_item: 強制ハード削除に必須(admin が必須)

GraphQL 公開

  • アイテムは soft_deleted フィールド(deleted? メソッドにマップ)を持つ
  • aiCatalogItem クエリは showSoftDeleted 引数を受け付ける(デフォルトは false
  • Ai::Catalog::ItemsFinder はデフォルトで not_deleted スコープを使用する

GraphQL API

AI Catalog バックエンドのデータは GitLab の GraphQL API を通じて公開されます。

タイプディレクトリ
クエリee/app/graphql/resolvers/ai/catalog/
ミューテーションee/app/graphql/mutations/ai/catalog/
タイプee/app/graphql/types/ai/catalog/

認可

認可が行われる場所

認可は 3 つの層 で強制されます:

場所メカニズム
GraphQLミューテーションauthorize :permission + authorized_find!
GraphQLResolvers(finder 経由)通常 finder を通じて Ability.allowed? チェック
Servicesすべてのサービスauthorized? メソッド内で Ability.allowed?
ModelItem スコープpublic_or_visible_to_user スコープがプロジェクトメンバーシップでフィルタ

Policy クラス

  • Ai::Catalog::ItemPolicy カタログアイテム自体へのアクセスを制御。
  • Ai::Catalog::ItemVersionPolicy — 未使用のバージョン実行権限を含む(代わりに ItemConsumerPolicy の類似権限が使用される)。基底権限は ItemPolicy に委譲。
  • Ai::Catalog::ItemConsumerPolicy — アイテム消費者の実行を制御。プロジェクトアイテム消費者には ProjectPolicy、グループアイテム消費者には GroupPolicy の両方に委譲。
  • ProjectPolicyGroupPolicy - アイテムと消費者作成のためのコンテナレベル権限。

MCP サーバー

概要

MCP サーバーは AI Catalog 内の独立した機能であり、カスタムエージェントが Model Context Protocolを介して外部データソースやサードパーティサービスに接続できるようにします。アーキテクチャ上、MCP サーバーは Ai::Catalog::Item レコードとは分離されています。独自のモデルを持ち、プロジェクトではなく組織をスコープとし、カタログ内の他のすべてとは異なる所有権、有効化、可視性の仕組みを使用します。

観点カタログアイテム(エージェント / フロー)MCP サーバー
モデルAi::Catalog::ItemAi::Catalog::McpServer
所有者プロジェクト(project_id組織(organization_id
名前空間での有効化オプトイン: プロジェクトごとに明示的な ItemConsumerオプトアウト: 名前空間ごとの McpServerBlock キルスイッチ
ユーザーごとの状態消費者上のオプションのサービスアカウントMcpServersUser(ユーザーおよびサーバーごとの OAuth トークン)
バージョニングItemVersion の JSONB 定義なし — バージョンなし
可視性制御visibility 列挙型(private / restricted / public)+ public フラグ組織メンバーシップ + ブロック状態
作成権限Maintainer 以上(カスタム権限 admin_ai_catalog_item_consumeradmin または organization_owner のみ

MCP サーバーはアイテムから参照されます。エージェントの ItemVersion.definition JSONB には McpServer ID の mcp_servers 配列を含めることができ、McpServers::ListService が実行時にそれらの ID を解決します。

データモデル

Ai::Catalog::McpServer はルートモデルです。organization_id でシャーディングされ、サーバーの nameurltransport(列挙型: http のみ)、auth_type(列挙型: oauthno_auth)、暗号化された oauth_client_secret を保持します。url はアドレス指定可能な URL として検証され、組織ごとに一意である必要があります。

Ai::Catalog::McpServersUser はサーバーとユーザーの結合テーブルで、ユーザーごとの OAuth tokenrefresh_token(どちらも保存時に暗号化)、および expires_at タイムスタンプを格納します。(mcp_server, user) のペアごとに 1 レコードが存在します。

Ai::Catalog::McpServerBlock は名前空間ごとのオプトアウトキルスイッチとして機能します。McpServerBlock.blocked_server_ids_for(namespace, server_ids)namespace.self_and_ancestor_ids をたどって祖先のブロックを検索し、これにより BLOCKED_BY_ANCESTOR ステータスが導出されます。

エンティティ関連図

erDiagram
    organizations ||--o{ ai_catalog_mcp_servers : "has many"
    ai_catalog_mcp_servers ||--o{ ai_catalog_mcp_servers_users : "has many"
    ai_catalog_mcp_servers ||--o{ ai_catalog_mcp_server_blocks : "has many"
    namespaces ||--o{ ai_catalog_mcp_server_blocks : "has many"
    users ||--o{ ai_catalog_mcp_servers_users : "has many"

    ai_catalog_mcp_servers {
        bigint id PK
        bigint organization_id FK
        bigint created_by_id FK
        text name
        text url
        text description
        text homepage_url
        smallint transport
        smallint auth_type
        text oauth_client_xid
        jsonb oauth_client_secret
    }
    ai_catalog_mcp_servers_users {
        bigint id PK
        bigint organization_id FK
        bigint ai_catalog_mcp_server_id FK
        bigint user_id FK
        jsonb token
        jsonb refresh_token
        timestamptz expires_at
    }
    ai_catalog_mcp_server_blocks {
        bigint id PK
        bigint organization_id FK
        bigint namespace_id FK
        bigint ai_catalog_mcp_server_id FK
        bigint created_by_id FK
    }

ai_catalog_items から MCP サーバーへの接続は(外部キーではなく)疎結合です。エージェントの ai_catalog_item_versions.definition JSONB は、ai_catalog_mcp_servers.id 値の mcp_servers 配列を保持します。Ai::Catalog::McpServers::ListService が実行時にこれらの ID を解決します。

エージェントとの関連付け

MCP サーバーはエージェントの ItemVersion 定義 JSONB を通じてエージェントにリンクされます:

{
  "system_prompt": "...",
  "tools": [1, 3],
  "mcp_servers": [42, 99]
}

Ai::Catalog::McpServers::ListServiceagent_version を受け取り、組織に対する :read_ai_catalog_mcp_server によって制御されたうえで、McpServer.id_in(agent_version.def_mcp_servers) を返します。

MCP ツール(サーバーが公開する個々の機能)は、インメモリモデル FixedItemsModel である Ai::Catalog::McpTool で表現され、永続化されません。ツールは起動時に Mcp::Tools::Manager から動的に検出され、エージェント定義内で名前によって参照されます。

名前空間の可視性

グループおよびプロジェクトレベルの AI > MCP servers ページには、その名前空間で有効化されたエージェントに関連付けられた MCP サーバーが表示されます。導出方法は次のとおりです:

  1. 名前空間のすべての ItemConsumer レコード(そこで有効化されたエージェント)を検索します。
  2. 有効化された各エージェントバージョンについて、def_mcp_servers ID を収集します。
  3. 祖先チェーンをたどる McpServerBlock によってブロックされたサーバーを除外します。

AiCatalogMcpServerblock_status GraphQL フィールド(group_full_path または project_full_path を受け取る)は、AiCatalogMcpServerBlockStatus の 3 つの値のいずれかを返します:

意味
ACTIVEこの名前空間ではサーバーがブロックされていない
BLOCKEDこの特定の名前空間にブロックレコードが存在する
BLOCKED_BY_ANCESTOR祖先の名前空間にブロックレコードが存在する

切断 / ブロックの書き込みパス(McpServerBlock の作成 / 削除)には、まだ専用サービスや GraphQL ミューテーションがありません。読み取り側のバッチローダーは存在しますが、書き込み操作はまだ公開されていません。

ユーザーごとの認証

MCP サーバーの auth_typeoauth の場合、ユーザーはエージェントがそのサーバーを呼び出す前に個別に認証する必要があります。GitLab は OAuth 2.0 Dynamic Client Registration(RFC 7591)をサポートしています。サーバーがこれをサポートしている場合、GitLab は初回接続時に OAuth クライアントとして自動的に自身を登録し、oauth_client_xid と暗号化された oauth_client_secretMcpServer レコードに格納します。

ユーザーごとのトークンは McpServersUser に格納されます(ユーザーおよびサーバーごとに 1 レコードで、tokenrefresh_token は暗号化されます)。McpServer#connected_by_user?(user) はこのレコードが存在するか確認します。AiCatalogMcpServer GraphQL タイプの current_user_connected フィールドは、この情報をフロントエンドに公開します。

エージェント実行中に使用する OAuth 設定(URL、ヘッダー)は、Ai::Catalog::McpServers::ConfigService によって組み立てられます。

サービスと GraphQL

サービスは ee/app/services/ai/catalog/mcp_servers/BaseServiceCreateServiceUpdateServiceListServiceConfigService)配下にあります。GraphQL ミューテーション AiCatalogMcpServerCreateAiCatalogMcpServerUpdate は存在しますが、削除またはブロック / ブロック解除のミューテーションはまだありません。リゾルバー(McpServersResolverMcpServerResolver)は組織スコープです。バッチローダーは McpServersBatchLoaderMcpServerBlockStatusBatchLoader を処理します。

現在の管理モデル

GitLab.com では、MCP サーバーは GitLab 管理者によって一元的に管理されます。GitLab Self-Managed および Dedicated では、インスタンス管理者が管理 UI を介して MCP サーバーを管理できます。

管理 UI はインスタンス / explore レベル(/explore/ai-catalog/mcp-servers)にあります。読み取り専用の名前空間ページはグループレベル(/groups/:group/-/automate/mcp-servers)およびプロジェクトレベル(/:project/-/automate/mcp-servers)に存在し、その名前空間で有効化されたエージェントに関連付けられたサーバーを表示します。

特定された不整合

1 ストレージモデルの不一致

基盤エージェントは FixedItemsModel(インメモリのみ)を使うが、基盤フローと外部エージェントはデータベースレコードを使う。

2 有効化の断片化

3 つの異なる有効化メカニズム:

  • 基盤エージェント: 専用ステータステーブル(*_foundational_agent_statuses
  • 基盤フロー: 2 段階プロセス(enabled_foundational_flows -> ItemConsumer
  • その他すべて: 直接 ItemConsumer レコード

3 Consumer レコードの不整合

基盤エージェントは ItemConsumer レコードを作成しないが、基盤フローはそれらを自動作成する。

4 カタログアイテムリンケージのばらつき

  • エージェント: Ai::FoundationalChatAgent#global_catalog_id フィールド
  • フロー: Ai::Catalog::Item#foundational_flow_reference カラム
  • 外部エージェント: verification_level: :gitlab_maintained を持つ標準アイテム

5 シーディングメカニズムの違い

  • エージェント: シーディングなし(純粋なフィクスチャ)
  • フロー: サービスベースのシーディング
  • 外部エージェント: Admin UI ボタン

6 MCP サーバーは完全に独立したモデルツリーを使用

MCP サーバーは組織スコープであり、エージェントとフローを管理する ai_catalog_items / ItemConsumer / ItemVersion の構造外に存在します。これにより、いくつかの非対称性が生じます:

  • 所有権: アイテムはプロジェクト所有、MCP サーバーは組織所有。
  • 有効化のセマンティクス: アイテムはオプトインの ItemConsumer レコードを使用し、MCP サーバーはオプトアウトの McpServerBlock レコードを使用。

MCP サーバーはバージョン管理された自動化定義ではなくインフラストラクチャエンドポイントであり、ライフサイクルが異なるため、この相違は意図的です。ただし、これは MCP サーバーが共有カタログの仕組み(可視性、ソフト削除、バージョンピン留め、使用数)の恩恵を受けないことを意味します。