AI product engineer · Shared primitives
A reminder can be sent before its receipt code fails
Remove a delivery check in a local fixture to see why a tool error cannot establish that no reminder was sent to the patient.
Key takeaways (3)
- A tool exception does not establish that its side effect never happened.
- Check booking state at delivery and inspect the gateway attempt separately.
- A lost reply needs original-attempt lookup before another send.
Skipping the delivery-time booking check lets an old reminder be delivered before its receipt code crashes. A caller seeing only the exception could wrongly assume nothing was sent.
We changed that single check in a temporary function, then replayed the fictional Cedar Clinic booking change. The original and changed functions produced these actual offline states:
{"booking_current_revision": 2, "delivery_counts": {"1": 0, "2": 1}, "receipt_error": null, "variant": "original"}
{"booking_current_revision": 2, "delivery_counts": {"1": 1}, "receipt_error": "KeyError", "variant": "skip_delivery_recheck"}
Inspect effects even when the tool throws
The changed function substitutes the queued version for the second booking read. The earlier request check remains intact. During delivery, the booking still changes, but the worker no longer notices that change.
The old version records a simulated delivery. Receipt construction then looks for the new version’s entry and raises an error because that entry was never created. The experiment catches the error and inspects the ledger afterwards.
This is a deliberate mutation of correct pinned code, not a claim that the shipped function has this bug. It demonstrates why an exception-only assertion would miss a side effect that occurred before the error. Check the queue entry, gateway attempt and receipt separately.
Show the counterexperiment script
Run this from the pinned companion tests directory shown below: save it as counterexperiment.py, then run python3 counterexperiment.py. It changes no files and uses no model or provider.
"""Offline counterexperiment. Run from the pinned companion tests directory.
No source files are changed. Replacement functions exist only in this process.
"""
import inspect
import json
import textwrap
import test_guard as t
original = t.cc.deliver
source = textwrap.dedent(inspect.getsource(original))
needle = 'now_current = service.current(appointment_id)["revision"]'
assert source.count(needle) == 1
namespace = dict(original.__globals__)
exec(source.replace(needle, 'now_current = entry.revision'), namespace)
for variant in ("original", "skip_delivery_recheck"):
t.cc.deliver = original if variant == "original" else namespace["deliver"]
service = t.cc.ReminderService()
error = None
try:
t.cc.send_reminder(service, t.ME, "blood test")
except KeyError as exc:
error = type(exc).__name__
counts = t.delivered_count(service, t.BLOOD)
print(json.dumps({"variant": variant, "receipt_error": error, "delivery_counts": counts, "booking_current_revision": service.current(t.BLOOD)["revision"]}, sort_keys=True))
assert counts == ({1: 0, 2: 1} if variant == "original" else {1: 1})
t.cc.deliver = original
assert error == "KeyError"Follow the old reminder through the queue
The bug to look for is a saved message that survives a booking change. A check when a reminder is queued cannot catch a change while it waits. The delivery worker needs the booking owner’s current version when it is about to send.
This fixture creates a reminder entry, changes the appointment inside delivery, and then reads the booking again. The old entry is suppressed. The loop considers the current version instead. Its ledger records no delivery for the old version and one for the replacement.
The second read is the point at which the saved job loses authority. This is an excerpt inside the delivery loop, not a standalone script.
suppressed: list[Entry] = []
for _ in range(2): # at most one reissue after a suppression
cur = service.current(appointment_id)["revision"]
entry = service.entry(appointment_id, cur)
entry.state = "queued"
entry.queued_at = entry.queued_at or _stamp(service)
prior = entry.deliveries
# The booking may change between queueing and delivery.
if appt["delivery"] == "rescheduled_before_delivery" and appointment_id not in service._rescheduled:
service._rescheduled.add(appointment_id)
move = appt["reschedule_at_delivery"]
service.reschedule(appointment_id, datetime.fromisoformat(move["starts"]), move["why"])
now_current = service.current(appointment_id)["revision"]
if now_current != entry.revision:
entry.state, entry.why = "suppressed", "booking changed before delivery"
attempts.append(_attempt(service, entry, now_current, "suppressed_obsolete", prior))
suppressed.append(entry)
continue
Choose what counts as the same reminder
The sample keys the ledger by appointment and revision. A patient alone is too broad: they may have two appointments. A network attempt is too narrow: retrying would create a new identity for the same message.
| Identity | What goes wrong | Use it for |
|---|---|---|
| Patient | Can suppress another appointment | Finding the recipient |
| Appointment and revision | Represents one version’s reminder | Deduplication in this fixture |
| Provider attempt | Identifies one transport request | Looking up a lost response |
For several reminder types or channels, extend the logical identity with those fields. Keep the booking version under the booking service’s control. The model should supply the appointment words, not manufacture the authoritative revision.
A lost reply is a different incident
The dermatology fixture simulates a delivery whose acknowledgement never returns. It leaves the entry unknown. Asking to send again is refused; lookup resolves the original delivery. That protects a different boundary from the version check: a current reminder can still be duplicated by a retry.
Translate the fixture into a worker contract
The sample stores appointments and ledger entries in memory. It has no durable queue or competing workers. In a production integration, require a durable unique claim for the logical reminder and a provider lookup tied to the original attempt. Check both ledgers after a timeout.
There is still a race after the last booking read. A real service must define how rescheduling and dispatch claims interact. A provider may already have accepted a send; a correction message might then be needed. Do not call a local uniqueness constraint exactly-once delivery.
Replay rescheduling, recovery and patient replies
Run these checks from the pinned companion project. They use Python’s standard library and make no model calls. The outputs below were recorded on 7 October 2026; elapsed times can differ.
git clone https://github.com/RasaHQ/rasa-community-resources.git
cd rasa-community-resources
git checkout 4aa0c4419dc193fef7a969c12d59edcf720f2606
cd examples/mantle-text-reminder-deduplication-gpt/tests
- Any system
python3 -m unittest test_guard.ReminderTests test_guard.AttendanceTests -q
----------------------------------------------------------------------
Ran 17 tests in 0.002s
OK
The companion tests contain the assertions for these cases. To print the additional fixture states yourself, run this script from the same directory:
Show the script that printed the fixture states
Paste it into a file such as trace.py, then run python3 trace.py. It prints selected fields from the actual tool results; it does not invent replies.
"""Run from the pinned companion project tests directory. Stdlib only."""
import json
import test_guard as t
service = t.cc.ReminderService()
old = t.cc.send_reminder(service, t.ME, "follow-up on Tuesday 6 October at 9:30")
print(json.dumps({"requested": "Tuesday 9:30", "status": old["status"], "reason": old["reason"]}, sort_keys=True))
result = t.cc.send_reminder(service, t.ME, "blood test")
print(json.dumps({"result": result["status"], "suppressed_revisions": [r["revision"] for r in result["suppressed"]], "deliveries": t.delivered_count(service, t.BLOOD)}, sort_keys=True))Give the booking owner the queue-time change and the messaging owner the lost-reply case. Agree which receipt authorises each state before enabling retries. For a spoken reminder, also check how punctuation affects SMS segments.
The complete fixture implementation defines the service state and remaining branches cited here.