# ツールセット

Goa-AI におけるツールセットの種類、実行モデル、検証、構造化された失敗回復、ツールカタログについて学びます。

Source: https://goa.design/ja/docs/2-goa-ai/toolsets/

Relative links resolve against the source URL above.


ツールセットは、エージェントが利用できるツールの集合です。Goa-AI は、実行モデルと用途が異なる複数のツールセット種類をサポートします。

## ツールセットの種類

### サービス所有のツールセット（メソッド連携）

`Toolset("name", func() { ... })` で宣言します。ツールは Goa サービスメソッドに `BindTo` でき、またはカスタムエクゼキュータで実装できます。

- `gen/<service>/toolsets/<toolset>/` の配下に、ツールセット単位の specs / types / codecs が生成されます
- 内部ツールレジストリを使用する場合、レジストリ経由のサービス側実行のために `gen/<service>/toolsets/<toolset>/provider.go` も生成されます
- これらのツールセットを `Use` するエージェントは、プロバイダ側 specs を import し、型付きのコールビルダとエクゼキュータ・ファクトリを取得します
- アプリケーションは、（ランタイム提供のコーデック経由で）型付き引数をデコードし、必要に応じて transforms を使い、サービスクライアントを呼び出して `ToolResult` を返すエクゼキュータを登録します

プロセス間呼び出しのために内部ツールレジストリをデプロイする場合、所有サービスは `toolset:<toolsetID>:requests` に subscribe して `result:<toolUseID>` に publish する provider ループを実行します。provider の接続スニペットは [レジストリのドキュメント]({{< ref "/docs/2-goa-ai/registry.md" >}}) を参照してください。

### エージェント実装のツールセット（Agent-as-Tool）

エージェントの `Export` ブロックで定義し、必要に応じて他のエージェントが `Use` できます。

- 所有権はあくまでサービス側にあり、エージェントが実装になります
- プロバイダ側には `gen/<service>/agents/<agent>/exports/<export>` 配下に export パッケージが生成され、`NewRegistration` と型付きのコールビルダを提供します
- エクスポートされたツールセットを `Use` するコンシューマ側のヘルパは、ルーティング用メタデータを一元化したままプロバイダ側ヘルパへ委譲します
- 実行はインラインで行われます。ペイロードは正規（canonical）の JSON として渡され、プロンプト作成のために必要な場合のみ境界でデコードされます

### MCP ツールセット

`Toolset(FromMCP(service, suite))` は Goa-backed MCP suite 用、`Toolset("name", FromExternalMCP(service, suite), func() { ... })` はインライン tool schema を持つ外部 MCP server 用です。

- 生成される登録は `DecodeInExecutor=true` を設定し、生の JSON が MCP エクゼキュータへそのまま渡されます
- MCP エクゼキュータは自身のコーデックでデコードします
- 生成されるラッパは JSON スキーマ、エンコーダ、HTTP または stdio トランスポートをリトライとトレーシング付きで扱います。HTTP は JSON と event-stream の応答を受け付けます

### BindTo とインライン実装の使い分け

**`BindTo` を使うのは次のときです：**

- 既存の Goa サービスメソッドを呼び出したい
- ツール型とメソッド型の間の transforms を生成したい
- 必要なビジネスロジックがすでにサービスメソッド側にある
- サービス層の検証やエラーハンドリングを再利用したい

```go
// Tool bound to existing service method
Tool("search", "Search documents", func() {
    Args(SearchPayload)
    Return(SearchResult)
    BindTo("Search")  // Calls the Search method on the same service
})
```

**インライン実装を使うのは次のときです：**

- サービスメソッドに紐づかない独自ロジックが必要
- 複数のサービス呼び出しをオーケストレーションする必要がある
- ツールが純粋な計算（外部呼び出しなし）で完結する
- 実行フローを完全に制御したい

```go
// Tool with custom executor implementation
Tool("summarize", "Summarize multiple documents", func() {
    Args(func() {
        Attribute("doc_ids", ArrayOf(String), "Document IDs to summarize")
        Required("doc_ids")
    })
    Return(func() {
        Attribute("summary", String, "Combined summary")
        Required("summary")
    })
    // No BindTo - implement in executor
})
```

インライン実装の場合は、エクゼキュータのロジックを直接記述します：

```go
func (e *Executor) Execute(
    ctx context.Context,
    meta *runtime.ToolCallMeta,
    call *runtime.ToolCall,
) (*runtime.ToolExecutionResult, error) {
    switch call.Name {
    case specs.Summarize:
        args, err := specs.SummarizeTool().Payload.FromJSON(call.Payload)
        if err != nil {
            return nil, fmt.Errorf("decode admitted %s payload: %w", call.Name, err)
        }
        // Custom logic: fetch multiple docs, combine, summarize
        summary := e.summarizeDocuments(ctx, args.DocIDs)
        return runtime.Executed(&planner.ToolResult{
            Name:   call.Name,
            Result: &specs.SummarizeResult{Summary: summary},
        }), nil
    }
    return runtime.Executed(&planner.ToolResult{
        Name: call.Name,
        Failure: &planner.ToolFailure{
            Kind:     planner.FailureInvalidCall,
            Error:    planner.NewToolError("unknown tool"),
            Recovery: planner.RecoveryDirective{Action: planner.RecoveryReplan},
        },
    }), nil
}
```

### 生成されるツールスキーマと例

Goa-AI では、生成された tool spec が model-facing な canonical contract
です。各 tool payload について、codegen は Goa attribute から JSON Schema
を導出し、provider adapter が必要とする projection を事前計算します:

- authored example と field-level JSON Schema example を含む annotated schema
- root の `example` だけを取り除いた同じ schema
- authored top-level example の raw JSON と parsed object example input

provider-facing な top-level tool example になるのは、tool payload に明示的に
書いた Goa の top-level `Example(...)` だけです。Goa が合成した example は
nested schema annotation として残ることがありますが、provider-native example
には昇格しません。

provider adapter は provider contract に合う projection を選びます。
OpenAI-style の tool calling は schema annotation を直接使えます。Direct
Anthropic と Bedrock Claude は root example を除いた schema とともに、parsed
example を native `input_examples` として送ります。Bedrock は必要な beta
contract が適用される場合、Anthropic の field を
`additionalModelRequestFields` 経由で渡します。

