Compensation
Configure reversible Steps and choose whether failure rolls back one Step or the full Flow.
Configure a Compensated Step
Set handling to compensated, configure the compensation endpoint, and select the Attributes it receives:
{
"id": "charge-card",
"type": "service",
"handling": "compensated",
"http": {
"invoke": {
"endpoint": "https://payment.example.com/charge",
"timeout": 1000,
"mode": "sync"
},
"compensate": {
"method": "DELETE",
"endpoint": "https://payment.example.com/charges/{charge_id}",
"timeout": 3000,
"mode": "async"
}
},
"attributes": {
"amount": {"role": "required", "type": "number"},
"charge_id": {
"role": "output",
"type": "string",
"compensated": true
}
}
}
compensated: true selects the data available to the endpoint, making the compensation contract and its data exposure explicit. Each selected Attribute uses its invocation name. When an input and output share that name, select the value the compensation operation consumes.
The compensation method defaults to POST. Its timeout defaults to the invocation timeout when omitted. Invocation and compensation modes are independent, and each defaults to sync.
Request Contract
POST and PUT send the selected Attributes as one JSON object:
{
"charge_id": "ch_abc123"
}
GET and DELETE resolve selected values into endpoint placeholders. Every method receives Argyll-Flow-ID, Argyll-Step-ID, and Argyll-Receipt-Token; use the receipt token as the endpoint’s idempotency key.
Async Compensation
Set http.compensate.mode to async when rollback continues after the initial compensation request:
- Argyll sends the compensation request with an action-specific
Argyll-Webhook-URL. - The service returns 2xx after accepting the rollback. This does not complete compensation.
- The worker posts to the supplied URL when rollback finishes. An empty callback body reports success;
application/problem+jsonreports permanent failure.
The compensation callback URL ends in /compensate; the invocation callback ends in /invoke. Post to the header value rather than constructing it. Repeating a settled callback is safe.
A non-2xx response to the initial request follows normal retry rules: 5xx and transport failures are retryable, while 4xx is permanent.
Choose the Rollback Scope
When a compensated Step fails, Argyll compensates its succeeded Work Items. This gives a for_each Step a consistent cleanup path when some items succeeded before another exhausted its retries.
Enable Rollback on Failure when starting a Flow to compensate every succeeded Work Item in that failed Flow whose Step uses compensated handling:
{
"id": "checkout-123",
"goals": ["ship-order"],
"compensate": true,
"init": {
"order_id": ["ord-abc"]
}
}
Flow Steps expose the same setting as flow.compensate, so each child Flow carries its own rollback policy.
Ordering
Flow rollback follows reverse dependency order. Steps that ran in parallel compensate in parallel; the preceding dependency wave starts after the current wave settles.
third wave"] Address["validate-address
third wave"] Charge["charge-card
second wave"] Ship["ship-order
first wave"] Reserve --> Charge Address --> Charge Charge --> Ship
Work Items within one Step compensate independently. A succeeded Work Item that finishes after Flow failure joins the appropriate rollback wave. The Flow deactivates after all active and compensating work settles.
Child Flows keep their own rollback boundary. A Flow Step’s flow.compensate setting controls its child, while the parent controls compensation of the Flow Step itself.
Retries and Recovery
Compensation uses the Step’s work_config retry and backoff settings. Retryable failures resume after engine restart, and permanent or exhausted failures finish as compensation_failed. See Retries for configuration and WebSocket API for event names.