A customer books one thing. The operation runs several. That gap is where a lot of otherwise solid integration work quietly falls apart.
I hit this on a healthcare-adjacent deployment where a single item on the customer's side maps to a handful of separate things internally: different rooms, different equipment, different staff, sometimes different durations, occasionally different days. The customer picked one package and got one confirmation. Behind it sat multiple records that all had to agree with each other, and with a second system of record, and with whatever the assistant said in chat five minutes ago.
The failure mode is not dramatic. Nothing throws an error. You just end up with three records where two moved and one didn't, and the first person to notice is the customer standing at reception.
Pick the unit of consistency before you write anything
The instinct is to model the thing the operation cares about, because that's what the underlying system stores. One procedure, one record. Then you write code that loops over them.
That's backwards. The unit that has to stay consistent is the one the customer holds in their head: the booking. Everything under it is an implementation detail that must never be observable on its own. This is the aggregate idea from domain-driven design, and it's worth the ten minutes even if you never touch DDD again. An aggregate is a cluster of objects treated as one unit for the purpose of changes: you don't modify a member directly, you go through the root, and the invariants hold across the whole cluster or the change doesn't happen.
In practice this means every operation the assistant can perform is defined over the booking, never over a procedure. Reschedule the booking. Cancel the booking. There is no "cancel the second scan," because there is no state of the world where the second scan is meaningfully separate from the appointment it belongs to. Once you accept that, a whole class of bug stops being possible, because the code that could have created it doesn't exist.
Partial success is the normal case
If a change touches four records in an external system, you will eventually get two through and fail on the third. Not as an edge case. As Tuesday.
The systems you're integrating with rarely give you a transaction to wrap this in. So you build the equivalent yourself, and the honest version has three parts: validate everything before you change anything, apply changes in an order where a halt leaves a recoverable state, and write down what you attempted, in full, before you attempt it. That last part is what makes recovery possible at all, and it's the same reasoning I've written about in keeping two systems of record in sync: reconciliation needs a record of intent, not just a record of outcome.
What you must not do is let a partially applied change report itself as done. A half-moved booking that says "confirmed" is worse than one that says "something went wrong, a human is looking at it." The second is a fifteen-minute fix. The first is a customer who turns up on the wrong day.
Two places that decide "is this safe to touch" must share one definition
This is the specific one that cost me, and it generalises further than it looks.
I had two checks in the system. One decided whether the assistant could see and describe a booking. The other decided whether it could move it. They were written at different times for different reasons, and they used slightly different rules to answer what is fundamentally the same question: is this booking one we're allowed to change?
Slightly different is enough. A booking that the first check classified as hands-off, the second check classified as fair game. The assistant looked it up, correctly declined to touch part of it, then moved it anyway through the other path. Both checks worked exactly as written. The system was still wrong.
The fix isn't better checks, it's one check. If two code paths answer the same safety question, they call the same function, or one of them is a bug waiting for the right input. I now treat "how many places in this system decide whether an action is permitted" as a number that should be one, and any answer above one as something to justify rather than assume.
The customer should never see the seams
None of this should surface in the conversation. The assistant does not explain that a booking comprises four records, or which one failed. It says one thing about one appointment, because that's the abstraction the customer bought.
Getting there means the seams have to be genuinely closed underneath, not just hidden by careful phrasing. An assistant that describes a bundled booking as a single coherent thing while the records underneath disagree isn't being helpful. It's being confidently wrong, which is the failure I care most about designing out. That's the other half of what I meant by an assistant that acts, not just answers: acting means owning the consequences of the action across every system it touched.
If you're integrating an AI layer onto an operation where one customer-facing thing is several internal things, the modelling question comes first and the prompt comes last. Happy to compare notes if you're in the middle of one.
Related reading: Is your AI assistant telling your patients the truth? on the failure classes that only surface months after go-live, and keeping two systems of record in sync on what reconciliation needs in order to work at all.