Skip to main content

Task Management

AdCP provides unified task management for tracking all async operations across media-buy and signals domains. This enables state reconciliation, progress monitoring, and webhook integration for long-running operations.

Overview

AdCP operations may complete immediately (~1 second), take processing time (~60 seconds), or require extended execution (minutes to days). Task management provides visibility and control over these async operations regardless of their domain or complexity. Core Tasks:
  • tasks/list - List and filter async operations for state reconciliation
  • tasks/get - Poll specific tasks for status, progress, and results
Key Features:
  • Cross-Domain Coverage: Works for both media-buy and signals operations
  • State Reconciliation: Prevents lost or orphaned operations
  • Webhook Integration: Optional push notifications for completion
  • Progress Tracking: Real-time status updates for active operations
  • Error Recovery: Detailed error information and retry guidance

Task Status Lifecycle

All AdCP operations follow a consistent status progression:

Status Definitions

StatusDescriptionDurationClient Action
submittedQueued for long-running executionHours to daysPoll periodically or wait for webhook
workingActively processing< 120 secondsPoll frequently (5-10s intervals)
input-requiredPaused, waiting for user inputUntil input providedRead message, provide clarification
completedSuccessfully finishedFinalProcess results, stop polling
failedFailed due to errorFinalHandle error, potentially retry
canceledCanceled by userFinalClean up, stop polling
rejectedRejected before startingFinalReview rejection reason
auth-requiredNeeds authenticationUntil auth providedProvide credentials
unknownIndeterminate stateVariableCheck again or escalate

Status Transitions

Immediate Completion: Simple operations may skip to completed without intermediate states. Working vs Submitted: working indicates active processing with completion expected within 120 seconds, while submitted indicates queued execution that may take much longer.

tasks/list

List and filter async tasks across your account to enable state reconciliation and operation tracking. Response Time: ~1 second (simple database lookup) Request Schema: https://adcontextprotocol.org/schemas/v1/core/tasks-list-request.json Response Schema: https://adcontextprotocol.org/schemas/v1/core/tasks-list-response.json

Request Parameters

ParameterTypeRequiredDescription
filtersobjectNoFilter criteria for querying tasks
sortobjectNoSorting parameters
paginationobjectNoPagination controls
include_historybooleanNoInclude full conversation history for each task (default: false)

Filtering Options

Status and Type Filtering

{
  "filters": {
    "status": "submitted",
    "statuses": ["submitted", "working", "input-required"],
    "task_type": "create_media_buy",
    "task_types": ["create_media_buy", "activate_signal"]
  }
}

Domain-Specific Filtering

{
  "filters": {
    "domain": "media-buy",           // Filter by domain
    "domains": ["media-buy", "signals"],
    "task_types": [
      "create_media_buy",
      "update_media_buy", 
      "sync_creatives",
      "activate_signal",
      "get_signals"
    ]
  }
}

Date and Context Filtering

{
  "filters": {
    "created_after": "2025-01-01T00:00:00Z",
    "created_before": "2025-01-31T23:59:59Z",
    "updated_after": "2025-01-20T00:00:00Z",
    "context_contains": "nike_q1_2025",
    "task_ids": ["task_456", "task_789"],
    "has_webhook": true
  }
}

Response Structure

{
  "message": "Found 27 tasks matching your criteria. 15 are pending and may need attention.",
  "context_id": "ctx-123",
  "query_summary": {
    "total_matching": 27,
    "returned": 20,
    "domain_breakdown": {
      "media-buy": 18,
      "signals": 9
    },
    "status_breakdown": {
      "submitted": 12,
      "working": 3,
      "completed": 8,
      "failed": 4
    }
  },
  "tasks": [
    {
      "task_id": "task_456",
      "task_type": "create_media_buy",
      "domain": "media-buy",
      "status": "submitted",
      "created_at": "2025-01-22T10:00:00Z",
      "updated_at": "2025-01-22T10:00:00Z",
      "message": "Media buy requires manual approval for $150K campaign",
      "has_webhook": true
    },
    {
      "task_id": "task_789",
      "task_type": "activate_signal",
      "domain": "signals",
      "status": "completed",
      "created_at": "2025-01-22T09:45:00Z",
      "completed_at": "2025-01-22T09:46:00Z",
      "message": "Signal sent successfully to 3 endpoints",
      "has_webhook": false
    }
  ],
  "pagination": {
    "limit": 20,
    "offset": 0,
    "has_more": true,
    "next_offset": 20
  }
}

