Hand off from AI to a human agent
Store the AI agent's notes by call ID, transfer the caller to a queue, and load those notes for the human agent.
Also called AI escalation to a live agent, warm transfer from AI
The claim
Three documented pieces, joined by one id. The SWAIG tool webhook, documented in the REST spec as ai-swaig-tool-webhook, carries the call’s call_id, so the handler can key its notes by it. enter_queue puts the caller in a named queue. The bundled schema requires queue_name and transfer_after_bridge on it. It describes the verb as placing the call where “it will wait to be connected to an available agent or resource”. The vendored REST spec’s GET /api/relay/rest/queues/{queue_id}/members/next “Retrieves the next member in the queue without dequeuing”, and the member carries call_id, position and wait_time. The human takes the call with a connect whose to is queue:support, one of the forms the schema lists for connect.to.
How it works
def hand_off(self, args, raw_data):
call_id = raw_data["call_id"]
save_note(call_id, {"caller_name": args["caller_name"], "issue": args["issue"],
"from": raw_data.get("caller_id_num")})
result = FunctionResult("Thanks. I am putting you through to a person now.")
result.action.append({"SWML": ENQUEUE, "transfer": "true"}) # the documented shape
return result
def brief(queue_id):
member = client.queues.get_next_member(queue_id)
return {"call_id": member["call_id"], ..., "notes": load_notes().get(member["call_id"])}What the platform receives from the tool, then what the human’s phone runs:
{"response": "Thanks. I am putting you through to a person now.",
"action": [{"SWML": {"version": "1.0.0", "sections": {"main": [
{"enter_queue": {"queue_name": "support", "transfer_after_bridge": "false"}}]}},
"transfer": "true"}]}
{"version": "1.0.0", "sections": {"main": [{"answer": {}}, {"connect": {"to": "queue:support"}}]}}transfer_after_bridge is a string the schema requires; "false" means carry on in this document after the bridge, and there is nothing after it. The transfer: "true" beside the SWML is what the tool webhook documents for a call that leaves the agent. FunctionResult.execute_swml(transfer=True) would put that flag inside the document, so the action is built by hand. The notes go to a JSON file at NOTES_PATH. The agent and the screen are two processes, so a dictionary in the agent would be empty in the shell that runs brief. Swap the two functions for your database. The screen asks for the next member, takes its call_id, and shows the notes filed under it.
Limitations
You prove the documents, the tool result and the requests. Who the platform bridges to whom, and how long the caller waits, are the platform’s side of a live call. The next-member read does not dequeue; the bridge does. The two are not one step. Between the read and the bridge the front of the queue can change, and the screen would then describe a different caller than the one bridged. Correlate by call_id on the bridged call before you act on the notes.
The notes are a local JSON file the two processes share, written whole and swapped in. It is not a store for concurrent hand-offs or for production; put them in a database keyed by call_id before anything depends on it.
What to change first
Key the note by args["caller_name"] instead of call_id and run the verifier. The brief assertion fails, because the queue member carries a call id and no name. The id is the only field both sides hold.