Skip to main content

AxonFlow v7.3.0 Release Notes

AxonFlow v7.3.0 — Retry Semantics & Idempotency — turns two long-standing operational questions into first-class wire surfaces:

  1. "Is this a retry, and if so, did the prior attempt actually finish?"
  2. "Can I pin this step to a business-level identity — an invoice number, a wire transfer ID, a claim reference — so a retry with a different identity is rejected before it does damage?"

The answers land on every existing /gate and /complete call — no new endpoints, no new lifecycle — as two additions:

  • retry_context — a structured object returned on every gate response that carries gate_count, completion_count, prior_completion_status ("none" / "completed" / "gated_not_completed"), prior_output, last_decision, timestamps, and the caller-supplied idempotency_key. Replaces the cached / decision_source booleans as the primary signal for reasoning about retries; those remain on the response for back-compat but are deprecated.
  • idempotency_key — an optional caller-supplied opaque string (up to 255 chars) on /gate and /complete. Recorded on the first gate call, immutable for the step's lifetime, and validated strictly on every subsequent gate and complete. Mismatches return HTTP 409 IDEMPOTENCY_KEY_MISMATCH with structured detail so SDKs can build typed errors.

Plus a new Evaluation-tier capability: retry-aware tenant policy conditions. The policy engine now resolves seven new step.* fields, so rules like "more than three attempts requires approval" or "rapid retries within 30 seconds escalates severity" become declarative instead of custom code.

No breaking changes. Purely additive. Every v7.2.x caller that never sets idempotency_key or reads retry_context keeps working unchanged.

This release matters most if you are:

  • Building payment agents, prior-authorization workflows, regulated-transaction automation, or any AI agent that can accidentally double-submit under retry
  • Using the Workflow Control Plane with LangGraph, CrewAI, or a custom orchestrator and want explicit retry-state visibility per step
  • Authoring tenant policies and want to route decisions based on retry patterns without reinventing the state tracking

Community — wire-level primitives on every tier

retry_context on every StepGateResponse

Every /gate call — including the very first one on a step — returns a populated retry_context block. Example first-call response:

{
"decision": "allow",
"step_id": "transfer",
"cached": false,
"decision_source": "fresh",
"retry_context": {
"gate_count": 1,
"completion_count": 0,
"prior_completion_status": "none",
"prior_output_available": false,
"prior_output": null,
"prior_completion_at": null,
"first_attempt_at": "2026-04-21T15:30:45.123Z",
"last_attempt_at": "2026-04-21T15:30:45.123Z",
"last_decision": "allow",
"idempotency_key": ""
}
}

On a retry, the same fields tell the agent exactly what happened between now and the first gate call — gate_count increments, prior_completion_status flips from "none" to "gated_not_completed" (if the agent never finished) or "completed" (if /complete did land), and last_decision carries the prior decision's verdict.

Counter bookkeeping is atomic inside the repository UPSERT. A separate cached-hit update keeps counters accurate across idempotent retries without re-evaluating policy.

The full field-by-field reference — and the mapping from the deprecated cached / decision_source to the new fields — lives on Retry Semantics & Idempotency.

?include_prior_output=true on /gate

Opt-in query parameter. When true and prior_completion_status == "completed", the prior /complete payload is populated in retry_context.prior_output. Default is false because prior output may be large and/or contain sensitive data.

When both conditions are satisfied, the full prior output is returned so a resumed agent can short-circuit re-execution and reuse the earlier outcome.

Caller-supplied idempotency_key on /gate and /complete

Opaque string up to 255 chars on the request body of both endpoints. Three rules:

  1. Recorded on the first gate call that sets it.
  2. Immutable for the step's lifetime — subsequent /gate and /complete calls must pass the same key or receive 409 IDEMPOTENCY_KEY_MISMATCH.
  3. Surfaced on every subsequent retry_context.idempotency_key.

The audit log records the key on every step_gate and step_completed event.

Common key formats teams use:

  • payment:wire:<invoice-id>
  • payment:card:<order-id>
  • pa:<patient-id>:<cpt-code>
  • SHA-256 hash of a canonical request body when you need one key per unique request

HTTP 409 IDEMPOTENCY_KEY_MISMATCH

Returned when a /complete (or a subsequent /gate) passes a different key than the one recorded on the first gate call, or when one side supplies a key and the other omits it. Response envelope includes structured detail so SDKs can build typed errors:

