Documentation / Advanced

Dispatch async work safely

Persist the operation before dispatch. Keep its identity stable through uncertain outcomes and ownership changes.

Declare the async contract

The callback is required and authenticated. Retry generations get separate credentials. Fixed, linear and exponential backoff are supported policy values. Adapter kinds describe contracts; customer/provider integration still has to be configured.

Recovery can preserve the callback credential’s original issued epoch. Current-owner fencing is a separate check; always return the issued credential unchanged.

json
{
  "type": "async_tool",
  "timeout": "30m",
  "callback": {
    "required": true,
    "authenticated": true
  },
  "retry": {
    "maxAttempts": 3,
    "backoff": "exponential",
    "initialInterval": "5s",
    "maxInterval": "1m"
  }
}
Complete example · JSON / YAML

JSON

json
{
  "name": "advanced-async",
  "version": "1.0.0",
  "controls": {
    "policyVersion": 1,
    "budgets": [
      {
        "id": "research",
        "resource": "model_cost",
        "unit": "usd",
        "limit": "5.00",
        "enforcement": "hard",
        "maxExposurePerOp": "0.50",
        "scope": "workflow_run",
        "reset": "operator_amendment_only",
        "violation": "pause"
      }
    ]
  },
  "phases": [
    {
      "id": "research",
      "type": "async_tool",
      "tool": "example-provider",
      "timeout": "30m",
      "callback": {
        "required": true,
        "authenticated": true
      },
      "retry": {
        "maxAttempts": 3,
        "backoff": "exponential",
        "initialInterval": "5s",
        "maxInterval": "1m"
      },
      "adapter": {
        "kind": "external_https",
        "target": "https://provider.example/jobs",
        "idempotentDispatch": true,
        "supportsStatusLookup": true,
        "supportsRemoteCancel": true
      },
      "controls": {
        "budget": "research"
      },
      "terminal": true
    }
  ]
}

YAML

yaml
name: advanced-async
version: 1.0.0
controls:
  policyVersion: 1
  budgets:
    - id: research
      resource: model_cost
      unit: usd
      limit: "5.00"
      enforcement: hard
      maxExposurePerOp: "0.50"
      scope: workflow_run
      reset: operator_amendment_only
      violation: pause
phases:
  - id: research
    type: async_tool
    tool: example-provider
    timeout: 30m
    callback:
      required: true
      authenticated: true
    retry:
      maxAttempts: 3
      backoff: exponential
      initialInterval: 5s
      maxInterval: 1m
    adapter:
      kind: external_https
      target: https://provider.example/jobs
      idempotentDispatch: true
      supportsStatusLookup: true
      supportsRemoteCancel: true
    controls:
      budget: research
    terminal: true

Download JSON · YAML

shell
rippflow controls validate advanced-async.workflow.yaml --json

Expected: ok: true. This checks the contract; deployment/enforcement is separate.

Build the callback with the SDK

Use the issued request helper to preserve operation, generation and credential binding. This builds a payload; send it only through the installation’s issued callback transport. Do not log its bearer credential.

rust
use rippflow_sdk::{AsyncCallbackPayload, AsyncToolRequest};
use serde_json::json;

pub fn complete_example(req: &AsyncToolRequest) -> AsyncCallbackPayload {
    req.complete(json!({"summary": "Example result"}))
}

Recover uncertain dispatch

Reuse the issued side-effect idempotency key. Use provider status lookup when available; an uncertain non-idempotent dispatch must not be blindly repeated. Cancellation revokes wait intent/credentials; a remote cancel capability is separate. Late callbacks are audited and must not revive cancelled work.

Track external-operation concurrency and budget reservations separately from the released worker slot.

Checkpoint context, reference large artifacts

DurableAgentContext binds context version, checkpoint sequence, ownership epoch, budget summaries, approval gates and artifact references. Use conditional version updates. Inline state defaults to 4 KiB; larger content uses offload references with plaintext/envelope digests and retention.

Inspect through research examples and execution history. Keep callback credentials and sensitive artifact content out of logs.

shell
rippflow executions inspect EXECUTION_ID --json
rippflow executions history EXECUTION_ID --json
Verified sources

Checked 2026-11-05.

Let's talk

Contact Rippflow

Tell us about your workflows, deployment or pricing.

We'll reply to your email.

Search docs

Search workflows, handlers and operations.