Common Use Cases

State Reconciliation

Find all pending operations across domains:
{
  "filters": {
    "statuses": ["submitted", "working", "input-required"]
  },
  "sort": {
    "field": "created_at",
    "direction": "asc"
  }
}

Domain-Specific Monitoring

Track only media-buy operations:
{
  "filters": {
    "domain": "media-buy",
    "statuses": ["submitted", "working"]
  }
}

Operations Needing Attention

{
  "filters": {
    "statuses": ["input-required", "failed"],
    "updated_before": "2025-01-20T00:00:00Z"  // Old stuck tasks
  }
}

Lost Connection Recovery

Recover from lost task submissions by examining conversation history:
{
  "filters": {
    "created_after": "2025-01-22T10:00:00Z"
  },
  "include_history": true
}
Then check history[0].data to see original requests and identify your lost task:
tasks.tasks.forEach(task => {
  const originalRequest = task.history?.[0]?.data;
  if (originalRequest?.buyer_ref === "nike_q1_2025") {
    console.log(`Found my lost task: ${task.task_id}`);
  }
});

tasks/get

Poll a specific task by ID to check status, progress, and retrieve results when complete. Response Time: ~1 second (simple database lookup) Request Schema: https://adcontextprotocol.org/schemas/v1/core/tasks-get-request.json Response Schema: https://adcontextprotocol.org/schemas/v1/core/tasks-get-response.json

Request Parameters

ParameterTypeRequiredDescription
task_idstringYesUnique identifier of the task to retrieve
include_historybooleanNoInclude full conversation history for this task (default: false)

Response Structure

Basic Task Information

{
  "message": "Media buy creation is 75% complete. Currently validating inventory availability.",
  "context_id": "ctx-123",
  "task_id": "task_456",
  "task_type": "create_media_buy",
  "domain": "media-buy",
  "status": "working",
  "created_at": "2025-01-22T10:00:00Z",
  "updated_at": "2025-01-22T10:15:00Z",
}

Progress Information

{
  "progress": {
    "percentage": 75,
    "current_step": "validating_inventory_availability",
    "total_steps": 4,
    "step_number": 3
  }
}

Task with Conversation History

{
  "status": "completed",
  "completed_at": "2025-01-22T10:25:00Z",
  "history": [
    {
      "timestamp": "2025-01-22T10:00:00Z",
      "type": "request",
      "data": {
        "buyer_ref": "nike_q1_2025",
        "brief": "Premium CTV inventory for Nike campaign"
      }
    },
    {
      "timestamp": "2025-01-22T10:25:00Z",
      "type": "response",
      "data": {
        "media_buy_id": "mb_987654321",
        "packages": [{ "package_id": "pkg_abc123" }]
      }
    }
  ]
}

Failed Task Errors

{
  "status": "failed",
  "completed_at": "2025-01-22T10:20:00Z",
  "error": {
    "code": "insufficient_inventory",
    "message": "Requested targeting yielded 0 available impressions",
    "details": {
      "domain": "media-buy",
      "operation": "create_media_buy",
      "specific_context": {}
    }
  }
}

Polling Patterns

Basic Polling Loop

async function pollTask(taskId) {
  while (true) {
    const response = await session.call('tasks/get', { 
      task_id: taskId
    });
    
    switch (response.status) {
      case 'completed':
        return response; // Get full result from history if needed
        
      case 'failed':
        throw new Error(`Task failed: ${response.error.message}`);
        
      case 'input-required':
        const input = await promptUser(response.message);
        // Continue conversation with same context_id
        return await handleUserInput(response.context_id, input);
        
      case 'working':
        console.log(`Progress: ${response.progress?.percentage || 0}%`);
        await sleep(5000); // Poll working tasks frequently
        break;
        
      case 'submitted':
        console.log(`Task queued for long-running execution`);
        await sleep(60000); // Poll submitted tasks less frequently
        break;
    }
  }
}