{
"error": {
"code": "IDEMPOTENCY_KEY_MISMATCH",
"message": "idempotency_key does not match the key recorded on the step's first gate call",
"details": {
"workflow_id": "wf_41231a72",
"step_id": "step-2",
"expected_idempotency_key": "payment:wire:INV-7721",
"received_idempotency_key": "payment:wire:INV-9999"
}
}
}

All four SDKs surface this as a typed error class (Python / TypeScript / Go: IdempotencyKeyMismatchError; Java: IdempotencyKeyMismatchException) that exposes the workflow ID, step ID, expected key, and received key — see the SDK section below.

cached and decision_source preserved

Both fields remain on every gate response so existing SDK versions continue working unchanged. Both are marked deprecated in the wire docs:

  • retry_context.gate_count > 1 replaces cached: true
  • retry_context.prior_completion_status replaces the string-typed decision_source

No removal in this release. A future major version will drop them.

MarkStepCompleted tenant-header parity

One small fix worth calling out explicitly: the MarkStepCompleted HTTP handler now reads tenant identity from X-Tenant-ID consistently with StepGate, rather than from X-Client-ID. A real multi-tenant caller setting the tenant header now works on both endpoints. Previously the complete path rejected the request as "workflow not found" because the isolation check was comparing against the wrong attribute. No behavior change for callers using empty headers.

Evaluation — retry-aware tenant policy conditions

The policy engine gains seven new step.* fields as evaluable conditions, populated from the step's retry_context before each evaluation:

FieldTypeDescription
step.gate_countintNumber of /gate calls, including the current one
step.completion_countintNumber of successful /complete calls
step.prior_completion_statusenum"none", "completed", "gated_not_completed"
step.prior_output_availablebooltrue iff a prior /complete landed
step.last_decisionenum"allow", "block", "require_approval" from the prior gate
step.first_attempt_age_secondsintSeconds since the first gate call on this step
step.idempotency_keystringThe caller-supplied key, empty string when none

Example patterns that are now declarative:

{
"name": "slow-payment-retry-needs-approval",
"description": "Retries on a payment that never completed within 5 minutes route to approval",
"type": "context_aware",
"category": "dynamic-compliance",
"priority": 900,
"enabled": true,
"conditions": [
{"field": "step.prior_completion_status", "operator": "equals", "value": "gated_not_completed"},
{"field": "step.first_attempt_age_seconds", "operator": "greater_than", "value": 300}
],
"actions": [
{"type": "require_approval", "config": {"reason": "Payment retry past 5-minute window — operator review required", "severity": "high"}}
]
}
{
"name": "rapid-retry-escalates-severity",
"description": "More than three gate attempts within 30 seconds signals runaway retry; escalate to critical",
"type": "context_aware",
"category": "dynamic-compliance",
"priority": 900,
"enabled": true,
"conditions": [
{"field": "step.gate_count", "operator": "greater_than", "value": 3},
{"field": "step.first_attempt_age_seconds", "operator": "less_than", "value": 30}
],
"actions": [
{"type": "block", "config": {"reason": "Runaway retry pattern — blocking further attempts pending investigation", "severity": "critical"}}
]
}

The policy shape: conditions is a flat array ANDed implicitly; operators are words (equals, greater_than, less_than, contains, not_equals, regex, in) — not shorthand; actions is a plural array with each action wrapped in {type, config}, and reason / severity live inside config; type must be context_aware (one of a fixed set) and category must start with dynamic- or media-. See WCP Policy Configuration for the full field reference.

Tier gating on create

Creating or updating a tenant policy with any step.* condition on a Community license is rejected at create time with HTTP 403 and a structured error:

{
"error": {
"code": "FEATURE_REQUIRES_EVALUATION_LICENSE",
"message": "Retry-aware policy condition \"step.gate_count\" requires Evaluation or Enterprise license. Get a free Evaluation license at https://getaxonflow.com/evaluation-license"
}
}

The check fires before the tenant policy-count query, so the rejection is immediate and does not consume a DB roundtrip. Evaluation and Enterprise tiers accept the fields at create and evaluate them at runtime.

UX note — retry-aware policies need retry_policy: "reevaluate"

