AgentFieldbuild

Deferred execution

Queue long-running executions, get an immediate tracking ID, and finish with polling or webhooks

Fire and forget — async execution with webhook callback

Fire an execution without waiting for the result. Come back later -- or let a webhook tell you when it finishes.

Request/response is the wrong abstraction for many AI workloads. Model latency varies, multi-agent chains compound it, and long-running work should not tie up an HTTP request.

This page covers deferred execution: POST /api/v1/execute/async/{target} queues work and returns immediately. Synchronous execution still exists at POST /api/v1/execute/{target} when you want the result inline.

AgentField's deferred execution model pushes work to a durable queue, returns a tracking ID immediately, and lets you finish with polling, batch status, or a webhook.

import asyncio
from agentfield import Agent

app = Agent(node_id="orchestrator", version="1.0.0")

@app.reasoner()
async def generate_quarterly_report(quarter: str) -> dict:
    # Fire three analyses in parallel — async execution is automatic
    financial, market, customer = await asyncio.gather(
        app.call("finance.deep_analysis", quarter=quarter),
        app.call("research.market_trends", quarter=quarter),
        app.call("analytics.churn_report", quarter=quarter),
    )

    return {"sections": [financial, market, customer]}

What just happened

  • Three long-running sub-tasks were queued immediately instead of blocking the request
  • The caller got stable tracking IDs for later inspection
  • Completion could be handled by polling, batch waiting, or webhook delivery

Example lifecycle:

{ "execution_id": "exec_a1b2c3", "status": "queued" }
{ "execution_id": "exec_a1b2c3", "status": "running" }
{ "execution_id": "exec_a1b2c3", "status": "succeeded", "duration_ms": 14200 }

Session tool calls

When a session invokes a tool — either via session.call(...) in the handler or via a tools=[...] allowlist call from the live realtime model — the control plane forwards the work to execute/async with X-Session-ID and X-Parent-Execution-ID attached. The resulting execution belongs to the session and appears in its workflow DAG alongside any other reasoner work. See Workflow tracing.

What you get
  • Fire-and-forget dispatch -- POST /api/v1/execute/async/{target} returns 202 Accepted immediately with a tracking ID
  • Durable PostgreSQL queue -- executions survive control plane restarts; nothing is lost
  • Polling and batch status -- check one execution or hundreds at once
  • Fair scheduling -- a configurable worker pool prevents any single agent from starving others
  • No timeout ceiling -- async executions run until they complete, fail, or are cancelled
  • Webhook delivery -- optional completion webhook fires when the execution reaches a terminal state
Full SDK examples
import asyncio
from agentfield import Agent

app = Agent(node_id="orchestrator", version="1.0.0")

@app.reasoner()
async def orchestrate(task: str) -> dict:
    # app.call() uses async execution automatically when connected to the control plane.
    # It submits the execution, polls for results, and returns the final output.
    result = await app.call("researcher.deep_dive", topic=task)
    return result

app.run()
Execution lifecycle

Every execution moves through a defined set of states:

StatusDescription
pendingCreated but not yet enqueued
queuedSitting in the durable PostgreSQL queue
runningDispatched to the target agent and executing
waitingPaused for human approval (see Human-in-the-Loop)
pausedTemporarily paused via API; can be resumed
succeededCompleted successfully with a result
failedCompleted with an error
timeoutExceeded the configured timeout
cancelledExplicitly cancelled via API
Worker pool architecture

The control plane runs a configurable worker pool for async executions. When POST /execute/async/{target} arrives:

  1. An execution record is persisted to PostgreSQL (durable -- survives restarts)
  2. The execution is enqueued to the in-memory worker pool
  3. A worker picks up the job and forwards the request to the target agent node
  4. The agent processes the request and posts a status update back to the control plane
  5. If a webhook was registered, the webhook dispatcher fires the completion notification

The pool size, queue depth, and agent call timeout are all configurable:

# agentfield.yaml
agentfield:
  execution_queue:
    agent_call_timeout: 90s # default: 1800s (30 minutes)
