06Deep dive · handoff
Passing a conversation to a person without the customer repeating anything
The agent knows when to stop. The hard part is everything after: who gets pinged, what they see, what the customer sees, and how the agent knows to shut up and not talk over them.
The problem
The agent knows when to stop: a hard case, or a customer asking for a person. Stopping is the easy part. The work is everything after it — who gets notified, what that person sees when they arrive, what the customer sees while they wait, and the part almost everyone forgets: the agent knowing it can't speak again once someone has taken over.
Constraints
- 01Never drop the conversation. A handoff that loses context makes the customer repeat everything, and then it was better not to have one.
- 02The human arrives with everything in hand: full history, the open order and the tools to reply.
- 03The agent goes quiet on cue. Two voices answering the same person is the ugliest thing that can happen in a conversation, and it doesn't happen.
- 04A credential is permission, not intent. Being allowed into the conversation doesn't make you the one who should answer.
Architecture
agent detects the limit
a hard case, or an explicit ask for a person
card + notify
a card is created and the human is pinged in-channel, with context
human takes over
co-pilot: the phone is the credential — permission, not intent
agent goes quiet
muted until "resolve" hands control back
It all turns on a distinction the first version got wrong: a phone number registered to receive alerts is permission to step in, not a sign that this person is now the one talking. Confusing the two is what let a notified human's identity replace the specialist mid-conversation, without anyone asking for it.
The hard part
The ugliest bug in the flow was a specialist that vanished. A phone number stored as a notification target — someone who was only meant to be told — was read as intent: the system decided that person was answering, the specialist went quiet and disappeared from the chat. The customer was left talking to an empty chair.
The fix was conceptual before it was code: separate credential from action everywhere. Being on the alert list means you may take over, never that you have; only an explicit "take this conversation" changes who speaks. The same class of bug was hiding in the automations: three different places were sending messages outside the conversation, so a follow-up went out without showing up in the chat the human was watching. Every automated send now goes through one function that writes it into the chat before sending.
Trade-offs
- Muting the agent the instant a human engages opens a short gap if they're slow to type. I'd rather have two seconds of silence than two voices contradicting each other.
- Sending the whole context means the human sees more than they sometimes need. Too much beats too little: they skim and find it.
- Routing alerts by phone number is simple and it's what people already have. But it's what forced me to make the difference between credential and intent explicit in the code.
What I'd do differently
I'd make "who is speaking" a single explicit piece of state with one owner at a time, instead of something inferred from alert targets and message sources. Almost every handoff bug was the same bug wearing different clothes: the system disagreeing with itself about who had the floor. One authoritative answer would have prevented all of them.