How must an instance verify asynchronous settlements before committing dependent state?
**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session initiates an asynchronous settlement (payment authorization, blockchain transaction, or background job) and must wait for confirmation before committing dependent state (updating records, notifying downstream consumers), where the settlement may fail, be delayed, or be reversed. Does not apply to synchronous settlements or settlements whose success is immediately confirmed.
Core Distinction
The separation between **settlement initiation** (the operation was submitted) and **settlement confirmation** (the settlement is final and irreversible). Relying on initiation creates the **Premature Commit**: the agent initiates settlement, receives an "accepted" acknowledgment, commits dependent state assuming success, and then the settlement fails, leaving dependent state that is invalid but committed.
What Was Observed
In async-settlement agent architectures, failing to verify finality before committing produces three primary failure modes:
- **Payment Settlement Failure:** Payment is "accepted" but fails at the backend (insufficient funds, fraud rejection). The agent has already shipped the product and updated inventory, all based on the premature commit.
- **Blockchain Reorg:** Transaction is included in a block but reverted in a chain reorganization. The agent has notified downstream that the transaction succeeded, but the on-chain state reverts.
- **Background Job Failure:** Job is enqueued successfully but crashes during execution. The agent has recorded the job as successful, and downstream consumers wait for output that never comes.
Recorded Elements
- **`settlement_finality_receipt`:** For each settlement, a cryptographic proof of finality (payment processor's final confirmation, blockchain depth ≥N confirmations, job completion signal), distinct from the initial "accepted" acknowledgment.
- **`dependent_state_binding_receipt`:** The exact dependent state that will be committed if and when settlement is confirmed, so the commit is prepared but not executed prematurely.
- **`settlement_failure_receipt`:** For settlements that fail after acceptance, the cryptographic proof of failure, allowing the agent to discard dependent state without corruption.
- **`async_settlement_timeout_signature`:** The exact timeout after which the agent must treat unconfirmed settlements as failed, preventing indefinite waiting.
Condition of Validity
* **Applies when:** Settlements are asynchronous, dependent state must not be committed until settlement is final, and settlement can fail after initial acceptance. * **Does not apply when:** Settlements are synchronous (immediate confirmation) or dependent state is trivially reversible.
Decision Boundary
- **Was a settlement initiated?** If no, no dependent state commitment applies. If yes, proceed.
- **Is a `settlement_finality_receipt` available?** If yes, dependent state can be committed. If no, the settlement is not final.
- **Has the `async_settlement_timeout_signature` expired without finality?** If yes, treat as failure and roll back dependent state. If no, continue waiting.
- **Conversely, when can dependent state commit before finality?** Only when the dependent state is itself reversible (e.g., "pending" status), or the settlement failure risk is acceptable.
Operational Effect
- **Premature Commit Prevention:** Dependent state commits only on finality proof.
- **Failure Handling:** Failures trigger rollback instead of leaving orphaned state.
- **Deterministic Timeouts:** Unconfirmed settlements do not block indefinitely.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class SettlementVerdict(Enum):
NO_SETTLEMENT = auto()
AWAITING_FINALITY = auto()
FINALITY_CONFIRMED = auto()
SETTLEMENT_FAILED = auto()
TIMEOUT_TREATED_AS_FAILURE = auto()
@dataclass(frozen=True)
class AsyncSettlement:
settlement_id: str
initiated: bool
initiation_epoch: float
finality_receipt: str | None
finality_epoch: float | None
failure_receipt: str | None
timeout_sec: float
current_epoch: float
@dataclass(frozen=True)
class SettlementPolicy:
max_wait_sec: float
@dataclass(frozen=True)
class SettlementManifest:
verdict: SettlementVerdict
directive: str
manifest_digest: str
class AsyncSettlementGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_settlement(
cls, s: AsyncSettlement, policy: SettlementPolicy
) -> SettlementManifest:
# Gate 1: Not initiated
if not s.initiated:
digest = cls._hash(json.dumps({
"settlement": s.settlement_id,
"status": "NOT_INITIATED"
}, sort_keys=True))
return SettlementManifest(
verdict=SettlementVerdict.NO_SETTLEMENT,
directive=f"PROCEED: No settlement initiated for {s.settlement_id}.",
manifest_digest=digest
)
# Gate 2: Failure
if s.failure_receipt:
return SettlementManifest(
verdict=SettlementVerdict.SETTLEMENT_FAILED,
directive=f"HALT: Settlement {s.settlement_id} failed. Do not commit dependent state.",
manifest_digest=cls._hash(f"FAILED:{s.settlement_id}")
)
# Gate 3: Finality
if s.finality_receipt and s.finality_epoch:
digest = cls._hash(json.dumps({
"settlement": s.settlement_id,
"finality": s.finality_receipt,
"epoch": f"{s.finality_epoch:.0f}"
}, sort_keys=True))
return SettlementManifest(
verdict=SettlementVerdict.FINALITY_CONFIRMED,
directive=f"PROCEED: Settlement {s.settlement_id} final. Commit dependent state.",
manifest_digest=digest
)
# Gate 4: Timeout
elapsed = s.current_epoch - s.initiation_epoch
max_wait = min(s.timeout_sec, policy.max_wait_sec)
if elapsed > max_wait:
return SettlementManifest(
verdict=SettlementVerdict.TIMEOUT_TREATED_AS_FAILURE,
directive=f"HALT: Settlement {s.settlement_id} no finality after {elapsed:.0f}s > {max_wait:.0f}s. Treat as failure.",
manifest_digest=cls._hash(f"TIMEOUT:{s.settlement_id}")
)
# Gate 5: Awaiting finality
digest = cls._hash(json.dumps({
"settlement": s.settlement_id,
"elapsed": f"{elapsed:.0f}",
"remaining": f"{max_wait - elapsed:.0f}"
}, sort_keys=True))
return SettlementManifest(
verdict=SettlementVerdict.AWAITING_FINALITY,
directive=f"WAIT: Settlement {s.settlement_id} awaiting finality ({elapsed:.0f}s / {max_wait:.0f}s).",
manifest_digest=digest
)