Smart Polling with Domain Awareness

async function smartPollTask(taskId) {
  let pollInterval = 2000;
  const maxInterval = 60000;
  
  while (true) {
    const response = await session.call('tasks/get', { task_id: taskId });
    
    if (['completed', 'failed', 'canceled'].includes(response.status)) {
      return response;
    }
    
    // Adjust polling based on domain and status
    if (response.status === 'working') {
      pollInterval = response.domain === 'signals' ? 2000 : 5000;
    } else if (response.status === 'submitted') {
      pollInterval = response.domain === 'media-buy' ? 60000 : 30000;
    }
    
    await sleep(Math.min(pollInterval, maxInterval));
  }
}

Protocol Integration

Task management works consistently across all AdCP protocols.

MCP Integration

// List tasks
const taskList = await session.call('tasks/list', {
  filters: { statuses: ['submitted', 'working'] }
});

// Poll specific task
const taskStatus = await session.call('tasks/get', {
  task_id: 'task_456'
});

A2A Integration

Natural Language

await a2a.send({
  message: {
    parts: [{
      kind: "text",
      text: "Show me all pending media buy tasks from the last week"
    }]
  }
});

Explicit Skill Invocation

await a2a.send({
  message: {
    parts: [{
      kind: "data",
      data: {
        skill: "tasks/list",
        parameters: {
          filters: {
            domain: "media-buy",
            statuses: ["submitted", "working"],
            created_after: "2025-01-15T00:00:00Z"
          }
        }
      }
    }]
  }
});

Webhook Integration

Task management integrates with protocol-level webhook configuration for push notifications.

Webhook Configuration

Configure webhooks at the protocol level when making async task calls. See Core Concepts: Protocol-Level Webhook Configuration for complete setup examples. Quick example:
const response = await session.call('create_media_buy',
  { /* task params */ },
  {
    push_notification_config: {
      url: "https://buyer.com/webhooks/adcp/create_media_buy/agent_id/operation_id",
      authentication: {
        schemes: ["HMAC-SHA256"],  // or ["Bearer"] for simple auth
        credentials: "shared_secret_32_chars"
      }
    }
  }
);

Webhook POST Format

When a task’s status changes, the publisher POSTs a payload with protocol fields at the top-level and the task-specific payload nested under result to your webhook URL. Webhook Payload Schema: https://adcontextprotocol.org/schemas/v1/core/webhook-payload.json Top-level fields:
  • operation_id (required) — Correlates a sequence of updates for this operation
  • domain - AdCP domain (“media-buy” or “signals”)
  • task_type (required) — e.g., “create_media_buy”, “sync_creatives”, “activate_signal”
  • status (required) — Current task status
  • task_id (optional) — Present when server exposes task tracking id
  • context_id (optional) — Conversation/session id
  • message (optional) — Human-readable context
  • timestamp (optional) — ISO 8601 time when webhook was generated
  • result (optional) — Task-specific payload for this status
  • error (optional) — Error message string when status is failed
Webhook trigger rule: Webhooks are ONLY used when the initial response status is submitted (long-running operations). When webhooks are NOT triggered:
  • Initial response is completed, failed, or rejected → Synchronous response, client already has result
  • Initial response is working → Will complete synchronously within ~120 seconds, client should wait for response
When webhooks ARE triggered (for submitted operations only):
  • Status changes to input-required → Webhook called (alerts that human input needed)
  • Status changes to completed → Webhook called (final result available)
  • Status changes to failed → Webhook called (error details provided)
  • Status changes to canceled → Webhook called (cancellation confirmed)
Example: input-required webhook (human approval needed):
POST /webhooks/adcp/create_media_buy/agent_123/op_456 HTTP/1.1
Host: buyer.example.com
Authorization: Bearer your-secret-token
Content-Type: application/json