Default-idempotent step gates return the cached decision from the database on retry without re-running the policy engine. That's the right behavior most of the time — consistent auditability, no counter drift — but it means a retry-aware policy like "block after 3 attempts" will never re-fire on attempt 4 if attempt 3's response was cached. To make retry-aware policies evaluate on each attempt, callers must pass retry_policy: "reevaluate" on the /gate request (or the SDK equivalent). See the SDK Integration page for the cache semantics and WCP Policy Configuration for the broader field surface.

Enterprise

No Enterprise-exclusive additions in this release.

Deferred and tracked for a later release:

  • Cross-workflow idempotency lookup — preventing the same business transaction from being processed twice through two different workflow IDs
  • Windowed operators like idempotency_key_seen_within
  • Retry-pattern correlation across workflows
  • Compliance-grade audit and reporting for duplicate-prevention events

The wire-level primitives in this release are a prerequisite for those — the idempotency_key recorded on every step is the building block a cross-workflow check needs — but the cross-workflow enforcement itself is not yet shipped.

SDK companion releases

All four AxonFlow SDKs ship companion MINOR releases alongside the platform:

  • Python v6.5.0 — adds retry_context on StepGateResponse, idempotency_key on gate + complete request types, include_prior_output as a keyword-only parameter on step_gate, and an IdempotencyKeyMismatchError exception class
  • TypeScript v5.5.0 — adds the same on StepGateResponse, MarkStepCompletedRequest, a StepGateOptions.includePriorOutput boolean on stepGate, and an IdempotencyKeyMismatchError class
  • Go v5.5.0 — adds RetryContext on StepGateResponse, IdempotencyKey on both request types, a new StepGateWithOptions method taking StepGateOptions{IncludePriorOutput}, and an IdempotencyKeyMismatchError (use errors.As to unwrap)
  • Java v5.6.0 — adds retryContext on StepGateResponse, idempotencyKey on both request builders, a StepGateOptions overload of stepGate for includePriorOutput, and an IdempotencyKeyMismatchException (subclass of AxonFlowException)

Every SDK preserves cached / decision_source as deprecated fields so existing callers keep working. See SDK Version Compatibility for the full matrix and WCP SDK Integration for the language-by-language usage.

New tutorials for the patterns this release enables

Two new end-to-end tutorials land with this release:

  • Payment Agent — Retry & Reconciliation — vendor payment agent that survives downstream timeouts without double-charging, using retry_context + idempotency_key + bank-side reconciliation. Runnable on Community; Evaluation-tier extension adds a retry-aware policy for long-running retries.
  • Healthcare Prior Authorization — HITL + Idempotency — prior-auth agent that pauses high-cost procedures for medical director review, with idempotency_key pinned to patient + procedure so an approved decision can never be retargeted. Evaluation license required for the HITL queue.

Both tutorials include curl, Python, TypeScript, Go, and Java code in tabs, with mermaid sequence diagrams and explicit scope notes on what ships in this release vs what's deferred.

Upgrade checklist

  1. Upgrade the platform to v7.3.0.
  2. Upgrade any SDK you use to its companion release — the new types and error classes are additive, so existing call sites keep working, but the new primitives are worth adopting for any workflow that retries.
  3. If your workflow calls pin business transactions to identifiers (invoice numbers, transfer IDs, claim refs, content hashes), pass the same identifier as idempotency_key on both /gate and /complete. Also consider using it on the downstream-system call (Stripe, Adyen, bank core-banking APIs, payer portals) so the reconciliation endpoint lookup becomes possible.
  4. Replace if (gate.cached) checks in new code with if (gate.retry_context.gate_count > 1), and replace decision_source == "fresh" with retry_context.prior_completion_status == "none". Existing code keeps working; this is about new integrations reading the recommended field.
  5. If you operate on Evaluation or Enterprise, consider authoring retry-aware tenant policies — especially "retry-after-gated-not-completed requires approval" and "rapid retry escalates severity" patterns. Remember to pair them with retry_policy: "reevaluate" on the retry call so the policy engine sees the new state.
  6. If your application retries by creating a new workflow_id, keep your own idempotency store — cross-workflow enforcement is deferred to a future release.

Relationship to previous releases

  • v7.2.0 — the Bug Bash Bonanza. Hardened the Customer Portal HTTP surface, tightened tenant-scope enforcement, and added require_approval as a valid override action.
  • v7.2.1 — surfaced HITL approver identity (approved_by, approved_at) on workflow step responses and exposed approval_id on gate responses. If you are building HITL flows, that release completes the observability picture before v7.3.0 layers retry awareness on top.