AI product manager · Retail & e-commerce (external)
Handle a customer switching from a return to an exchange
Check that retrying a return request does not silently restore the choice the customer has changed.
Key takeaways (3)
- A retry preserves the accepted choice; it does not amend it.
- Inspect the original submission before handling a correction.
- Give an amendment its own identity and acknowledgement.
A customer starts a return, then decides they would rather exchange the item. The assistant retries the earlier request, which still contains the return choice. Avoiding a duplicate request is useful, but it does not mean the new choice was accepted.
You will replay that correction in a fictional shop and inspect the choice stored by the service. The exercise helps you separate recovering an old attempt from accepting a changed request.
{"effects": 0, "refund": "not_decided", "replay": true, "requests": 1, "resolution": "return", "status": "pending", "step": "retry_with_exchange_argument", "submission_key": "WS-RSUB-1EDB5CC5"}
We called the Willow Shop submission function directly after it accepted a return and lost the acknowledgement. This is an offline fixture experiment, not a recorded conversation or evidence that a model chose this path.
The key lookup runs before choice validation
The pinned submission function makes a key from the conversation and item. Its early return is the important boundary:
key = submission_key(conversation_id, ref)
if key in service.requests:
record = service.requests[key]
return _receipt(service, record, replay=True)
# Eligibility and newly supplied choice validation happen after this branch.

The sample’s pre-submission correction test, test_changed_choice_blocks_the_old_request, exercises a different boundary: it updates the choice before a request exists. A passing correction test there does not establish amendment after acceptance. The selected SubmitTests, ChoiceTests, and FindTests receipt has 17 passing checks without skips.
Replay the uncertain submission, then inspect it
Use the pinned returns example. The canvas bag creates one request with an unavailable first acknowledgement. We change the memory and argument to exchange, preserving the original key, then look up that key:
Save this as /tmp/retail-return-check.py. From the example’s tests directory, run PYTHONPATH=. python3 -B /tmp/retail-return-check.py:
"""Run from this example's tests directory with PYTHONPATH=. Stdlib only."""
import json
import test_guard as t
svc = t.fresh()
said = ["Return the canvas weekender bag"]
memory = t.chosen(svc, t.BAG, "return", said)
first = t.wr.submit_return_request(svc, t.ME, memory, t.BAG, "return", said, "catchup")
corrected = {**memory, "selected_resolution": "exchange"}
again = t.wr.submit_return_request(svc, t.ME, corrected, t.BAG, "exchange", said + ["Actually exchange it instead"], "catchup")
looked = t.wr.check_return_status(svc, t.ME, first["submission_key"])
for name, result in (("initial_return", first), ("retry_with_exchange_argument", again), ("status_lookup", looked)):
print(json.dumps({"step": name, "status": result["status"], "resolution": result["resolution"], "effects": result.get("effects"), "replay": result.get("replay", False), "requests": len(svc.requests), "submission_key": result.get("submission_key"), "refund": result["stages"]["refund"]}, sort_keys=True))
Actual offline output:
{"effects": 1, "refund": "not_decided", "replay": false, "requests": 1, "resolution": "return", "status": "pending", "step": "initial_return", "submission_key": "WS-RSUB-1EDB5CC5"}
{"effects": 0, "refund": "not_decided", "replay": true, "requests": 1, "resolution": "return", "status": "pending", "step": "retry_with_exchange_argument", "submission_key": "WS-RSUB-1EDB5CC5"}
{"effects": 0, "refund": "not_decided", "replay": true, "requests": 1, "resolution": "return", "status": "authorized", "step": "status_lookup", "submission_key": "WS-RSUB-1EDB5CC5"}
The lookup later reports authorized, but the stored resolution remains return. Normal selection tools can reject an already-authorized item. This direct call bypasses those tools, the skill, and the confirmation gate to isolate the submission contract.
Decide what a correction means after acceptance
| Customer intent | Operation to expose | Evidence to report |
|---|---|---|
| Try the same return again | Retry the original key | Original stored resolution and stages |
| Find out whether it went through | Status lookup | Accepted request, even if acknowledgement was lost |
| Change return to exchange | A supported amendment operation | Acknowledged change to the original request |
The sample has no amendment operation. Its returns-desk route is a supported escalation, not proof that an exchange happened. Authorization also leaves the refund undecided; it does not prove inspection or reimbursement.
For a production retry endpoint, compare the supplied intent with the accepted record. A different payload should produce an explicit conflict or an equally explicit old-intent replay. This is a proposed contract, not a repair demonstrated here.
If the service supports amendment, give that operation a new amendment ID linked to the original request. Do not create a second return submission to imitate a change. The service must define whether the accepted stage is still amendable and acknowledge the outcome. Preserving accepted intent during a lost acknowledgement can require another lookup and leave the customer’s correction waiting; that cost is preferable to silently claiming a different operation succeeded.
The ledger is in memory. This experiment does not establish cross-process safety, real warehouse behaviour, or customer comprehension.