アプリケーションが inference service や proxy を通して model request を
ルーティングする場合、その boundary は provider-neutral な
`model.ToolInputContract` として projection をまとめて運ぶ必要があります。
その boundary は generator 専用の `tools.TypeSpec` を import したり、decode
済み schema を再 marshal したり、どの provider がどの projection を使うかを
知ったりしてはいけません。schema-without-root-example や parsed example
input を落とすと、生成 tool spec が正しくても provider adapter は native
`input_examples` を送れません。

### バウンデッドなツール結果

一部の tool は、大きな list、graph、time-series window を返すのが自然です。これらを **bounded view** としてマークすると、trim は service が責任を持ったまま、runtime が contract を強制し表面化できます。

#### agent.Bounds contract

`agent.Bounds` は、tool result が underlying data set 全体に対してどのように bounded されたかを表します。paged tool では provider が次ページの opaque cursor を `NextCursor` に設定します。`ContinueWith` は cursor を runtime が所有し、direct `Cursor` contract は model に公開します。

```go
type Bounds struct {
    Returned       int    // Number of items in the bounded view
    Total          *int   // Best-effort total before truncation (optional)
    Truncated      bool   // Whether any caps were applied (length, window, depth)
    NextCursor     *string // Private provider cursor when another page exists
    RefinementHint string // Guidance on how to narrow the query when truncated
}
```

| フィールド | 説明 |
|-------|-------------|
| `Returned` | result に実際に含まれる item 数 |
| `Total` | truncation 前の total item 数の best-effort 値 (不明なら nil) |
| `Truncated` | pagination、depth limit、size limit など何らかの cap が適用された場合 true |
| `NextCursor` | 次ページ用の opaque cursor。model への公開方法は paging contract が決める |
| `RefinementHint` | result が truncated のとき、query を絞るための人間可読な案内 |

#### trim は service の責務

runtime は subset や truncation を自分では計算しません。**service は次を担います**:

1. **truncation logic の適用**: pagination、result limit、depth cap、time window
2. **runtime bounds metadata の populate**: `planner.ToolResult.Bounds` を設定する
3. **refinement hint の提供**: result が truncated のとき、user/model に query の絞り方を案内する

この設計により、truncation logic を domain knowledge がある service に置いたまま、runtime、planner、UI が消費できる統一 contract を提供できます。

#### bounded tool を宣言する

`Tool` 定義内で DSL helper `BoundedResult()` を使います:

```go
Tool("list_devices", "List devices with pagination", func() {
    Args(func() {
        Attribute("site_id", String, "Site identifier")
        Required("site_id")
    })
    Return(func() {
        Attribute("devices", ArrayOf(Device), "Matching devices")
        Required("devices")
    })
    BoundedResult(func() {
        ContinueWith("continue_devices", "cursor")
        NextCursor("next_cursor")
    })
    BindTo("DeviceService", "ListDevices")
})

Tool("continue_devices", "Continue the available device results", func() {
    Args(func() {
        Attribute("cursor", String)
        Required("cursor")
    })
    Return(func() {
        Attribute("devices", ArrayOf(Device), "Matching devices")
        Required("devices")
    })
    BoundedResult(func() {
        Cursor("cursor")
        NextCursor("next_cursor")
    })
    BindTo("DeviceService", "ContinueDevices")
})
```

continuation tool の cursor は execution contract に存在しますが、model-facing
schema からは除かれます。runtime は一意な live chain head を続行できる場合だけ
action を公開します。model は cursor のコピーや元 query の繰り返しをせず、`{}`
で呼び出します。

#### コード生成

tool が `BoundedResult()` でマークされると:

- 生成 tool spec に `tools.ToolSpec.Bounds` が含まれます
- 生成 JSON result schema には正規 bounded field (`returned`, `total`, `truncated`, `refinement_hint`, optional `next_cursor`) が含まれます
- `tools.ToolSpec.Bounds` は model-facing JSON 名を保存します。DSL が
  `NextCursor("nextCursor")` のような lower-camel Goa attribute を指定しても、
  codegen は `NextCursorField: "next_cursor"` を出力し、schema、runtime
  projection、result codec は同じ spelling を使います。
- `ContinueWith` は cursor を runtime 内に保持し、継続可能な live chain head が一つだけ存在する場合に限って引数なしの continuation action を公開します。正確な cursor lineage によって連続 page を進めます。source call は並列実行できますが、複数の live head がある間は引数なしの continuation action を公開しません。direct `Cursor` contract は opaque cursor を `next_cursor` に公開します
- semantic Go result type は domain-specific のままで、それらの field を重複させる必要はありません

method-backed `BindTo` tool では、生成 executor が runtime projection 前に `planner.ToolResult.Bounds` を構築できるよう、bound service method result は正規 bounded field を保持する必要があります。

```go
spec.Bounds = &tools.BoundsSpec{
    Paging: &tools.PagingSpec{
        ContinueTool:    "tools.continue_devices",
        CursorField:     "cursor",
        NextCursorField: "next_cursor",
    },
}
```

#### bounded tool を実装する

bounded tool は強い contract です。service は truncation を実装し、すべての successful path で bounds metadata を populate します。

**Contract:**

- `Bounds.Returned` と `Bounds.Truncated` は successful bounded tool result で常に設定する必要があります。
- `Bounds.Total`、`Bounds.NextCursor`、`Bounds.RefinementHint` は optional で、分かる場合だけ設定します。
  provider code は `Bounds.NextCursor` に次ページの opaque cursor を設定します。

executor は truncation を実装し、bounds metadata を populate します:

```go
func (e *DeviceExecutor) Execute(ctx context.Context, meta *runtime.ToolCallMeta, call *runtime.ToolCall) (*runtime.ToolExecutionResult, error) {
    args, err := specs.ListDevicesTool().Payload.FromJSON(call.Payload)
    if err != nil {
        return nil, fmt.Errorf("decode admitted %s payload: %w", call.Name, err)
    }

    devices, total, nextCursor, truncated, err := e.repo.QueryDevices(ctx, args.SiteID, nil)
    if err != nil {
        return nil, err
    }

    return runtime.Executed(&planner.ToolResult{
        Name: call.Name,
        Result: &ListDevicesResult{
            Devices: devices,
        },
        Bounds: &agent.Bounds{
            Returned:       len(devices),
            Total:          ptr(total),
            Truncated:      truncated,
            NextCursor:     nextCursor,
            RefinementHint: "Add a status filter or reduce the site scope to see fewer results",
        },
    }), nil
}
```

#### Runtime behavior

bounded tool が実行されると:

1. runtime は successful bounded tool が `planner.ToolResult.Bounds` を返したことを検証します
2. runtime は `BoundedResult(...)` の field name を使い、emitted JSON に bounds を merge します
3. `ContinueWith` では、runtime は一意な live chain head に対してのみ空の action を公開し、実行前に cursor を bind します
4. 同じバッチの別のツールが `finish` を要求した場合、新しい操作は開始できません。成功したクエリに次のページがあれば、その継続アクションと最終結果を保存する終端ツールを提示します。プランナーはページ取得か結果送信を選び、両方を同じバッチには含められません。ページや拒否された応答によって新しい操作が再開することはありません。ページがなければ終端の完了だけが可能です。[ツール失敗後の回復](../runtime/#finish-recovery)を参照してください
5. direct `Cursor` では、runtime は opaque cursor を `next_cursor` に出力し、model が次の call で指定します
6. stream subscriber と finalizer は bounds を UI display、logging、policy decision に使えます

truncated result に次ページの cursor がない場合、runtime の reminder は model に view の限界を明示するよう求めます。partial result でも、返された item についての回答であることを明確にすれば、有用な回答の根拠になります。truncation を明記したり、別のまだ不完全な page を取得したりするだけでは、依然として省略されている item についての事実は確立できません。provider が query 全体について確立した total はその範囲を保ち、後から得られた、それ自体で完全な証拠は、その証拠が対象とする範囲内の結論の根拠になります。利用可能な証拠ですでに質問に答えられる場合、追加の page 取得は不要です。

```go
// In a stream subscriber
func handleToolEnd(event *stream.ToolEndEvent) {
    if event.Bounds != nil && event.Bounds.Truncated {
        log.Printf("Tool %s returned %d of %d results (truncated)",
            event.ToolName, event.Bounds.Returned, *event.Bounds.Total)
        if event.Bounds.RefinementHint != "" {
            log.Printf("Hint: %s", event.Bounds.RefinementHint)
        }
    }
}
```

#### BoundedResult を使う場面

`BoundedResult()` は次のような tool に使います:

- paginated list を返す (device、user、record、log)
- result limit 付きで大きな dataset を query する
- nested structure (graph/tree) に depth/size cap を適用する
- time-windowed data (metric/event) を返す

bounded contract は次を助けます:

- **Model** は result が不完全かもしれないことを理解し、refinement を要求できます
- **UI** は truncation indicator や pagination control を表示できます
- **Policy** は size limit を強制し、runaway query を検出できます

### 注入フィールド（Injected Fields）

`Inject` DSL 関数は、特定のペイロードフィールドを「注入フィールド」としてマークします。これは LLM からは隠され、ツール実行前に生成コードが値を設定する、サーバサイドの値です。セッション ID や、テナント／household 単位のスコープなど、ランタイムや呼び出し側から与えられる値に使えます。

#### Inject の仕組み

フィールドを `Inject` でマークすると、コード生成時に次の 2 つのいずれかのソースへ解決されます：

1. **LLM から隠される**：モデルへ送る JSON Schema と、モデル向けの必須フィールド一覧から除外されます
2. **設計時に検証される**：ツールの実効ペイロード（明示的な `Args()` があればそれ、なければバインド先メソッドの payload）上の必須 `String` フィールドでなければなりません
3. **メタデータ由来またはラベル由来**：Goify した結果が `runtime.ToolCallMeta` の 5 つの固定フィールド（`run_id`/`runId`、`session_id`/`sessionId`、`turn_id`/`turnId`、`tool_call_id`/`toolCallId`、`parent_tool_call_id`/`parentToolCallId`）のいずれかに一致する名前は **メタデータ由来** となり、そのメタデータを直接読み取るコードにコンパイルされます。それ以外の名前はすべて **ラベル由来** です：ラン（run）のラベルを検索するコードにコンパイルされ（ラベルキーは設計上の名前そのまま）、そのフィールド自身に宣言されたバリデーション（`Pattern`、`Length`、enum など）がラベル値に適用されます。ラベル由来のフィールドは `BindTo` ツールでは宣言できません。これは、レジストリ経由で提供されるバインド済みツールが使うレジストリのワイヤープロトコルには、ランのラベルが一切含まれないためです
4. **executor による設定**：両方の実行トポロジー（インプロセスのローカル executor と、レジストリ経由で提供される provider）は、デコードと実行の間で *同じ* 生成済み `Inject<Tool>` 関数を呼び出すため、ツールがどこで実行されても値の設定が食い違うことはありません

#### DSL での宣言

```go
Tool("get_user_data", "Get data for current user", func() {
    Args(func() {
        Attribute("session_id", String, "Current session ID")
        Attribute("query", String, "Data query")
        Required("session_id", "query")
    })
    Return(func() {
        Attribute("data", ArrayOf(String), "Query results")
        Required("data")
    })
    BindTo("UserService", "GetData")
    Inject("session_id")  // メタデータ由来: LLM から隠され、ランタイムが設定する
})
```

ラベル由来のフィールドも同じ書き方ですが、`runtime.ToolCallMeta` の名前ではありません：

```go
Tool("lookup_household", "Lookup scoped to a household", func() {
    Args(func() {
        Attribute("household_id", String, "Household to scope the search to.", func() {
            Pattern("^[a-z0-9-]+$")
        })
        Attribute("query", String, "Search query.")
        Required("household_id", "query")
    })
    Inject("household_id")  // ラベル由来: WithLabels("household_id", ...) で設定する
})
```

呼び出し側は、ラン開始時に `runtime.WithLabels(...)` を指定してラベルの値を渡します：

```go
out, err := client.Run(ctx, sessionID, messages,
    runtime.WithLabels(map[string]string{"household_id": "house-42"}),
)
```

ツールセットのラベル由来フィールドは、エージェント単位に集約された生成済みの `RequiredLabels` 一覧にも反映されます。`Runtime.Start`/`StartOneShot` は、ワークフローやアクティビティをスケジュールする **前に**、呼び出し側が渡したラベルをこの一覧と照合し、不足しているキーをすべて 1 つのエラーにまとめて早期に失敗します。ローカルにエージェントを登録していない `Runtime.ClientFor(route)`（ゲートウェイ／オーケストレーション用クライアント）だけを持つプロセスでは、このチェックは no-op になります。そのトポロジーでは、ラベル不足はツール呼び出しごとに、より後になってから検出されます。

#### 生成されるコード

メソッドにバインドされた生成 executor は、注入を行う各ツールごとに生成された `Inject<Tool>` 関数（ツールセットの `inject.go` に、コーデックと並んで定義されます）を呼び出し、メタデータ由来のフィールドは `runtime.ToolCallMeta` から、ラベル由来のフィールドはランのラベルから、型付き payload へコピーします：

```go
p, err := specs.InjectGetUserData(toolArgs, meta, labels)
```

サポートされる注入フィールド名は固定リストでは **ありません**：`runtime.ToolCallMeta` のフィールドに一致する名前はメタデータ由来、それ以外の名前はすべてラベル由来です。

#### カスタム executor でのペイロードのデコード

手書きの `ToolCallExecutor`（`BindTo` を持たず、ランタイムに直接登録するツール向け）には、代わりに `Inject<Tool>` を呼び出してくれる生成済みのディスパッチがありません。これらのツールの payload は、生の payload コーデックではなく、ツールセットが生成する `Decode<Tool>` 関数でデコードしてください：

```go
p, err := specs.DecodeLookupHousehold(call.Payload, meta, call.Labels)
if err != nil {
    // デコードまたは注入の失敗（ラベル不足・不正など）を処理する
}
```

`Decode<Tool>` は `<Tool>PayloadCodec.FromJSON` と `Inject<Tool>` を 1 回の呼び出しに合成するため、注入が黙って省略されることはありません。コーデックだけでデコードすると、注入対象フィールドはワイヤータグが `json:"-"`（モデルから隠される）であるために「キーが無い」というシグナルが発生せず、エラーなしで Go のゼロ値のまま残ってしまいます。

#### 生成された interceptor による実行時設定

生成されたサービス executor は、`Inject()` とは独立した、型付き interceptor フックも公開します。設計で宣言した注入フィールドに加えて（あるいはその代わりに）、リクエストコンテキストや他のランタイム状態からメソッド payload を補完したい場合に使います：

```go
type SessionInterceptor struct{}

func (i *SessionInterceptor) Inject(ctx context.Context, payload any, meta *runtime.ToolCallMeta) error {
    sessionID, ok := ctx.Value(sessionKey).(string)
    if !ok {
        return fmt.Errorf("session ID not found in context")
    }

    switch p := payload.(type) {
    case *userservice.GetDataPayload:
        p.SessionID = sessionID
    }
    return nil
}

exec := usertools.NewChatUserToolsExec(
    usertools.WithClient(userClient),
    usertools.WithInterceptors(&SessionInterceptor{}),
)
```

登録された interceptor は、生成された `Inject<Tool>` 呼び出しの後、すでにデコード済みの型付き payload に対して実行されます。

#### Inject を使うべきとき

`Inject` は次のようなフィールドに使います：

- サービスにとっては必須だが、LLM に「選ばせる」べきではない
- ランタイムコンテキスト由来（セッション、run/turn/tool-call の ID）、または呼び出し側が渡すランのラベル由来（テナント、household、ユーザ）
- 機密値（認証トークン、API キー）
- インフラ関心事（トレース ID、相関 ID）

---

## 実行モデル

### アクティビティベース実行（デフォルト）

サービス連携のツールセットは Temporal のアクティビティ（他の実行エンジンでも同等の仕組み）として実行されます：

1. 検証済み model client は、schema に違反する provider call を planner が受け取る前に拒否します。planner が作る call は `planner.NewToolRequest` を使い、encode 失敗をその場で返します。
2. planner は生成済み tool 名、正規 payload bytes、任意の provider call ID を持つ、schema 検証済みの `ToolCalls []planner.ToolRequest` を返します。
3. runtime は plan 全体を検証し、各実行 ID を割り当てて `ExecuteToolActivity` を schedule します。
4. activity は受理済み payload を decode します。ここでの失敗は correction 用の evidence ではなく、内部 invariant error です。
5. activity は正規 JSON と runtime が割り当てた実行 ID を含む `Execute(ctx, meta, *runtime.ToolCall)` を呼びます。
6. activity は生成済み result codec で結果を再 encode します。

### インライン実行（Agent-as-Tool）

Agent-as-Tool のツールセットは、プランナー視点では「インライン」実行のように見えます。一方でランタイムはプロバイダ側エージェントを実際の子ランとして起動します：

1. ランタイムがツールセット登録の `Inline=true` を検出する
2. `engine.WorkflowContext` を `ctx` に注入し、ツールセットの `Execute` がプロバイダ側エージェントを子ワークフローとして開始できるようにする（子ランは独自の `RunID` を持つ）
3. 親の `RunID` と `ToolCallID` を含む tool metadata、および正規 JSON payload とともに `Execute(ctx, meta, *runtime.ToolCall)` を呼ぶ
4. 生成済み agent-tool エクゼキュータが、ツールペイロードからネストしたエージェントメッセージ（system + user）を組み立て、プロバイダ側エージェントを子ランとして実行する
5. 子ラン側で plan/execute/resume ループを完走し、`RunOutput` とツールイベントが親 `planner.ToolResult` に集約される（結果ペイロード、集約テレメトリ、`ChildrenCount`、子ランへの `RunLink` を含む）
6. ストリーム購読者が親ツールコールの `tool_start` / `tool_end` に加え、`child_run_linked` のリンクイベントも発行するため、UI は単一のセッションストリームを消費しながらネストされたエージェントカードを構築できる

### 結果マテリアライザ

toolset には、型付きの結果マテリアライザを登録できます。

```go
reg := runtime.ToolsetRegistration{
    Name: "chat.ask_question",
    Execute: runtime.ToolCallExecutorFunc(func(
        ctx context.Context,
        meta *runtime.ToolCallMeta,
        call *runtime.ToolCall,
    ) (*runtime.ToolExecutionResult, error) {
        return runtime.Executed(&planner.ToolResult{
            Name: call.Name,
            Failure: &planner.ToolFailure{
                Kind:  planner.FailureUnavailable,
                Error: planner.NewToolError("externally provided"),
                Recovery: planner.RecoveryDirective{
                    Action: planner.RecoveryReplan,
                },
            },
        }), nil
    }),
    Specs: []tools.ToolSpec{specs.SpecAskQuestion()},
    ResultMaterializer: func(ctx context.Context, meta runtime.ToolCallMeta, call *runtime.ToolCall, result *planner.ToolResult) error {
        // 決定論的な server-only sidecar をここで付与します。
        result.ServerData = buildServerData(call, result)
        return nil
    },
}
```

契約:

- `ResultMaterializer` は、**通常の実行パス** と **外部提供結果による await パス** の両方で実行されます。
- runtime が hooks、workflow 境界、呼び出し側向けに JSON を encode する前に、runtime が割り当てた実行 ID を含む検証済み `runtime.ToolCall` と型付き `planner.ToolResult` を受け取ります。
- `result.ServerData` を付与したり、結果の意味的な形を決定論的に正規化したりする用途に使います。
- 純粋かつ決定論的である必要があります。workflow コード内で動く場合、I/O を行ってはいけません。

これは、元のツール payload と型付き結果から observer-only sidecar を導出しつつ、それらの sidecar をモデルプロバイダから見えないままに保つための正規の場所です。

---

## エクゼキュータ・ファースト（Executor-First）モデル

生成された service toolset は、エージェントが使う toolset ごとに `runtime.ToolCallExecutor` 実装を受け取る registration helper を公開します:

```go
if err := chat.RegisterUsedToolsets(ctx, rt,
    chat.WithSearchExecutor(searchExec),
    chat.WithProfilesExecutor(profileExec),
); err != nil {
    return err
}
```

アプリケーションは、消費する local toolset ごとに executor 実装を登録します。executor は tool の実行方法 (service client、custom function、registry caller など) を決め、`ToolCallMeta` で呼び出し単位の明示的な metadata を受け取ります。

**エクゼキュータ例：**

```go
func Execute(ctx context.Context, meta *runtime.ToolCallMeta, call *runtime.ToolCall) (*runtime.ToolExecutionResult, error) {
    switch call.Name {
    case "orchestrator.profiles.upsert":
        args, err := profilesspecs.UpsertTool().Payload.FromJSON(call.Payload)
        if err != nil {
            return nil, fmt.Errorf("decode admitted %s payload: %w", call.Name, err)
        }

        // tool と bound method が互換な別型を使う場合に生成される。
        mp := profilesspecs.InitUpsertMethodPayload(args)
        methodRes, err := client.Upsert(ctx, mp)
        if err != nil {
            return runtime.Executed(&planner.ToolResult{
                Name: call.Name,
                Failure: &planner.ToolFailure{
                    Kind:     planner.FailureUnavailable,
                    Error:    planner.ToolErrorFromError(err),
                    Recovery: planner.RecoveryDirective{Action: planner.RecoveryReplan},
                },
            }), nil
        }
        tr := profilesspecs.InitUpsertToolResult(methodRes)
        return runtime.Executed(&planner.ToolResult{
            Name:   call.Name,
            Result: tr,
        }), nil

    default:
        return runtime.Executed(&planner.ToolResult{
            Name: call.Name,
            Failure: &planner.ToolFailure{
                Kind:     planner.FailureInvalidCall,
                Error:    planner.NewToolError("unknown tool"),
                Recovery: planner.RecoveryDirective{Action: planner.RecoveryReplan},
            },
        }), nil
    }
}
```

---

## ツールコール・メタデータ

ツールエクゼキュータは、`context.Context` から値を「釣り上げる」のではなく、`ToolCallMeta` を通じて呼び出し単位の明示的なメタデータを受け取ります。これにより、相関付け・テレメトリ・親子関係のための、ラン単位 ID に直接アクセスできます。

### ToolCallMeta のフィールド

| フィールド | 説明 |
|----------|------|
| `RunID` | このツールコールを所有するランの耐久的なワークフロー実行 ID。リトライをまたいで安定し、ランタイム記録とテレメトリの相関に使います。 |
| `SessionID` | 関連するランを論理的にグルーピング（例：チャット会話）。サービスは通常、セッション単位でメモリや検索属性をインデックスします。 |
| `TurnID` | このツールコールを生成した会話ターン ID。イベントストリームがイベントの順序付けやグルーピングに使います。 |
| `ToolCallID` | ツール呼び出しを一意に識別。開始/更新/終了イベントおよび親子関係の相関に使います。 |
| `ParentToolCallID` | 子呼び出しの場合の親ツールコール ID（例：agent-tool が起動したツール）。UI と購読者がコールツリー再構築に使います。 |

### エクゼキュータのシグネチャ

すべてのツールエクゼキュータは、`ToolCallMeta` を明示パラメータとして受け取ります：

```go
func Execute(ctx context.Context, meta *runtime.ToolCallMeta, call *runtime.ToolCall) (*runtime.ToolExecutionResult, error) {
    // Access run context directly from meta
    log.Printf("Executing tool in run %s, session %s, turn %s",
        meta.RunID, meta.SessionID, meta.TurnID)

    // Use ToolCallID for correlation
    span := tracer.StartSpan("tool.execute", trace.WithAttributes(
        attribute.String("tool.call_id", meta.ToolCallID),
        attribute.String("tool.parent_call_id", meta.ParentToolCallID),
    ))
    defer span.End()

    typedResult := buildTypedResult()
    return runtime.Executed(&planner.ToolResult{Name: call.Name, Result: typedResult}), nil
}
```

### 明示的メタデータにする理由

明示的メタデータ・パターンには次の利点があります：

- **型安全**：必要な識別子が存在することをコンパイル時に保証
- **テスト容易性**：context をモックせずにメタデータを簡単に構築できる
- **明快さ**：context key やミドルウェア順序への暗黙依存がない
- **相関性**：ネストした agent-tool 呼び出しの親子関係に直接アクセスできる
- **トレーサビリティ**：ユーザ入力 → ツール実行 → 最終応答までの因果チェーンを明確に追える

---

## 非同期・耐久実行（Async & Durable Execution）

Goa-AI は、サービス連携のツール実行に **Temporal Activities** を利用します。この「async-first」なアーキテクチャは暗黙であり、特別な DSL は不要です。

### 暗黙の非同期（Implicit Async）

プランナーがツール呼び出しを決めたとき、ランタイムは OS スレッドをブロックしません。代わりに：

1. ランタイムがツールコールの **Temporal Activity** をスケジュールする
2. エージェントワークフローが実行を一時停止（状態を保存）
3. アクティビティが実行される（ローカル / リモート / 別クラスタでもよい）
4. アクティビティ完了後、ワークフローが復帰し状態を復元して、結果とともに再開する

つまり **すべてのツールコール** は自動的に並列化可能で、耐久的で、長時間実行にも向きます。標準の async 動作のために `InterruptsAllowed` を設定する必要は **ありません**。

### Pause & Resume（エージェントレベル）

`InterruptsAllowed(true)` は別物です。これは、現在実行中のツールアクティビティに紐づかない任意の外部シグナル（例：ユーザの追加回答）を待つために、**エージェント自体**が停止できるようにします。

| 機能 | 暗黙の非同期 | Pause & Resume |
|------|--------------|----------------|
| **スコープ** | 単一ツール実行 | エージェントワークフロー全体 |
| **トリガ** | 任意のサービスツール呼び出し | 引数不足 / プランナー要求 |
| **必要なポリシー** | なし（デフォルト） | `InterruptsAllowed(true)` |
| **用途** | 遅い API / バッチ / 処理 | Human-in-the-loop / 追加確認 |

ポリシーを有効化する前に、本当に *エージェントレベル* の停止が必要かを確認してください。多くの場合、標準のツール非同期で十分です。

### 非ブロッキングなプランナー

**プランナー（LLM）** の視点では、やり取りは同期的に見えます。モデルがツールを要求し「停止」し、次のターンで結果が「見える」からです。

一方、**インフラ** の視点では完全に非同期・ノンブロッキングです。そのため、小さなエージェントワーカーでも、スレッドやメモリ枯渇なしに多数の長時間実行を同時に扱えます。

### 再起動を跨いだ継続

実行は耐久的なので、ツール実行中にバックエンド全体（エージェントワーカー含む）を再起動しても問題ありません。復旧後は：

- 保留中のツールアクティビティがワーカーにより再取得されます
- 完了したツールが親ワークフローへ結果を返します
- エージェントは中断した地点から正確に再開します

これは、動的な環境でも信頼性高く動くプロダクション品質のエージェントシステムを構築するうえで重要です。

---

## Transforms

ツールが `BindTo` を介して Goa メソッドに紐づくと、コード生成はツールの Args / Return とメソッドの Payload / Result を解析します。形状が互換なら、Goa は型安全な transform ヘルパを生成します：

- `Init<Tool>MethodPayload(in <ToolPayload>) <MethodPayload>` は生成された tool payload を bound Goa method payload に変換します。
- `Init<Tool>ToolResult(in <MethodResult>) <ToolResult>` は bound Goa method result を生成された tool result に変換します。

Transforms はツールセットのオーナー・パッケージ（例: `gen/<service>/toolsets/<toolset>/transforms.go`）に生成され、Goa の GoTransform を使って安全にフィールドをマッピングします。各 helper の戻り値は 1 つで、生成された Go 型参照が pointer/value の正確な signature を決めます。transform が生成されない場合は、executor 側で明示的な mapper を書いてください。

---

## ツール識別子（Tool Identity）

各ツールセットは、生成されたすべてのツール（非エクスポートのツールセットを含む）に対して、型付きツール ID（`tools.Ident`）を定義します。アドホックな文字列ではなく、これらの定数を使ってください：

```go
import searchspecs "example.com/assistant/gen/orchestrator/toolsets/search"

// Use a generated constant instead of ad-hoc strings/casts
spec, _ := rt.ToolSpec(searchspecs.Search)
schemas, _ := rt.ToolSchema(searchspecs.Search)
```

エクスポートされたツールセット（agent-as-tool）については、Goa-AI は `gen/<service>/agents/<agent>/exports/<export>` 配下に export package を生成します:

- 型付きツール ID
- エイリアスの payload / result 型
- コーデック
- 各 tool ID と payload/result codec を組み合わせる型付き `<Tool>Tool()` descriptor。`planner.NewToolRequest` に渡して使います。

---

## ツールの検証と回復

Goa-AI は **Goa の設計時検証** と **構造化された tool failure model** を組み合わせ、LLM planner が受理済み tool call の失敗から安全に回復できるようにします。

### 中核型: ToolError と ToolFailure

**ToolError**（`runtime/agent/toolerrors.ToolError` の alias）:

- `Message string`: 人が読める要約
- `Cause *ToolError`: retry や agent-as-tool hop をまたいで chain を保持する任意の原因
- constructor: `planner.NewToolError(msg)`、`planner.NewToolErrorWithCause(msg, cause)`、`planner.ToolErrorFromError(err)`、`planner.ToolErrorf(format, args...)`

**ToolFailure** は失敗の分類と、次に許可する planner transition を分けて保持します:

```go
type ToolFailure struct {
    Kind     FailureKind
    Error    *ToolError
    Recovery RecoveryDirective
}

type RecoveryDirective struct {
    Action      RecoveryAction
    Issues      []*tools.FieldIssue
    PriorInput  rawjson.Message
    ExampleJSON rawjson.Message
}
```

failure kind には invalid call、domain rejection、unavailability、rate limit、
timeout、malformed result、internal error があります。回復 action は次の 3 つです:

- `RecoveryCorrectCall`: 失敗した tool を利用可能なままにし、構造化された correction evidence を渡す
- `RecoveryReplan`: 次の planner turn から失敗した tool を除く
- `RecoveryFinish`: 新しい操作を禁止し、終端の完了または開始済みクエリの提示されたページ取得を許可する

`ToolResult` は型付き result または 1 つの構造化 failure を持ちます:

```go
type ToolResult struct {
    Name                tools.Ident
    Result              any
    ServerData          rawjson.Message
    ResultBytes         int
    ResultOmitted       bool
    ResultOmittedReason string
    Bounds              *agent.Bounds
    Failure             *ToolFailure
    Telemetry           *telemetry.ToolTelemetry
    ToolCallID          string
    ChildrenCount       int
    RunLink             *run.Handle
}
```

### 受理済み tool failure から回復する

推奨パターンは次のとおりです:

1. **強い payload schema で tool を設計する**（Goa design）
2. **executor の decode failure は invariant error として扱う**。model が出した不正 payload と planner の encode failure は実行前に停止するためです
3. **受理後の domain failure は `ToolFailure` として返す**。schema では表せない cross-field rule や business rule に有効な payload が違反した場合、model-authored call は correction を要求できます
4. **planner は `ToolOutput.Failure` を読む**。runtime は `Recovery` action に従い、同じ tool を残すか、除いて replan するか、run を終了するかを決めます

検証済み model client は生成 codec を使い、unknown field、JSON type mismatch、
schema constraint violation を planner や executor が動く前に拒否します。これらは
`ToolFailure` ではなく output-contract error です。次の例は代わりに、schema
検証を通過した model-authored call が、payload schema では表せない domain rule
`validateUpsertRule` に違反する場合を扱います。`RecoveryCorrectCall` の場合、
workflow は provider call と登録済み tool spec から prior input と example を
導出し、executor が設定した `PriorInput` と `ExampleJSON` は使いません。

**executor 例:**

```go
func Execute(ctx context.Context, meta *runtime.ToolCallMeta, call *runtime.ToolCall) (*runtime.ToolExecutionResult, error) {
    args, err := spec.UpsertTool().Payload.FromJSON(call.Payload)
    if err != nil {
        return nil, fmt.Errorf("decode admitted %s payload: %w", call.Name, err)
    }
    if err := validateUpsertRule(args); err != nil {
        return runtime.Executed(&planner.ToolResult{
            Name: call.Name,
            Failure: &planner.ToolFailure{
                Kind:  planner.FailureInvalidCall,
                Error: planner.ToolErrorFromError(err),
                Recovery: planner.RecoveryDirective{
                    Action: planner.RecoveryCorrectCall,
                },
            },
        }), nil
    }

    res, err := client.Upsert(ctx, args)
    if err != nil {
        return runtime.Executed(&planner.ToolResult{
            Name: call.Name,
            Failure: &planner.ToolFailure{
                Kind:  planner.FailureUnavailable,
                Error: planner.ToolErrorFromError(err),
                Recovery: planner.RecoveryDirective{
                    Action: planner.RecoveryReplan,
                },
            },
        }), nil
    }

    return runtime.Executed(&planner.ToolResult{Name: call.Name, Result: res}), nil
}
```

`PlanResumeInput.ToolOutputs` は各 call の workflow-safe な形、つまり正規
payload/result bytes と `Failure` を持ちます。`RecoveryCorrectCall` では field
issue、prior input、example JSON により次の planner turn が call を修正できます。
`RecoveryReplan` はその turn から失敗した tool を除き、`RecoveryFinish` は
[失敗後の完了契約](../runtime/#finish-recovery)に従います。runtime がこの transition を強制するため、
planner が error text から推測する必要はありません。`RecoveryCorrectCall` を
使えるのは provider-authored call だけです。runtime-created continuation には
model-authored input がないため、execution payload を公開せず replan または
finish します。

---

## ツールカタログとスキーマ

Goa-AI エージェントは、Goa デザインから **単一の権威あるツールカタログ** を生成します。このカタログは次を支えます：

- プランナーへのツール広告（モデルが呼べるツール一覧）
- UI での発見（ツール一覧、カテゴリ、スキーマ）
- 機械可読な仕様が必要な外部オーケストレータ（MCP、カスタムフロントエンドなど）

### 生成される specs と tool_schemas.json

エージェントごとに、Goa-AI は **specs パッケージ** と **JSON カタログ** を生成します。

**Specs パッケージ（`gen/<service>/agents/<agent>/specs/...`）：**

- `types.go`：payload / result の Go 構造体
- `codecs.go`: 型付き payload / result を encode/decode し、closed object の key を強制して structured validation issue を生成する JSON codec
- `specs.go`: 正規 tool ID、payload/result schema、hint を持つ `[]tools.ToolSpec` と、tool ID を型付き payload/result codec に結び付ける tool ごとの `tools.TypedTool` descriptor（例: `SummarizeDocTool`）

生成 spec accessor は毎回新しい copy を返します。返された schema、example、
codec wrapper を application が変更しても、後続 model request には影響しません。
通常の payload は `<Tool>Tool().Payload.FromJSON(...)` で decode し、injected field
を持つ tool は生成された `Decode<Tool>(payload, meta, labels)` helper を使います。
spec 内部を共有 runtime state として保持したり変更したりしないでください。

### Result を持たない tool

Goa service method に result がない method-backed tool では、result schema と
result codec を持たない空の `TypeSpec` が生成されます。executor は架空の
payload を作らず、次のように成功を返します:

```go
return runtime.Executed(&planner.ToolResult{Name: call.Name}), nil
```

生成された型付き descriptor は result に空の `tools.JSONCodec[any]` を使います。
`PlanResume` は空の result bytes を持つ成功として受け取ります。

**JSON カタログ（`tool_schemas.json`）：**

場所：`gen/<service>/agents/<agent>/specs/tool_schemas.json`

ツールごとに 1 エントリがあり、次を含みます：

- `id`：正規ツール ID（`"<service>.<toolset>.<tool>"`）
- `service`, `toolset`, `title`, `description`, `tags`
- `payload.schema` と `result.schema`（JSON Schema）

この JSON は、LLM プロバイダへスキーマを渡す、UI のフォーム/エディタを構築する、オフラインのドキュメントツールを作る、といった用途に向きます。

### ランタイムのイントロスペクション API

実行時に `tool_schemas.json` をディスクから読む必要はありません。ランタイムがイントロスペクション API を提供します：

```go
agents   := rt.ListAgents()     // []agent.Ident
toolsets := rt.ListToolsets()   // []string

spec,   ok := rt.ToolSpec(toolID)              // single ToolSpec
schemas, ok := rt.ToolSchema(toolID)           // payload/result schemas
specs   := rt.ToolSpecsForAgent(chat.AgentID)  // []ToolSpec for one agent
```

ここで `toolID` は、生成された specs もしくは agenttools パッケージの型付き `tools.Ident` 定数です。

### Server Data

一部の tool は、UI や audit system には有用でも model provider には重すぎる rich observer-facing output (完全な time series、topology graph、大きな result set、evidence reference など) を返す必要があります。Goa-AI は、その model 向けではない出力を **server-data** としてモデル化します。

#### Model-facing result と Server Data

重要なのは、どの data がどこへ流れるかです:

| Data Type | Model へ送る | 保存/stream | 目的 |
|-----------|---------------|-----------------|---------|
| **Model-facing result** | ✓ | ✓ | LLM が推論する bounded summary |
| **Timeline server-data** | ✗ | ✓ | UI、timeline、chart、map、table 向け observer-facing data |
| **Evidence server-data** | ✗ | ✓ | provenance reference または audit evidence |
| **Internal server-data** | ✗ | consumer に依存 | tool-composition attachment または server-only metadata |

この分離により:

- model context window を bounded で focused に保てます
- LLM prompt を肥大化させずに rich visualization (chart、graph、table) を提供できます
- model が見る必要のない provenance/audit data を付けられます
- model は summary で作業しつつ、large dataset を UI へ stream できます

#### DSL で ServerData を宣言する

`Tool` 定義内で `ServerData(kind, schema)` を使います:

```go
Tool("get_time_series", "Get time series data", func() {
    Args(func() {
        Attribute("device_id", String, "Device identifier")
        Attribute("start_time", String, "Start timestamp (RFC3339)")
        Attribute("end_time", String, "End timestamp (RFC3339)")
        Required("device_id", "start_time", "end_time")
    })
    // Model-facing result: bounded summary
    Return(func() {
        Attribute("summary", String, "Summary for the model")
        Attribute("count", Int, "Number of data points")
        Attribute("min_value", Float64, "Minimum value in range")
        Attribute("max_value", Float64, "Maximum value in range")
        Required("summary", "count")
    })
    // Server-data: full-fidelity data for observers (e.g., UIs)
    ServerData("metrics.time_series", func() {
        Attribute("data_points", ArrayOf(TimeSeriesPoint), "Full time series data")
        Attribute("metadata", MapOf(String, String), "Additional metadata")
        Required("data_points")
    }, func() {
        AudienceTimeline()
    })
})
```

`kind` parameter (例: `"metrics.time_series"`) は server-data kind を識別し、UI が適切な renderer に dispatch できるようにします。audience は routing intent を宣言します:

- `AudienceTimeline()` は observer-facing timeline/UI payload 用です。
- `AudienceEvidence()` は provenance または audit evidence 用です。
- `AudienceInternal()` は server-only composition payload 用です。

`BindTo(...)` tool で server-data payload を bound service method result の field から project する場合は `FromMethodResultField("field_name")` を使います。

#### 生成 spec と helper

specs package では、各 `tools.ToolSpec` entry に次が含まれます:

- `Payload tools.TypeSpec` – tool input schema
- `Result tools.TypeSpec` – model-facing output schema
- `ServerData []*tools.ServerDataSpec` – result に付随して emitted される server-only payload

server-data entry には生成 schema と codec が含まれるため、subscriber はそれらの canonical JSON bytes を model provider へ送らずに decode できます。

#### Runtime usage patterns

**tool executor 側**では、canonical server-data JSON を tool result に attach します:

```go
func (e *Executor) Execute(
    ctx context.Context,
    meta *runtime.ToolCallMeta,
    call *runtime.ToolCall,
) (*runtime.ToolExecutionResult, error) {
    args, err := specs.GetTimeSeriesTool().Payload.FromJSON(call.Payload)
    if err != nil {
        return nil, fmt.Errorf("decode admitted %s payload: %w", call.Name, err)
    }

    // Fetch full data
    fullData, err := e.dataService.GetTimeSeries(ctx, args.DeviceID, args.StartTime, args.EndTime)
    if err != nil {
        return runtime.Executed(&planner.ToolResult{
            Name: call.Name,
            Failure: &planner.ToolFailure{
                Kind:     planner.FailureUnavailable,
                Error:    planner.ToolErrorFromError(err),
                Recovery: planner.RecoveryDirective{Action: planner.RecoveryReplan},
            },
        }), nil
    }

    // Build bounded model-facing result
    result := &specs.GetTimeSeriesResult{
        Summary:  fmt.Sprintf("Retrieved %d data points from %s to %s", len(fullData.Points), args.StartTime, args.EndTime),
        Count:    len(fullData.Points),
        MinValue: fullData.Min,
        MaxValue: fullData.Max,
    }

    // Build full-fidelity server-data for UIs
    // Generated server-data codecs are named from the tool and kind, for example:
    // specs.GetTimeSeriesMetricsTimeSeriesServerDataCodec.ToJSON(...)
    serverData, err := buildCanonicalServerData("metrics.time_series", fullData)
    if err != nil {
        return nil, err
    }

    return runtime.Executed(&planner.ToolResult{
        Name:   call.Name,
        Result: result,
        ServerData: serverData,
    }), nil
}
```

method-backed tool は、生成 provider と result materializer を通じて server-data を attach することもできます。materializer は deterministic で、normal execution と externally provided-result await path の両方で実行されます:

```go
reg := runtime.ToolsetRegistration{
    Name:  "orchestrator.metrics",
    Specs: []tools.ToolSpec{specs.SpecGetTimeSeries()},
    ResultMaterializer: func(ctx context.Context, meta runtime.ToolCallMeta, call *runtime.ToolCall, result *planner.ToolResult) error {
        if len(result.ServerData) != 0 {
            return nil
        }
        result.ServerData = buildServerData(call, result)
        return nil
    },
}
```

**stream subscriber や UI handler 側**では、tool end event または run log から `ServerData` を読み、宣言 kind 用の生成 codec で decode します:

```go
func handleToolEnd(event stream.ToolEnd) {
    if len(event.Data.ServerData) == 0 {
        return
    }
    data, err := decodeTimeSeriesServerData(event.Data.ServerData)
    if err != nil {
        log.Printf("invalid server-data: %v", err)
        return
    }
    renderTimeSeriesChart(data.DataPoints)
}
```

#### ServerData を使う場面

server-data は次の場合に使います:

- tool result に model context には大きすぎる data (time series、log、大きな table) が含まれる
- UI が visualization (chart、graph、map) のために structured data を必要とする
- model が推論する data と user が見る data を分けたい
- downstream system が full-fidelity data を必要とし、model は summary で十分な場合

次の場合は server-data を避けます:

- 完全な result が model context に無理なく収まる
- 完全 data を必要とする UI/downstream consumer がない
- bounded result だけで必要な情報をすでに含んでいる

---

## ベストプラクティス

- **検証はプランナーではなくデザインに置く**：Goa の属性 DSL（`Required`, `MinLength`, `Enum` など）を使う
- **executor は `ToolFailure` を返す**: plain error や panic ではなく、原因を保持して正確な recovery action を選ぶ
- **correction evidence を正確に保つ**: 生成された field issue、正規 prior input、schema に適合する JSON example を使う
- **planner に failure を読ませる**: `ToolOutput.Failure` の処理を planner の first-class な動作にする
- **サービス内で再検証しない**：Goa-AI はツール境界で検証される前提

---

## 次のステップ

- **[Agent Composition](./agent-composition.md)** - agent-as-tool パターンで複雑なシステムを構築する
- **[MCP Integration](./mcp-integration.md)** - 外部ツールサーバに接続する
- **[Runtime](./runtime.md)** - ツール実行フローを理解する


現在のソース解決、生成済み契約、プロバイダー動作、移行については[ツール検索と動的カタログ](../tool-search/)を参照してください。