Patterns

Fan-Out / Fan-In

Fire multiple executions in parallel, then collect results:

import asyncio

@app.reasoner()
async def analyze_portfolio(stocks: list[str]) -> dict:
    # Fan out — app.call() handles async execution and polling internally
    tasks = [
        app.call("stock-analyzer.analyze", symbol=symbol)
        for symbol in stocks
    ]

    # Fan in — collect all results
    results = await asyncio.gather(*tasks)
    return {symbol: result for symbol, result in zip(stocks, results)}

Webhook Instead of Polling

Register a webhook at submission time to avoid polling entirely:

{
  "input": { "document": "..." },
  "webhook": {
    "url": "https://your-api.com/hooks/execution-done",
    "secret": "whsec_your_hmac_secret",
    "headers": { "Authorization": "Bearer tok_..." }
  }
}

The control plane will POST to that URL with HMAC-SHA256 signature verification when the execution reaches a terminal state. See Webhooks for details.

AsyncConfig

Configure async execution behavior in agentfield.yaml under execution_queue.

Configuration Fields

FieldTypeDefaultDescription
agent_call_timeoutduration1800sTimeout for the control plane's call to the target agent
webhook_timeoutduration10sTimeout for webhook delivery requests
webhook_max_attemptsint3Maximum number of webhook delivery attempts
webhook_retry_backoffduration1sInitial backoff between webhook retry attempts
webhook_max_retry_backoffduration5sMaximum backoff between webhook retry attempts
# agentfield.yaml
agentfield:
  execution_queue:
    agent_call_timeout: 1800s
    webhook_timeout: 10s
    webhook_max_attempts: 3
    webhook_retry_backoff: 1s
    webhook_max_retry_backoff: 5s

Queue Management Methods

Beyond polling and batch status, the SDK provides methods for managing the async queue.

from agentfield import Agent

app = Agent(node_id="orchestrator", version="1.0.0")

# Cancel a running or queued execution
await app.client.cancel_async_execution("exec_a1b2c3")

# List async executions with optional status filter
executions = await app.client.list_async_executions(
    status_filter="running",   # filter by status
    limit=50,
)
for e in executions:
    print(f"{e['execution_id']} -> {e['status']} ({e['target']})")

# Get queue health metrics
metrics = await app.client.get_async_execution_metrics()
print(metrics)
API reference

Submit Async Execution

POST /api/v1/execute/async/{target}

The {target} is the agent's function identifier in node_id.reasoner_id or node_id.skill_id format.

Request body:

{
  "input": { "topic": "quantum computing" },
  "context": { "session_id": "sess_abc" },
  "webhook": {
    "url": "https://example.com/hooks/complete",
    "secret": "whsec_...",
    "headers": { "X-Custom": "value" }
  }
}

Response (202 Accepted):

{
  "execution_id": "exec_a1b2c3",
  "run_id": "run_x7y8z9",
  "workflow_id": "run_x7y8z9",
  "status": "queued",
  "target": "researcher.deep_dive",
  "type": "reasoner",
  "created_at": "2026-03-23T10:00:00Z",
  "enqueued_at": "2026-03-23T10:00:00Z",
  "webhook_registered": true
}

Poll Execution Status

GET /api/v1/executions/{execution_id}

Response:

{
  "execution_id": "exec_a1b2c3",
  "run_id": "run_x7y8z9",
  "status": "succeeded",
  "result": { "summary": "..." },
  "started_at": "2026-03-23T10:00:01Z",
  "completed_at": "2026-03-23T10:00:15Z",
  "duration_ms": 14200,
  "webhook_registered": true
}

Batch Status

Fetch up to hundreds of execution statuses in a single round trip.

POST /api/v1/executions/batch-status

Request body:

{
  "execution_ids": ["exec_a1b2c3", "exec_d4e5f6", "exec_g7h8i9"]
}

Response: A map keyed by execution ID, each value matching the single-status shape above.

Cancel Execution

POST /api/v1/executions/{execution_id}/cancel

Transitions a running or queued execution to cancelled.