{
  "operation_id": "op_456",
  "task_id": "task_456",
  "task_type": "create_media_buy",
  "domain": "media-buy",
  "status": "input-required",
  "timestamp": "2025-01-22T10:15:00Z",
  "context_id": "ctx_abc123",
  "message": "Campaign budget $150K requires VP approval to proceed",
  "result": {
    "buyer_ref": "nike_q1_campaign_2024"
  }
}
Example: completed webhook (after approval granted - full create_media_buy response):
POST /webhooks/adcp/create_media_buy/agent_123/op_456 HTTP/1.1
Host: buyer.example.com
Authorization: Bearer your-secret-token
Content-Type: application/json

{
  "operation_id": "op_456",
  "task_id": "task_456",
  "task_type": "create_media_buy",
  "domain": "media-buy",
  "status": "completed",
  "timestamp": "2025-01-22T10:30:00Z",
  "message": "Media buy created successfully with 2 packages ready for creative assignment",
  "result": {
    "media_buy_id": "mb_12345",
    "buyer_ref": "nike_q1_campaign_2024",
    "creative_deadline": "2024-01-30T23:59:59Z",
    "packages": [
      { "package_id": "pkg_12345_001", "buyer_ref": "nike_ctv_package" }
    ]
  }
}
The webhook receives the full response object for each status, not just a notification. This means your webhook handler gets all the context and data needed to take appropriate action.

Webhook Handling Example

app.post('/webhooks/adcp/:task_type/:agent_id/:operation_id', async (req, res) => {
  const { task_type, agent_id, operation_id } = req.params;
  const response = req.body;

  // Webhooks are only called for 'submitted' operations
  // So we only need to handle status changes that occur after submission
  switch (response.status) {
    case 'input-required':
      // Alert human that input is needed
      await notifyHuman({
        operation_id,
        message: response.message,
        context_id: response.context_id,
        approval_data: response.data
      });
      break;

    case 'completed':
      // Process the completed operation
      if (task_type === 'create_media_buy') {
        await handleMediaBuyCreated({
          media_buy_id: response.result?.media_buy_id,
          buyer_ref: response.result?.buyer_ref,
          packages: response.result?.packages,
          creative_deadline: response.result?.creative_deadline
        });
      }
      break;

    case 'failed':
      // Handle failure
      await handleOperationFailed({
        operation_id,
        error: response.error,
        message: response.message
      });
      break;

    case 'canceled':
      // Handle cancellation
      await handleOperationCanceled(operation_id, response.message);
      break;
  }

  res.status(200).json({ status: 'processed' });
});

Webhook Reliability

Important: Webhooks use at-least-once delivery semantics and may be duplicated or arrive out of order. See Core Concepts: Webhook Reliability for detailed implementation guidance including:
  • Idempotent webhook handlers
  • Sequence handling and out-of-order detection
  • Security considerations (signature verification)
  • Polling as backup mechanism
  • Replay attack prevention

Error Handling

Common Error Scenarios

  1. Task Not Found: Invalid task ID or access permissions
  2. Invalid Filters: Malformed filter criteria in tasks/list
  3. Pagination Errors: Invalid offset or limit values
  4. Permission Denied: Task exists but user lacks access

Error Response Format

{
  "status": "failed",
  "message": "Task not found or access denied",
  "context_id": "ctx-123",
  "errors": [{
    "code": "task_not_found", 
    "message": "No task found with ID 'task_456' for this account",
    "field": "task_id"
  }]
}

Best Practices

State Reconciliation

  • Run tasks/list with pending filters during application startup
  • Check for old tasks that may be stuck in submitted status
  • Use domain filtering to focus on relevant operation types
  • Include webhook status to understand notification expectations

Performance Optimization

  • Use pagination for accounts with many operations
  • Filter by date ranges to limit results to relevant periods
  • Use include_history: false by default to keep responses lightweight
  • Implement exponential backoff for polling loops

Monitoring and Alerting

  • Monitor input-required tasks for user attention needs
  • Alert on tasks stuck in submitted status beyond expected duration
  • Track failed tasks for error reporting and system health
  • Use domain breakdown to understand operation distribution

Integration Patterns

  • Store task IDs with your application entities for later reference
  • Use webhooks as primary notification mechanism, polling as backup
  • Implement proper error handling for both webhook and polling failures
  • Consider domain-specific polling intervals and timeout values