A refund goes through, the reply never arrives, and the agent asks again.
A customer cancels and a ₹1,200 refund is asked of the payment gateway. The gateway makes it, but its answer is lost on the way back, so the agent can’t tell whether it happened. Three runs of that one moment, from a real test run:
Without a check
The gateway alone, no Guard: a ₹2,400 booking, half of it (₹1,200) refunded.
- 1Refund ₹1,200 askedReply lostThe gateway made the refund, but its answer never came back.
- 2Refund ₹1,200 asked againPaid twiceThe gateway made it a second time.
The customer got ₹2,400 back on a ₹2,400 payment: 2 refunds.
Had the whole payment been refunded, the gateway would have refused the second refund itself. It can’t refuse a second part refund.
With Geometer: the agent looks first
Behind Guard. After the lost answer, the agent checks the gateway’s list of refunds before doing anything else.
- 1Refund ₹1,200 askedWe’re checkingGuard let it through. The answer was lost, so nobody knows yet if the money moved.
- 2Checked the gateway’s list of refundsAllowedGuard let the question through, and the gateway answered.
Confirmed by the gateway 1 refund of ₹1,200, and no second one asked for.
- Held Razorpay holds exactly one refund (not two)
- Held The refund is the deposit, ₹1,200
- Held After the answer was lost the agent did not ask for the refund again
- Held The agent was still running at the end
With Geometer: the agent asks again
Behind Guard. This agent just sends the refund again at once, with a new key each time.
- 1Refund ₹1,200 askedWe’re checkingGuard let it through. The answer was lost, so nobody knows yet if the money moved.
- 2Refund ₹1,200 asked againStopped, not sentGuard held it back: the first refund may already have gone through.
- 3Refund ₹1,200 asked againStopped, not sentGuard held it back: the first refund may already have gone through.
- 4Refund ₹1,200 asked againStopped, not sentGuard held it back: the first refund may already have gone through.
The gateway holds 1 refund of ₹1,200. Guard stopped all 3 repeats.
- Held Razorpay holds exactly one refund (not two)
- Held The refund is the deposit, ₹1,200
- Did not hold After the answer was lost the agent did not ask for the refund again
- Held The agent was still running at the end
- The agent that looked first was stopped by its own check. It read the gateway’s list instead of asking again, so Guard had nothing to stop.
- The agent that asked again was stopped by Guard’s at-most-once rule. Guard held back 3 of 3 repeats, even with a new key, because the first refund’s outcome was unknown. The crash test still marks this agent as not holding: it shouldn’t have asked.
- Both layers are there on purpose. The agent’s check can be missing or wrong. Guard’s rule holds whatever the agent does.
This run drove the two example agents in the agents repository (bundle/examples/own-agent) behind the real Guard, on a stand-in gateway with test money. It does not run the Bookings template, which is not part of this evidence.
node bundle/geometer.ts agent crash-test my-agent --situations refund-lost-replyRun it on your own agent. The Guard program in this run was built on 2026-11-08.