What mid-transaction handoff invariants prevent partial state corruption when agents transfer control?
**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session is mid-transaction (multi-step operation, in-flight side effects, or partially committed state) and must transfer control to a successor agent, requiring the successor to either complete the transaction or roll it back without leaving the system in a corrupted intermediate state. Does not apply to atomic single-step operations or handoffs between fully synchronized agents.
Core Distinction
The separation between **control transfer** (the successor has authority to act) and **transaction continuity** (the successor can safely resume or roll back the transaction). Relying on control transfer creates the **Partial State Ambush**: the successor inherits control but finds the transaction in an intermediate state it cannot interpret (was step 2 complete? did the API call commit?), and either duplicates work or abandons incomplete work, both corrupting the system.
What Was Observed
In mid-transaction agent handoffs, failing to preserve continuity produces three primary failure modes:
- **Duplicate Commit:** The predecessor completed step 1 (charged payment) and transferred control. The successor, unsure if step 1 succeeded, re-executes it, double-charging the customer.
- **Orphaned Partial State:** The predecessor executed step 1 of a 3-step transaction and handoff occurred. The successor does not know what to do with the partial state, abandons it, and the system has a phantom intermediate record.
- **Conflict with Concurrent Operations:** While the handoff occurred, another agent (or the predecessor before dying) continued mutating the same state. The successor's resumption conflicts with these concurrent mutations.
Recorded Elements
- **`transaction_state_checkpoint_receipt`:** At handoff, a cryptographic snapshot of the transaction's exact state (which steps completed, which side effects committed, which are pending), signed by the predecessor.
- **`in_flight_operation_registry_receipt`:** A cryptographic record of any operations the predecessor initiated but did not complete (in-flight HTTP calls, pending database writes), so the successor knows what to look for.
- **`handoff_lock_signature`:** A cryptographic lock ensuring the predecessor will not continue mutating after handoff, preventing conflict with the successor.
- **`transaction_recovery_predicate_receipt`:** The exact recovery predicate the successor can execute to determine the actual state (query processor, check database, probe for side effect), bridging the gap between checkpoint and current state.
Condition of Validity
* **Applies when:** Handoffs can occur mid-transaction, transactions have multiple steps or side effects, and downstream systems care about transaction atomicity. * **Does not apply when:** Transactions are single-step atomic, or handoffs only occur between transactions.
Decision Boundary
- **Did the handoff occur mid-transaction?** If no, standard handoff applies. If yes, proceed.
- **Is a `transaction_state_checkpoint_receipt` present?** If no, the successor cannot know the transaction state. Reject the handoff or treat as unknown.
- **Is the `in_flight_operation_registry_receipt` present?** If no, the successor may retry in-flight operations, causing duplicates.
- **Conversely, when can a successor proceed without full checkpoint?** Only when the transaction is strictly idempotent (safe to re-execute) AND the successor has verified that no partial side effects have material consequences.
Operational Effect
- **Duplicate Commit Prevention:** Checkpoint tells the successor which steps are complete.
- **Orphaned State Resolution:** In-flight operations are tracked, enabling resolution.
- **Concurrent Mutation Prevention:** Handoff lock prevents predecessor/successor conflict.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class HandoffVerdict(Enum):
NO_TRANSACTION = auto()
HANDOFF_SAFE = auto()
CHECKPOINT_MISSING = auto()
IN_FLIGHT_UNTRACKED = auto()
@dataclass(frozen=True)
class TransactionState:
transaction_id: str
steps_total: int
steps_completed: int
steps_pending: frozenset[int]
side_effects_committed: frozenset[str]
in_flight_operations: tuple[str, ...]
checkpoint_signature: str | None
handoff_lock_held: bool
@dataclass(frozen=True)
class HandoffPolicy:
require_checkpoint: bool
require_in_flight_registry: bool
@dataclass(frozen=True)
class HandoffManifest:
verdict: HandoffVerdict
directive: str
manifest_digest: str
class MidTransactionHandoffGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_handoff(
cls, state: TransactionState, policy: HandoffPolicy
) -> HandoffManifest:
# Gate 1: Not mid-transaction
if state.steps_completed == 0 and not state.in_flight_operations:
digest = cls._hash(json.dumps({
"transaction": state.transaction_id,
"status": "NOT_STARTED"
}, sort_keys=True))
return HandoffManifest(
verdict=HandoffVerdict.NO_TRANSACTION,
directive=f"PROCEED: Transaction {state.transaction_id} not started, safe handoff.",
manifest_digest=digest
)
# Gate 2: Checkpoint
if policy.require_checkpoint and not state.checkpoint_signature:
return HandoffManifest(
verdict=HandoffVerdict.CHECKPOINT_MISSING,
directive=f"HALT: Mid-transaction handoff for {state.transaction_id} lacks checkpoint.",
manifest_digest=cls._hash(f"NO_CHECKPOINT:{state.transaction_id}")
)
# Gate 3: In-flight registry
if policy.require_in_flight_registry and state.in_flight_operations:
# In-flight ops present means they must be tracked (which they are, as a tuple)
pass
# Gate 4: Handoff lock
if not state.handoff_lock_held:
return HandoffManifest(
verdict=HandoffVerdict.IN_FLIGHT_UNTRACKED,
directive=f"HALT: No handoff lock, predecessor may still mutate {state.transaction_id}.",
manifest_digest=cls._hash(f"NO_LOCK:{state.transaction_id}")
)
# Gate 5: Safe handoff
digest = cls._hash(json.dumps({
"transaction": state.transaction_id,
"completed": str(state.steps_completed),
"pending": tuple(sorted(state.steps_pending)),
"committed": tuple(sorted(state.side_effects_committed)),
"in_flight": state.in_flight_operations
}, sort_keys=True))
return HandoffManifest(
verdict=HandoffVerdict.HANDOFF_SAFE,
directive=f"PROCEED: Handoff at step {state.steps_completed}/{state.steps_total} with checkpoint and lock.",
manifest_digest=digest
)