Read OSS

ツールとスケジューラー:Gemini CLI がアクションを実行する仕組み

上級

前提知識

  • 第2回:エージェントループ
  • ビルダーパターンおよびストラテジーパターンの理解
  • MCP(Model Context Protocol)の基礎知識

ツールとスケジューラー:Gemini CLI がアクションを実行する仕組み

Gemini のモデルがファイルの読み取りやシェルコマンドの実行、あるいは MCP ツールの呼び出しを決定すると、その命令は精巧なパイプラインへと入っていきます。ツール呼び出しはバリデーションとポリシーチェックを受け、必要に応じてユーザーの確認を取った後に実行されます。その結果は次のターンのためにモデルへとフィードバックされます。この記事では、Gemini CLI のツールシステムとスケジューラーがこの一連のフローをどのように制御しているかを解説します。

ToolInvocation インターフェース

Gemini CLI におけるすべてのツール呼び出しは、ToolInvocation インターフェース によって定義された統一的なライフサイクルに従います。

flowchart LR
    V[validate params] --> D[getDescription]
    D --> C[shouldConfirmExecute]
    C --> E[execute]
    E --> P[getPolicyUpdateOptions]

このインターフェースは明確なコントラクトを提供します。

  • params — この呼び出しに対してバリデーション済みのパラメーター
  • getDescription() — ツールが何をするかを人間が読める形で説明するテキスト
  • toolLocations() — ツールが操作するファイルパス(UI 表示に使用)
  • shouldConfirmExecute(abortSignal, forcedDecision?) — ユーザーの承認が必要な場合は確認情報を返し、不要な場合は false を返す
  • execute(signal, updateOutput?, options?) — ツールを実行して ToolResult を返す
  • getPolicyUpdateOptions(outcome) — ユーザーが「常に許可」を承認した際に、ツール固有のオプションを提供する

shouldConfirmExecute は、ポリシーの判断がユーザー向けの振る舞いとして具体化される場所です。MessageBus のポリシーチェックから渡された forcedDecision を受け取り、false(確認なしで続行)か、型に応じた UI データを持つ ToolCallConfirmationDetails オブジェクトのいずれかを返します。

BaseDeclarativeTool とビルダーパターン

ツールは BaseDeclarativeTool を通じて宣言的に定義されており、スキーマの定義と実行が明確に分離されています。各ツールクラスは以下を指定します。

  • モデルの function declaration 向けの名前、表示名、説明
  • バリデーション用の JSON パラメータースキーマ
  • Kind 列挙値(ReadOnly、Write、Execute、Other)
  • マークダウン出力の有無、ライブアップデートのサポート有無

モデルがツール呼び出しを要求すると、BaseDeclarativeToolSchemaValidator を使ってパラメーターをスキーマに照らしてバリデーションし、createInvocation() を呼び出して ToolInvocation インスタンスを生成します。これがビルダーパターンの実装そのもので、ツール定義がビルダーであり、invocation がその成果物です。

classDiagram
    class BaseDeclarativeTool~TParams, TResult~ {
        +name: string
        +displayName: string
        +description: string
        +kind: Kind
        +parameterSchema: object
        #createInvocation(): ToolInvocation
        +validate(params): ToolInvocation
    }
    
    class ToolInvocation~TParams, TResult~ {
        <<interface>>
        +params: TParams
        +getDescription(): string
        +shouldConfirmExecute(): Promise
        +execute(): Promise~TResult~
        +toolLocations(): ToolLocation[]
    }
    
    class BaseToolInvocation~TParams, TResult~ {
        +messageBus: MessageBus
        +respectsAutoEdit: boolean
        +getApprovalMode: Function
        #getConfirmationDetails(): Promise
        #getMessageBusDecision(): Promise
    }
    
    BaseDeclarativeTool ..> ToolInvocation : creates
    BaseToolInvocation ..|> ToolInvocation

BaseToolInvocation 抽象クラスは、デフォルトの確認フローを提供します。その shouldConfirmExecute メソッドは、まずツールが AUTO_EDIT モードに対応しているかを確認し、次に MessageBus にポリシー判断を問い合わせます。判断が allow であれば確認は不要、deny であれば例外をスローし、ask_user であればツール固有の UI のために getConfirmationDetails() に処理を委譲します。

ヒント: BaseToolInvocationrespectsAutoEdit フラグは、AUTO_EDIT モードでツールの自動承認を許可するかどうかを制御します。このフラグを true に設定するのは WriteFile や Edit といった書き込み系のツールだけで、シェルコマンドはデフォルトモードでは常に明示的な確認が必要です。

ToolRegistry とモデル対応の定義

ToolRegistry は、組み込みツールと動的に発見されたツールの両方を管理する中央ストアです。ツール名を AnyDeclarativeTool インスタンスにマッピングし、モデルの function-calling API 向けに getFunctionDeclarations(modelId?) を提供します。

重要な設計上の特徴が モデルファミリーを考慮したツール定義 です。coreTools.ts の先頭にある getToolSet 関数は、モデルに応じて異なるスキーマセットへルーティングします。

export function getToolSet(modelId?: string): CoreToolSet {
  const family = getToolFamily(modelId);
  switch (family) {
    case 'gemini-3':
      return GEMINI_3_SET;
    case 'default-legacy':
    default:
      return DEFAULT_LEGACY_SET;
  }
}

Gemini 3 モデルはレガシーモデルとは異なるパラメータースキーマや説明をサポートする場合があります。各ツール定義(READ_FILE_DEFINITION など)には、デフォルトのレガシースキーマを返す base プロパティと、現在のモデルに適したセットを解決する overrides 関数があります。

組み込みツールの全ラインナップはファイル操作、コードインテリジェンス、Web アクセス、エージェント制御にわたります。

