What discovery invariants prevent infinite refund loops in autonomous barter networks?
**Date of Record:** 2026 October 26 **Scope:** Applies when autonomous agents participate in a decentralized barter or exchange network where goods, services, or tokens are swapped, and refunds or reversals are permitted due to failed delivery or disputed quality. Does not apply to strict one-way payments or escrow systems with a single trusted arbiter.
Core Distinction
The separation between **bilateral refunds** (Agent A refunds Agent B for a failed transaction) and **network-wide refund loops** (Agent A refunds B, B refunds C, C refunds A, creating an infinite cycle of refunds that drains network liquidity). Failing to distinguish these creates the **Refund Recursion Bug**: a malicious or buggy set of agents exploits refund discovery mechanisms to create a DAG (Directed Acyclic Graph) violation, cycling value infinitely between them.
What Was Observed
In decentralized agent economies and barter protocols, failing to enforce acyclic refund invariants produces three primary failure modes:
- **Liquidity Drain:** A group of colluding agents creates a circular chain of refunds. Each refund appears legitimate in isolation, but the cycle drains the network's escrow pool.
- **Double Refund:** An agent refunds a transaction, and the counterparty re-refunds the same transaction through a different discovery path, draining the original agent's balance.
- **Causal Inversion:** Agent A refunds B, then B refunds A for the same trade, then A refunds B again. The ledger shows infinite oscillation with no net settlement.
Recorded Elements
- **`refund_dependency_graph_hash`:** The hash of the DAG representing the refund chain (A <- B <- C), ensuring no cycles exist before a new refund is appended.
- **`original_transaction_id`:** The exact identifier of the original trade, preventing a single transaction from being refunded multiple times.
- **`refund_epoch`:** A monotonically increasing epoch timestamp, ensuring refunds occur in causal order and cannot loop backwards.
- **`network_liquidity_bound`:** The maximum cumulative refund value allowed across a specific agent subgraph before the network halts and escalates to arbitration.
Condition of Validity
* **Applies when:** The barter network permits refunds or reversals and discovery is distributed, meaning agents can discover refund counterparties without a central authority validating the graph. * **Does not apply when:** The network enforces a strict, centralized clearinghouse that rejects cycle-forming refunds at the protocol level, or refunds are strictly manual and human-mediated.
Decision Boundary
- **Is a refund being requested?** If no, standard trade discovery applies. If yes, proceed to validate the dependency graph.
- **Does the new refund introduce a cycle in the DAG?** If yes, reject the refund immediately and flag the counterparty as `MALICIOUS_LOOP_ATTEMPT`.
- **Has the `original_transaction_id` already been refunded?** If yes, reject the duplicate refund. If no, proceed.
- **Conversely, when can a refund be approved without graph validation?** Only when the refund is strictly unilateral (A gifts B without expectation of reversal), or the network uses a time-locked escrow where refunds are only possible after the original transaction's expiration window.
Operational Effect
- **Liquidity Protection:** Prevents colluding agents from draining the network's escrow pool through circular refunds.
- **Deterministic Settlement:** Guarantees that every refund resolves to a final, acyclic state, enabling the network to reach a stable balance.
- **Sybil Exploitation Prevention:** Stops Sybil agents from creating fake transaction chains to generate infinite refund cycles.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class RefundVerdict(Enum):
REFUND_APPROVED = auto()
CYCLE_DETECTED = auto()
DUPLICATE_REFUND = auto()
@dataclass(frozen=True)
class RefundRequest:
refund_id: str
original_transaction_id: str
sender_id: str
receiver_id: str
amount: int
epoch: int
@dataclass(frozen=True)
class NetworkState:
existing_refund_edges: frozenset[tuple[str, str]]
refunded_transactions: frozenset[str]
current_epoch: int
@dataclass(frozen=True)
class RefundManifest:
verdict: RefundVerdict
directive: str
manifest_digest: str
class BarterRefundGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_refund(
cls, req: RefundRequest, state: NetworkState
) -> RefundManifest:
# Gate 1: Duplicate refund check
if req.original_transaction_id in state.refunded_transactions:
return RefundManifest(
verdict=RefundVerdict.DUPLICATE_REFUND,
directive=f"REJECT: Transaction {req.original_transaction_id} already refunded.",
manifest_digest=cls._hash(f"DUP:{req.original_transaction_id}")
)
# Gate 2: Cycle detection (simplified DFS)
new_edge = (req.sender_id, req.receiver_id)
edge_set = set(state.existing_refund_edges)
edge_set.add(new_edge)
# Build adjacency list
adj: dict[str, list[str]] = {}
for src, dst in edge_set:
adj.setdefault(src, []).append(dst)
# DFS to detect cycle
def has_cycle(node: str, visited: set[str], rec_stack: set[str]) -> bool:
visited.add(node)
rec_stack.add(node)
for neighbor in adj.get(node, []):
if neighbor not in visited:
if has_cycle(neighbor, visited, rec_stack):
return True
elif neighbor in rec_stack:
return True
rec_stack.remove(node)
return False
visited = set()
rec_stack = set()
for node in adj:
if node not in visited:
if has_cycle(node, visited, rec_stack):
return RefundManifest(
verdict=RefundVerdict.CYCLE_DETECTED,
directive=f"REJECT: Refund cycle detected involving {req.sender_id} and {req.receiver_id}.",
manifest_digest=cls._hash(f"CYCLE:{req.sender_id}")
)
# Gate 3: Approved
digest = cls._hash(json.dumps({
"refund": req.refund_id,
"txn": req.original_transaction_id,
"edge": f"{req.sender_id}->{req.receiver_id}"
}, sort_keys=True))
return RefundManifest(
verdict=RefundVerdict.REFUND_APPROVED,
directive="PROCEED: Refund is valid and introduces no cycles.",
manifest_digest=digest
)