> ## Documentation Index
> Fetch the complete documentation index at: https://adcp-docs-ja.pier1.co.jp/llms.txt
> Use this file to discover all available pages before exploring further.

# A2A レスポンス形式

> A2A プロトコルで送信される AdCP レスポンスに必要な DataPart 構造、完了タスクおよび非同期タスクのアーティファクトレイアウト、Agent-to-Agent Protocol におけるステータス別レスポンスパターン。

このドキュメントは、A2A プロトコルで送信される AdCP レスポンスの **標準構造** を定義します。

## A2A ワイヤーフォーマット

以下の例は **A2A 1.0** ワイヤーフォーマットを使用します。Part は `kind` 判別子を持たず（コンテンツタイプはどのフィールドが設定されているか — `text`、`data`、`url`、`raw` — で暗黙的に決まる）、ロールは `ROLE_USER` / `ROLE_AGENT`、タスク状態は `TASK_STATE_*`（ProtoJSON 正準形）です。v0.3 との対比は [A2A ガイド](/docs/building/by-layer/L0/a2a-guide#a2a-protocol-versions)を参照してください。

AdCP のトップレベル統合 `status` フィールド（`@adcp/sdk` が返す）は、引き続き小文字の短縮形（`"completed"`、`"failed"`、`"working"`、`"input-required"`、`"submitted"`）を使用します。これは `status.state` 上の AdCP の抽象化であり、A2A ワイヤー値ではありません。

v0.3 サーバーの場合、同じ DataPart は `{ "kind": "data", "data": {...} }` になり、状態は小文字になります。抽出クライアントは互換期間中に両方の形状を受け入れます。

## 必須構造

### 最終レスポンス（status: "completed"）

**A2A 上の AdCP レスポンスは必ず以下を満たす必要があります:**

* タスクのペイロードを含む DataPart（非 null の `data` フィールドを持つ Part）を少なくとも 1 つ含めます
* 複数アーティファクトではなく、1 つのアーティファクトに複数パートを入れる
* DataPart が複数ある場合は最後のものを正とします
* AdCP ペイロードをフレームワーク固有オブジェクトでラップしない（`{ response: {...} }` など禁止）

**Recommended pattern:**

```json theme={null}
{
  "status": "completed",
  "taskId": "task_123",
  "contextId": "ctx_456",
  "artifacts": [{
    "name": "task_result",
    "parts": [
      {
        "text": "Found 12 video products perfect for pet food campaigns"
      },
      {
        "data": {
          "products": [...],
          "total": 12
        }
      }
    ]
  }]
}
```

* **TextPart**（`text` フィールドを持つ Part）: 人間向けサマリー — **推奨**（任意）
* **DataPart**（`data` フィールドを持つ Part）: 構造化された AdCP レスポンスペイロード — **必須**
* **FilePart**（`url` または `raw` フィールドを持つ Part）: 任意のファイル参照（プレビュー、レポート）

**複数アーティファクト:** 本質的に異なる成果物（例: クリエイティブと別個のトラフィッキングレポート）がある場合のみ。AdCP では稀であり、基本は 1 アーティファクト内に複数パートを推奨。

### 中間レスポンス（working, submitted, input-required, auth-required）

中間ステータス更新は `TaskStatusUpdateEvent` として配信され、任意の進捗/チャレンジデータは（`artifacts` ではなく）`status.message.parts[]` に含まれます。アーティファクトはタスクライフサイクル中に蓄積され、タスクが終端状態に達すると最終成果物として読まれます。

```json theme={null}
{
  "taskId": "task_123",
  "contextId": "ctx_456",
  "status": {
    "state": "TASK_STATE_WORKING",
    "timestamp": "2026-01-22T10:15:00.000Z",
    "message": {
      "role": "ROLE_AGENT",
      "parts": [
        {
          "text": "Processing your request. Analyzing 50,000 inventory records..."
        },
        {
          "data": {
            "percentage": 45,
            "current_step": "analyzing_inventory"
          }
        }
      ]
    }
  }
}
```

SSE 経由またはプッシュ通知として配信される場合、このイベントは A2A 1.0 の `StreamResponse` oneof でラップされます: `{ "statusUpdate": { … } }`。非ストリーミングレスポンス（例: `tasks/get`）は素のオブジェクトを配信します。クライアントは `status.state` を読む前にアンラップします — [A2A レスポンス抽出](/docs/building/by-layer/L0/a2a-response-extraction#extraction-algorithm)を参照してください。

**中間レスポンスの特徴:**

* **TextPart** はステータス表示のため推奨
* **DataPart** は任意だが、提供する場合は AdCP スキーマに準拠
* 中間ステータス用スキーマ（`*-async-response-working.json`、`*-async-response-input-required.json` など）は策定中で変わる可能性あり
* スキーマ進化を踏まえ、中間データの扱いを緩やかにする選択も可能

**最終ステータスになった場合**（`completed`、`failed`、`canceled`、`rejected`）、完全な AdCP タスクレスポンスが `Task` オブジェクトで配信され、DataPart は `.artifacts[0].parts[]` に入ります。

### フレームワークのラッパー（禁止）

**重要**: DataPart の内容はフレームワーク固有オブジェクトでラップせず、AdCP レスポンスペイロードを直接含める必要があります。

```json theme={null}
// ❌ WRONG - Wrapped in custom object
{
  "data": {
    "response": {           // ← Framework wrapper
      "products": [...]
    }
  }
}

// ✅ CORRECT - Direct AdCP payload
{
  "data": {
    "products": [...]       // ← Direct schema-compliant response
  }
}
```

**理由:**

* スキーマ検証が破綻する（クライアントは `products` がルートにあると期待）
* 不要なネストが増える
* プロトコル非依存設計に反する（ラッパーがフレームワーク依存）
* クライアントでのデータ抽出が複雑化

**実装がラッパーを追加している場合**、クライアントサイドで回避するのではなく、フレームワーク層のバグとして修正すべきです。

## クライアントの標準的な扱い

このセクションでは、クライアントが A2A プロトコルレスポンスから AdCP レスポンスを抽出する方法を正確に定義します。

### クイックリファレンス

| Status                | Webhook Type            | Data Location                                       | Schema Required?          | Returns                              |
| --------------------- | ----------------------- | --------------------------------------------------- | ------------------------- | ------------------------------------ |
| `working`             | `TaskStatusUpdateEvent` | `status.message.parts[]`                            | ✅ Yes (if present)        | `{ status, taskId, message, data? }` |
| `submitted`           | `TaskStatusUpdateEvent` | `status.message.parts[]`                            | ✅ Yes (if present)        | `{ status, taskId, message, data? }` |
| `input-required`      | `TaskStatusUpdateEvent` | `status.message.parts[]`                            | ✅ Yes (if present)        | `{ status, taskId, message, data? }` |
| `auth-required` (1.0) | `TaskStatusUpdateEvent` | `status.message.parts[]`                            | ✅ Yes (auth challenge)    | `{ status, taskId, message, data }`  |
| `completed`           | `Task`                  | `.artifacts[]` (fallback: `status.message.parts[]`) | ✅ Required                | `{ status, taskId, message, data }`  |
| `failed`              | `Task`                  | `.artifacts[]` (fallback: `status.message.parts[]`) | ✅ Required                | `{ status, taskId, message, data }`  |
| `rejected` (1.0)      | `Task`                  | `.artifacts[]`                                      | ✅ Required (`adcp_error`) | `{ status, taskId, message, data }`  |

**ポイント**:

* **最終ステータス** は `Task` オブジェクトを用い、データは `.artifacts` に格納。サーバーに構造化ペイロードがない場合（例: JSON-RPC パースエラー、タスク前の認証失敗）、`status.message.parts` にテキストメッセージのみを置くことがある — クライアントはその場所にフォールバックする。
* **中間ステータス** は `TaskStatusUpdateEvent` を用い、`status.message.parts[]` に任意データ。
* **ストリーム/Webhook 配信** はペイロードを A2A 1.0 の `StreamResponse` oneof（`{ task }`、`{ statusUpdate }`、`{ artifactUpdate }`、`{ message }`）でラップする。クライアントはフィールドを読む前にアンラップする。
* いずれのステータスもデータがある場合は AdCP スキーマを使用。
* 中間ステータスのスキーマは策定中で変わる可能性あり。

### ルール1: ステータスに応じた処理

クライアントは、正しいデータ抽出場所を決定するため、正規化されたステータスで分岐しなければなりません。ここで参照する `status` は AdCP の統合小文字値（例: `"completed"`）です。`status.state` の生の A2A ワイヤー値は 1.0 では `TASK_STATE_COMPLETED`、v0.3 では `completed` です。比較する前に正規化してください — [A2A レスポンス抽出](/docs/building/by-layer/L0/a2a-response-extraction#extraction-algorithm)を参照してください。

```javascript theme={null}
const INTERIM = ['working', 'submitted', 'input-required', 'auth-required'];
const FINAL = ['completed', 'failed', 'canceled', 'rejected'];

function handleA2aResponse(response) {
  const status = response.status; // AdCP unified status

  // 中間ステータス - status.message.parts から抽出（TaskStatusUpdateEvent）
  if (INTERIM.includes(status)) {
    return {
      status: status,
      taskId: response.taskId,
      contextId: response.contextId,
      message: extractTextPartFromMessage(response),
      data: extractDataPartFromMessage(response),  // Optional AdCP data (required for auth-required)
    };
  }

  // 最終ステータス - .artifacts から抽出（Task オブジェクト）、status.message にフォールバック
  if (FINAL.includes(status)) {
    return {
      status: status,
      taskId: response.taskId,
      contextId: response.contextId,
      message: extractTextPartFromArtifacts(response) ?? extractTextPartFromMessage(response),
      data: extractDataPartFromArtifacts(response) ?? extractDataPartFromMessage(response),
    };
  }

  // 前方互換: 未知の将来状態は null を返し、throw しない
  return { status, taskId: response.taskId, contextId: response.contextId, message: null, data: null };
}
```

**重要**:

* **中間ステータス**: `TaskStatusUpdateEvent` → `status.message.parts[]` から抽出
* **最終ステータス**: `Task` オブジェクト → `.artifacts[0].parts[]` から抽出。アーティファクトが空の場合は `status.message.parts[]` にフォールバック

### Rule 2: Data Extraction Helpers

Extract data from the appropriate location based on webhook type:

```javascript theme={null}
// Part-type detectors: field presence (A2A 1.0) with kind fallback (v0.3)
const isDataPart = (p) =>
  p.data != null && typeof p.data === 'object' && !Array.isArray(p.data);
const isTextPart = (p) => typeof p.text === 'string';

// For FINAL statuses (Task object) - extract from .artifacts, return null if absent
function extractDataPartFromArtifacts(response) {
  const dataParts = response.artifacts?.[0]?.parts?.filter(isDataPart) || [];
  if (dataParts.length === 0) return null;  // caller falls back to status.message.parts

  // Use LAST data part as authoritative
  const lastDataPart = dataParts[dataParts.length - 1];
  const payload = lastDataPart.data;

  // CRITICAL: Payload MUST be direct AdCP response, not a framework wrapper.
  // A wrapper is a single-key object { response: {...} } — reject it.
  // Objects that have 'response' alongside other keys are NOT wrappers.
  const keys = Object.keys(payload);
  if (keys.length === 1 && keys[0] === 'response' && typeof payload.response === 'object') {
    throw new Error(
      'Invalid response format: DataPart contains wrapper object. ' +
      'Expected direct AdCP payload (e.g., {products: [...]}) ' +
      'but received {response: {products: [...]}}. ' +
      'This is a server-side bug that must be fixed.'
    );
  }

  return payload;
}

function extractTextPartFromArtifacts(response) {
  const textPart = response.artifacts?.[0]?.parts?.find(isTextPart);
  return textPart?.text || null;
}

// For INTERIM statuses (TaskStatusUpdateEvent) - extract from status.message.parts
function extractDataPartFromMessage(response) {
  const dataPart = response.status?.message?.parts?.find(isDataPart);
  return dataPart?.data || null;
}

function extractTextPartFromMessage(response) {
  const textPart = response.status?.message?.parts?.find(isTextPart);
  return textPart?.text || null;
}
```

これらの検出器は両方のワイヤーフォーマットで動作します。1.0 の DataPart は `data` が設定されている（`kind` なし）、v0.3 の DataPart は `kind: "data"` と `data` が設定されている — どちらも `p.data != null` を満たします。

### ルール3: スキーマ検証

すべての AdCP レスポンスはスキーマを用いますが、検証方法はステータスによって異なります:

```javascript theme={null}
function validateResponse(response, taskName) {
  const status = response.status;
  let data, schemaName;

  // ステータスに応じてデータを抽出しスキーマを決定
  if (INTERIM.includes(status)) {
    // 中間: status.message.parts の任意データ
    data = extractDataPartFromMessage(response);

    if (data) {
      // 中間ステータス専用スキーマ（策定中）
      schemaName = `${taskName}-async-response-${status}.json`;

      // 任意: スキーマが変わる可能性があるため中間検証を省略してもよい
      if (STRICT_VALIDATION_MODE) {
        validateAgainstSchema(data, loadSchema(schemaName));
      }
    }
  } else if (FINAL.includes(status)) {
    // 最終: .artifacts から必須データ（status.message.parts にフォールバック）
    data = extractDataPartFromArtifacts(response) ?? extractDataPartFromMessage(response);
    schemaName = `${taskName}-response.json`;

    // 最終レスポンスは必ず検証
    if (!validateAgainstSchema(data, loadSchema(schemaName))) {
      throw new Error(
        `Response payload does not match ${taskName} schema. ` +
        `Ensure DataPart contains direct AdCP response structure.`
      );
    }
  }
}
```

**スキーマ進化の注意**: 中間ステータスのスキーマ（`*-async-response-working.json` など）は策定中です。安定するまでは緩やかな扱いにする選択も可能です。

### 完全な例

Task と TaskStatusUpdateEvent の両方を正しく扱う統合例:

```javascript theme={null}
async function executeTask(taskName, params) {
  const response = await a2aClient.send({
    task: taskName,
    params: params
  });

// 1. ステータスに基づいて正しい場所から抽出
  const result = handleA2aResponse(response);

// 2. スキーマ検証
  validateResponse(response, taskName);

  return result;
}

// 使い方
const result = await executeTask('get_products', {
  brief: 'CTV inventory in California'
});

// ステータス別の処理
if (result.status === 'working') {
  // TaskStatusUpdateEvent - data は status.message.parts
  console.log('Processing:', result.message);
  if (result.data) {
    console.log('Progress:', result.data.percentage + '%');
  }
} else if (result.status === 'input-required') {
  // TaskStatusUpdateEvent - data from status.message.parts
  console.log('Input needed:', result.message);
  console.log('Reason:', result.data?.reason);
} else if (result.status === 'completed') {
  // Task オブジェクト - data は .artifacts
  console.log('Success:', result.message);
  console.log('Products:', result.data.products); // Full AdCP response
}
```

## Last Data Part Authority パターン

<details>
  <summary><strong>このパターンの理由</strong></summary>

  ストリーミング処理では、中間レスポンスに古い進捗データが含まれることがあります:

  ```json theme={null}
  // Working status with progress
  {
    "status": "working",
    "artifacts": [{
      "parts": [
        {"text": "Searching inventory..."},
        {"data": {"progress": 25}}
      ]
    }]
  }

  // Completed - last data part is authoritative
  {
    "status": "completed",
    "artifacts": [{
      "parts": [
        {"text": "Found 12 products"},
        {"data": {"progress": 25}},           // Old
        {"data": {"products": [...], "total": 12}}  // ← Authoritative
      ]
    }]
  }
  ```

  **Note:** This is an AdCP-specific convention, not required by A2A protocol. Document this in your Agent Card when serving non-AdCP clients.
</details>

## Test Cases

### ✅ Correct Behavior

```javascript theme={null}
// Test 1: Working status (TaskStatusUpdateEvent) - extract from status.message.parts
const workingResponse = {
  taskId: 'task_123',
  contextId: 'ctx_456',
  status: {
    state: 'TASK_STATE_WORKING',
    message: {
      role: 'ROLE_AGENT',
      parts: [
        { text: 'Processing inventory...' },
        { data: { percentage: 50, current_step: 'analyzing' } }
      ]
    }
  }
};

const result1 = handleA2aResponse(workingResponse);
assert(result1.data.percentage === 50, 'Should extract data from status.message.parts');
assert(result1.message === 'Processing inventory...', 'Should extract text from status.message.parts');

// Test 2: Completed status (Task) - extract from .artifacts
const completedResponse = {
  taskId: 'task_123',
  contextId: 'ctx_456',
  status: {
    state: 'TASK_STATE_COMPLETED',
    timestamp: '2026-01-22T10:30:00.000Z'
  },
  artifacts: [{
    parts: [
      { text: 'Found 3 products' },
      { data: { products: [...], total: 3 } }
    ]
  }]
};

const result2 = handleA2aResponse(completedResponse);
assert(result2.data !== undefined, 'Completed status must have data');
assert(Array.isArray(result2.data.products), 'Data should be direct AdCP payload');

// Test 3: Wrapper detection (should reject)
const wrappedResponse = {
  taskId: 'task_123',
  status: { state: 'TASK_STATE_COMPLETED' },
  artifacts: [{
    parts: [
      { data: { response: { products: [...] } } }
    ]
  }]
};

assert.throws(() => {
  extractDataPartFromArtifacts(wrappedResponse);
}, /Invalid response format.*wrapper/);
```

### ❌ Incorrect Behavior (Common Mistakes)

```javascript theme={null}
// 誤り: 中間ステータスで抽出元を間違える
function badHandleWorking(response) {
  // ❌ TaskStatusUpdateEvent doesn't have .artifacts - data is in status.message.parts
  const data = response.artifacts?.[0]?.parts?.find(isDataPart)?.data;
  return { status: 'working', data }; // Will be null/undefined!
}

// 誤り: completed で抽出元を間違える
function badHandleCompleted(response) {
  // ❌ Task object has data in .artifacts, not in status.message.parts
  const data = response.status?.message?.parts?.find(p => p.data)?.data;
  return { status: 'completed', data }; // Will be null/undefined!
}

// 誤り: ラッパーを確認しない
function badExtraction(response) {
  const payload = response.artifacts[0].parts[0].data;
  // ❌ Returns { response: { products: [...] } } instead of { products: [...] }
  return payload; // Client receives wrong structure!
}

// 誤り: ネストされた response を参照
function badClientUsage(result) {
  // ❌ クライアントコードがこうする必要はない
  const products = result.data.response.products;
  // 正しくは: result.data.products
}
```

## エラーハンドリング

### タスクレベルのエラー（部分失敗）

タスクは実行されたが完全には完了しなかった場合。`status: "completed"` の DataPart に `errors` 配列を入れます:

```json theme={null}
{
  "status": "completed",
  "taskId": "task_123",
  "artifacts": [{
    "parts": [
      {
        "text": "Signal discovery completed with partial results"
      },
      {
        "data": {
          "signals": [...],
          "errors": [{
            "code": "NO_DATA_IN_REGION",
            "message": "No signal data available for Australia",
            "field": "deliver_to.countries[1]",
            "details": {
              "requested_country": "AU",
              "available_countries": ["US", "CA", "GB"]
            }
          }]
        }
      }
    ]
  }]
}
```

**errors 配列を使う場面:**

* プラットフォーム認可の問題（`PLATFORM_UNAUTHORIZED`）
* データが部分的にしかない場合
* データの一部でバリデーション問題がある場合

### プロトコルレベルのエラー（致命的）

タスクが実行できなかった場合。`status: "failed"` とメッセージを返します:

```json theme={null}
{
  "taskId": "task_456",
  "status": "failed",
  "message": {
    "role": "ROLE_AGENT",
    "parts": [{
      "text": "Authentication failed: Invalid or expired API token"
    }]
  }
}
```

**`status: failed` を使う場面:**

* 認証失敗（無効/期限切れトークン）
* リクエスト不正（JSON 破損、必須フィールド欠落）
* リソース不在（未知の taskId、期限切れ context）
* システムエラー（DB 不調、内部サービス障害）

### エラーの所在: 決定ルール

配置は、サーバーが何を持っているか、どの状態にあるかで選択されます:

| 状況                           | 状態          | 場所                                | ペイロード                                 |
| ---------------------------- | ----------- | --------------------------------- | ------------------------------------- |
| タスク実行、一部失敗                   | `completed` | `artifacts[0].parts[]` DataPart   | `{ <success_fields>, errors: [...] }` |
| 構造化エラーで失敗                    | `failed`    | `artifacts[0].parts[]` DataPart   | `{ adcp_error: {...} }`               |
| ポリシー/検証による拒否（1.0）            | `rejected`  | `artifacts[0].parts[]` DataPart   | `{ adcp_error: {...} }`               |
| システム起因のキャンセル（タイムアウト、上流障害）    | `canceled`  | `artifacts[0].parts[]` DataPart   | `{ adcp_error: {...} }`               |
| ユーザー起因のキャンセル（`tasks/cancel`） | `canceled`  | `status.message.parts[]` TextPart | 人間可読テキストのみ                            |
| プロトコル/トランスポート障害、アーティファクト未生成  | `failed`    | `status.message.parts[]` TextPart | 人間可読テキストのみ                            |

**目安:** サーバーが構造化エラーデータを持つ場合、それを DataPart としてアーティファクトに入れる。`status.message` は、タスクアーティファクトが一度も生成されなかったケース（JSON-RPC パースエラー、認証ハンドシェイク失敗、不正リクエスト、詳細のないユーザー起因キャンセル）向けのフリーテキストフォールバックだ。A2A 1.0 §3.7 もこれを補強する: *「メッセージはタスク出力の配信に使うべきではない。結果はアーティファクトで返すべきである。」*

**`rejected` vs `failed`。** サーバーがタスクの試行を拒否する場合（作業開始前のポリシー/ティア/検証チェック）は `rejected` を使う。作業が開始されて致命的なエラーに遭遇した場合は `failed` を使う。どちらもアーティファクトに `adcp_error` を運ぶ — 状態は障害が*いつ*発生したかを区別し、それが呼び出し元側で異なるリトライと UX 挙動を駆動する。

**キャンセル起源はセラー帰属ではなくクライアントで照合される。** `status.state: "canceled"`（または `TASK_STATE_CANCELED`）は、キャンセルがユーザー起因かシステム起因かを呼び出し元に伝えない — セラーは、実際にはユーザー起因だったキャンセルについて、バイヤーの帳簿やリトライロジックを誤らせるために `adcp_error` をアーティファクトに置くこともできる。クライアントはキャンセル起源をローカルで照合しなければなりません（MUST）: この `taskId` について未処理の `tasks/cancel` リクエストがある場合、ペイロードに関わらずキャンセルをユーザー起因として扱い、セラーが付加した `adcp_error` を無視します。クライアントは、セラーが送った `adcp_error.recovery` ヒントを根拠にユーザー起因のキャンセルをリトライしてはなりません（MUST NOT）。

## ステータスマッピング

AdCP は A2A の TaskState enum をそのまま使用します:

| A2A Status            | Payload Type            | Data Location                                    | AdCP Usage                                                           |
| --------------------- | ----------------------- | ------------------------------------------------ | -------------------------------------------------------------------- |
| `completed`           | `Task`                  | `.artifacts`                                     | Task finished successfully, data in DataPart, optional errors array  |
| `failed`              | `Task`                  | `.artifacts` (or `status.message` for text-only) | Fatal error preventing completion, `adcp_error` when structured      |
| `rejected` (1.0)      | `Task`                  | `.artifacts`                                     | Policy/validation rejection, `adcp_error` with rejection reason      |
| `canceled`            | `Task`                  | `.artifacts` (typically none)                    | Task canceled by user or system                                      |
| `input-required`      | `TaskStatusUpdateEvent` | `status.message.parts`                           | Need user input/approval, data + text explaining what's needed       |
| `auth-required` (1.0) | `TaskStatusUpdateEvent` | `status.message.parts`                           | Authentication challenge during task execution (scheme, URL, scopes) |
| `working`             | `TaskStatusUpdateEvent` | `status.message.parts`                           | Processing (\< 120s), optional progress data                         |
| `submitted`           | `TaskStatusUpdateEvent` | `status.message.parts`                           | Long-running (hours/days), minimal data, use webhooks/polling        |

## Webhook ペイロード

非同期処理（`status: "submitted"`）では Webhook でも同じアーティファクト構造を返します:

```json theme={null}
POST /webhook-endpoint
{
  "taskId": "task_123",
  "status": "completed",
  "timestamp": "2026-01-22T10:30:00.000Z",
  "artifacts": [{
    "parts": [
      {"text": "Media buy approved and live"},
      {"data": {
        "media_buy_id": "mb_456",
        "packages": [...],
        "creative_deadline": "2026-01-30T23:59:59.000Z"
      }}
    ]
  }]
}
```

AdCP データは同じ Last DataPart パターンで抽出します。**Webhook 認証、リトライパターン、セキュリティ** は [Webhooks](/docs/building/by-layer/L3/webhooks) を参照してください。

## レスポンス内の File Part

クリエイティブ系の操作ではファイル参照を含む場合があります:

```json theme={null}
{
  "status": "completed",
  "artifacts": [{
    "parts": [
      {"text": "Creative uploaded and preview generated"},
      {"data": {
        "creative_id": "cr_789",
        "format_id": {
          "agent_url": "https://creatives.adcontextprotocol.org",
          "id": "video_standard_30s"
        },
        "status": "ready"
      }},
      {"url": "https://cdn.example.com/cr_789/preview.mp4", "filename": "preview.mp4", "mediaType": "video/mp4"}
    ]
  }]
}
```

**File Part の用途:** プレビュー URL、生成済みアセット、トラフィッキングレポート。**AdCP レスポンスの生データには使わず**、必ず DataPart を使用。

## リトライと冪等性

### TaskId による重複排除

A2A の `taskId` はリトライ検出に使えます。エージェントは次を行うべきです:

* `taskId` が完了済みオペレーションと一致する場合（TTL 内）、キャッシュレスポンスを返す
* 進行中のオペレーションに対する重複 `taskId` 送信は拒否します

```json theme={null}
// Duplicate taskId during active operation
{
  "taskId": "task_123",
  "status": "failed",
  "message": {
    "role": "ROLE_AGENT",
    "parts": [{
      "text": "Task 'task_123' is already in progress. Use tasks/get to check status."
    }]
  }
}
```

## 例

<details>
  <summary><strong>Product Discovery 成功</strong></summary>

  ```json theme={null}
  {
    "status": "completed",
    "taskId": "task_001",
    "contextId": "ctx_abc",
    "artifacts": [{
      "name": "product_catalog",
      "parts": [
        {
          "text": "Found 8 CTV products targeting sports fans under $50 CPM"
        },
        {
          "data": {
            "products": [
              {
                "product_id": "ctv_sports_premium",
                "name": "Premium Sports CTV"
              }
              // ... 7 more products
            ]
          }
        }
      ]
    }]
  }
  ```
</details>

<details>
  <summary><strong>承認が必要な Media Buy</strong></summary>

  ```json theme={null}
  {
    "status": "input-required",
    "taskId": "task_002",
    "contextId": "ctx_def",
    "artifacts": [{
      "name": "approval_request",
      "parts": [
        {
          "text": "Media buy exceeds auto-approval limit ($100K). Please approve to proceed."
        },
        {
          "data": {
            "media_buy_id": "mb_pending_456",
            "packages": [
              {
                "package_id": "pkg_pending_001",
                "status": "pending_approval"
              },
              {
                "package_id": "pkg_pending_002",
                "status": "pending_approval"
              }
            ],
            "creative_deadline": "2025-02-01T23:59:59Z"
          }
        }
      ]
    }]
  }
  ```
</details>

<details>
  <summary><strong>部分的失敗を含む Signal Discovery</strong></summary>

  ```json theme={null}
  {
    "status": "completed",
    "taskId": "task_003",
    "contextId": "ctx_ghi",
    "artifacts": [{
      "name": "signal_results",
      "parts": [
        {
          "text": "Found 3 signals for luxury automotive. Note: No data available for Australia region."
        },
        {
          "data": {
            "signals": [
              {
                "signal_id": "lux_auto_us",
                "name": "Luxury Auto Intenders - US",
                "reach": 2500000
              }
            ],
            "total": 3,
            "errors": [{
              "code": "NO_DATA_IN_REGION",
              "message": "No signal data available for requested region: Australia",
              "field": "deliver_to.countries[1]",
              "details": {
                "requested_country": "AU",
                "available_countries": ["US", "CA", "GB"]
              }
            }]
          }
        }
      ]
    }]
  }
  ```
</details>

<details>
  <summary><strong>プラットフォーム認可の問題（タスクレベルエラー）</strong></summary>

  プラットフォームや操作固有の認可失敗はタスクレベルのエラーです:

  ```json theme={null}
  {
    "status": "completed",
    "taskId": "task_004",
    "contextId": "ctx_jkl",
    "artifacts": [{
      "name": "signal_activation_result",
      "parts": [
        {
          "text": "Signal activation failed: Account not authorized for Peer39 data on PubMatic"
        },
        {
          "data": {
            "errors": [{
              "code": "PLATFORM_UNAUTHORIZED",
              "message": "Account 'brand-456-pm' not authorized for Peer39 data on PubMatic. Contact your PubMatic account manager to enable access.",
              "details": {
                "platform": "pubmatic",
                "account_id": "brand-456-pm",
                "data_provider": "peer39"
              }
            }]
          }
        }
      ]
    }]
  }
  ```
</details>

<details>
  <summary><strong>プロトコルレベルの失敗（致命的）</strong></summary>

  認証失敗はプロトコルレベルのエラーです:

  ```json theme={null}
  {
    "taskId": "task_005",
    "status": "failed",
    "message": {
      "parts": [{
        "text": "Authentication failed: Invalid or expired API token. Please refresh your credentials and retry."
      }]
    }
  }
  ```
</details>

## Implementation Checklist

When implementing A2A responses for AdCP:

**Final Responses (status: "completed" or "failed") - Use `Task` object:**

* [ ] **Always include status field** from TaskState enum
* [ ] **Use `.artifacts` array with at least one DataPart** containing AdCP response payload
* [ ] **Include TextPart** with human-readable message (recommended for UX)
* [ ] **Use single artifact with multiple parts** (not multiple artifacts)
* [ ] **Use last DataPart as authoritative** if multiple exist
* [ ] **Never nest AdCP data in custom wrappers** (no `{ response: {...} }` objects)
* [ ] **DataPart content MUST match AdCP schemas** (validate against `[task]-response.json`)

**Interim Responses (status: "working", "submitted", "input-required") - Use `TaskStatusUpdateEvent`:**

* [ ] **Use `status.message.parts[]` for optional data** (not `.artifacts`)
* [ ] **TextPart** is recommended for human-readable status updates
* [ ] **DataPart** is optional but follows AdCP schemas when provided (`[task]-async-response-[status].json`)
* [ ] **Interim schemas are work-in-progress** - clients may handle more loosely
* [ ] **Include progress indicators** when applicable (percentage, current\_step, ETA)

**Error Handling:**

* [ ] **Use `status: "failed"` for protocol errors only** (auth, invalid params, system errors)
* [ ] **Use `errors` array for task failures** (platform auth, partial data) with `status: "completed"`

**General:**

* [ ] **Include taskId and contextId** for tracking
* [ ] **Follow discriminated union patterns** for task responses (check schemas)
* [ ] **Use correct payload type**: `Task` for final states, `TaskStatusUpdateEvent` for interim
* [ ] **Support taskId-based deduplication** for retry detection

## See Also

* [A2A Guide](/docs/building/by-layer/L0/a2a-guide) - Complete A2A integration guide
* [Task Lifecycle](/docs/building/by-layer/L3/task-lifecycle) - Status handling patterns
* [Error Handling](/docs/building/by-layer/L3/error-handling) - Fatal vs non-fatal errors
* [Protocol Comparison](/docs/building/concepts/protocol-comparison) - MCP vs A2A differences