カテゴリ ツール
ファイル I/O ReadFile, WriteFile, Edit, ReadManyFiles, Glob, LS
検索 Grep(ripgrep バックエンド)
実行 Shell
Web WebSearch, WebFetch
メモリ Memory(ファクトの保存・呼び出し)
エージェント制御 ActivateSkill, AskUser, ExitPlanMode, UpdateTopic
タスク管理 WriteTodos

スケジューラー:イベント駆動型のツールオーケストレーション

Scheduler は、モデルからのツール呼び出し要求と実際のツール実行をつなぐ橋渡し役です。ソースコード中では「ツール実行のためのイベント駆動型オーケストレーター」と説明されています。

flowchart TD
    A[Tool Call Requests from Turn] --> B[Scheduler.schedule]
    B --> C{Already processing?}
    C -- Yes --> D[Enqueue request]
    C -- No --> E[Start batch]
    E --> F[ToolModificationHandler<br/>validates & transforms]
    F --> G[Check Policy via PolicyEngine]
    G --> H{Policy Decision}
    H -- ALLOW --> I[Execute tool]
    H -- DENY --> J[Return error]
    H -- ASK_USER --> K[Evaluate BeforeTool hook]
    K --> L[Resolve confirmation via MessageBus]
    L --> M{User confirms?}
    M -- Yes --> N[Update policy if always-allow]
    N --> I
    M -- No --> O[Return cancelled]
    I --> P[ToolExecutor.execute]
    P --> Q[Track state via SchedulerStateManager]
    Q --> R[Return CompletedToolCall]

スケジューラーは 5 つのコンポーネントを連携させます。

  1. ToolModificationHandler — 実行前にツール呼び出し要求をバリデーションし変換する
  2. PolicyEnginecheckPolicy 経由) — allow / deny / ask_user を判断するルールを評価する
  3. evaluateBeforeToolHook — 実行前フックを発火し、変更やブロックを可能にする
  4. resolveConfirmation — MessageBus を通じてユーザーの確認フローを処理する
  5. ToolExecutor — ツールを実際に実行し結果をキャプチャーする

192 行目schedule() メソッドは、1 つまたは複数の ToolCallRequestInfo オブジェクトを受け取ります。モデルが 1 つのレスポンスで複数の function call を返した場合(並列ツール使用)、それらはまとめてバッチ処理されます。すでにバッチが処理中の場合、新しい要求は _enqueueRequest() でキューに積まれます。

各ツール呼び出しは SchedulerStateManager によって追跡される状態を順に進みます。Scheduled → Validating → Executing → Completed(または Errored)。状態マネージャーは MCP のプログレス更新も処理します。MCP ツールが逐次的な進捗を報告すると、スケジューラーは実行中の状態を進捗率とメッセージで更新します。

詳解:ShellTool と EditTool

組み込みツールの中で最も複雑なのが ShellTool です。このツールは以下を担います。

  1. ポリシー評価のためのコマンドパース(shell-quote パースを使用)
  2. コマンドのラッピングのためのサンドボックスマネージャーとの連携
  3. PID トラッキングを伴うバックグラウンド実行のサポート
  4. ShellExecutionService を通じたプラットフォーム固有の実行

Edit ツールはまったく異なるアプローチを取ります。ファイル内容を丸ごと置き換えるのではなく、diff ベースの変更モデルを採用しています。モデルは old_stringnew_string パラメーターを提供し、ツールは外科的な置換を行います。ファイル全体を送信するよりもトークン効率が高く、ユーザーへの確認表示もクリーンな diff として提示できます。

両ツールはそれぞれの方法で確認フローを実装しています。ShellTool はパースされたコマンドとルートコマンドを含む exec 型の確認詳細を生成します。EditTool はファイルの diff、変更前の内容、変更後の内容を含む edit 型の詳細を生成し、ターミナル UI ではビジュアル diff としてレンダリングされます。

MCP ツールの統合

MCP(Model Context Protocol)ツールは、DiscoveredMCPTool クラスと mcp_{serverName}_{toolName} という命名規則を通じて統合されています。MCP サーバーが設定されると、McpClientManager が利用可能なツールを検出し、mcp_ プレフィックスを付けて ToolRegistry に登録します。

sequenceDiagram
    participant Config as Config.initialize()
    participant MCM as McpClientManager
    participant MCP as MCP Server
    participant TR as ToolRegistry
    
    Config->>MCM: connectToServers()
    MCM->>MCP: listTools()
    MCP-->>MCM: tool schemas
    MCM->>TR: registerTool(DiscoveredMCPTool)
    Note over TR: Tool registered as<br/>mcp_serverName_toolName

MCP ツールは組み込みツールとまったく同じスケジューラーパイプラインを通過します。バリデーション、ポリシーチェック、確認を経てから実行されます。mcp_ プレフィックスのおかげで、mcp_serverName_* のようなワイルドカードポリシーを使い、特定サーバーのツール全体を一括で許可・拒否できます。この仕組みについては、次回のポリシーエンジンに関する記事で詳しく取り上げます。

ヒント: MCP ツールの問題をデバッグする際は、ToolRegistry の allKnownTools マップを確認しましょう。MCP ツールはプレフィックス付きの名前で登録されており、ポリシーマッチングのためにサーバー名が _serverName アノテーションとして含まれています。

スケジューラーは、ツールシステムのビルダーパターンと MessageBus の確認フローと組み合わさることで、柔軟かつセキュアな実行パイプラインを実現しています。次回の記事では、このパイプラインが何を許可されるかを管理するセキュリティレイヤー——ポリシーエンジン、サンドボックス、セーフティチェッカー——を詳しく見ていきます。