How to write the handover note your agent leaves for a human
Escalation gets a whole section of your prompt. The thing the agent hands over gets one line, and that line is usually the word summarise doing all the work.
TL;DR A handover note is not something your agent says, it is an argument it passes: replace summarise the conversation for the human agent with a $TOOL whose typed parameters name the four things the next human actually needs, and the same policy goes from F (59/100) with two blocking logic/numeric errors to A (96/100) with 0 error, 0 warning, 0 info, the note's shape enforced by a schema instead of by an adverb.
Every contact-centre prompt has an escalation section, and it is usually the most carefully written part of the file: the triggers, the tiers, the tool. That part has a guide of its own, How to write escalation rules for an AI support agent, and this one starts where it stops. Then, at the end, one line about what the agent tells the colleague who picks the conversation up. That colleague is walking into a conversation they were not in, with a customer who has already explained themselves once, and the line that decides how that goes is summarise the conversation clearly for the human agent. It is not a bad sentence. It just is not an instruction.
§1One line, two errors
Here is the handover as it usually exists, lifted into a small file so we can look at it on its own. A water utility, a chat agent, and the four lines that between them constitute the entire handover policy.
# Role You are a support agent for Halden Water, a utility company. # Constraints - MUST write at most 3 sentences. - ALWAYS summarise the conversation clearly for the human agent. - MUST write at least 5 sentences of context before you transfer. - SHOULD include anything relevant in the handover. - Tell the customer that somebody will be with them shortly.
Read it as a person and it is fine. Read it as a spec and two of the lines are the same rule at two bounds.
$ typeglish check handover.tg handover.tg:5:1 error logic/numeric Conflicts with line 6. Numeric conflict - "at most 3 sentences" and "at least 5 sentences" can't both hold. handover.tg:6:1 info prompt/unintroduced-definite "the conversation" retrieves something this document never introduces - a model must guess which conversation is meant. handover.tg:7:1 error logic/numeric Conflicts with line 4. Numeric conflict - "at most 3 sentences" and "at least 5 sentences" can't both hold. handover.tg:8:27 info prompt/vague Vague - "relevant" names a judgment call without the criterion to judge by, so nothing can check it. handover.tg:8:1 info prompt/unintroduced-definite "the handover" retrieves something this document never introduces ... ✗ 1 file - 2 error, 0 warning, 3 info # exit 1 $ typeglish score handover.tg handover.tg - F (59/100) proven errors - grade capped at F tiers: base+z3 planes runtime 63 (what the model reads) · hygiene 50 (source only) facets enforceability 80 x.21 · hardness 83 x.12 · directness 75 x.08 consistency 0 x.17 · structure 100 x.12 · annotation 0 x.12 style 63 x.08 · security 100 x.08 lever consistency 0/100 (up to +17 overall)
That is the first thing to internalise about handover text, and it is the reason this is a guide rather than a paragraph: the note is written by the same agent, in the same turn, under the same rules as everything else. Every constraint you wrote for customer-facing replies applies to it, including the ones you would never want applied to it. Brevity is the obvious one. Tone is worse. A file that says always be warm and reassuring is also, silently, saying it about the note you hand your escalations team.
§2The guard that fixes the wrong thing
The mechanical fix is to scope one of the two rules so they cannot both fire, which is exactly what the diagnostic suggests. Put a WHEN ... THEN in front of the handover rule and the arithmetic stops being impossible.
# Role You are a support agent for Halden Water, a utility company. # Constraints - MUST write at most 3 sentences. - WHEN you transfer a conversation THEN write at least 5 sentences of context for the human agent. - ALWAYS summarise the conversation clearly for the human agent. - SHOULD include anything relevant in the handover. - Tell the customer that somebody will be with them shortly.
$ typeglish check handover-scoped.tg handover-scoped.tg:8:27 info prompt/vague Vague - "relevant" names a judgment call without the criterion to judge by, so nothing can check it. handover-scoped.tg:8:1 info prompt/unintroduced-definite "the handover" retrieves something this document never introduces ... ✓ 1 file - 0 error, 0 warning, 2 info $ typeglish score handover-scoped.tg handover-scoped.tg - C (75/100) proven errors: none tiers: base+z3 planes runtime 83 (what the model reads) · hygiene 50 (source only) facets enforceability 65 x.21 · hardness 83 x.12 · directness 83 x.08 consistency 100 x.17 · structure 100 x.12 · annotation 0 x.12
This is the trap the rest of the guide is about. The guard was the right move and it fixed a real defect, and the note the colleague receives has not changed by one word, because the note was never underspecified in a way arithmetic could reach. enforceability even went down, from 80 to 65, which is the honest signal: a guarded rule applies less often than an unconditional one, so the file is now correct and less binding at the same time. That trade shows up everywhere once you start scoping rules, and your policy remembers, your agent does not takes the same measurement from the other end.
A clean check on a handover rule means the sentence is consistent. It says nothing about whether the note is usable.
§3A handover is an argument, not a speech act
Here is the reframe the whole guide turns on. Summarise the conversation for the human agent is written as a thing the agent says. It is not. It is a thing the agent passes, to a system, alongside a routing decision, and the moment you write it that way most of the problem disappears, because a parameter has a type and a sentence does not.
The four things a human picking up an escalation actually needs, in our experience of reading these, are boring and fixed: what the customer asked for, what has already been tried, why the agent stopped, and whether identity was verified. Everything else is available by scrolling up. So do not ask for a summary. Ask for four fields.
<$CONFIG> $CONFIG modality chat </$CONFIG> $SERVICE deskapi - base:: https://api.halden.example - headers:: - Authorization:: Bearer @{env.DESK_TOKEN} $TOOL transfer_to_human - description:: Hands the conversation to a queue with a written handover. Call it once, last. - input:: - queue:: one of billing, leaks, complaints: the destination queue. - asked_for:: string: what the customer asked for, in their own words. - tried:: list of strings: the steps already attempted, in order. - blocked_by:: one of no_permission, no_data, tool_failure, customer_refused: why the agent stopped. - verified:: boolean: whether identity was verified in this conversation. - request:: POST deskapi /conversations/transfer # Role @@ role: the doer every rule below is addressed to You are a support agent for Halden Water, a utility company. # Constraints @@ brevity: three sentences keeps a chat reply scannable, and the handover is not a reply - MUST write at most 3 sentences. @@ handover_is_a_call: the note is an argument, so the schema decides what is in it - ALWAYS call @[transfer_to_human] before you leave a conversation. @@ own_words: a paraphrase is where the human's picture starts drifting from the customer's - ALWAYS quote a customer in their own words rather than a paraphrase. @@ no_diagnosis: a guess in a handover is read by the next human as a finding - NEVER state a cause a customer did not state. @@ no_promise: the queue decides the wait, and the agent does not know the queue depth - NEVER state a callback time. $TEST handover_has_no_guess - input:: This is the third time I have called about the smell in my tap water and nobody has come out. - expect:: - at most 3 sentences - matches /^(?!.*(?:probably|likely|seems to be|must be)).*$/ $TEST handover_promises_nothing - input:: Fine, just put me through to a person. How long will they be? - expect:: - at most 3 sentences - matches /^(?!.*(?:minutes|hours)).*$/
The brevity rule is back to unconditional and there is no conflict, because nothing in the file asks the agent to write five sentences any more. The handover is not prose the agent produces; it is five arguments to one call.
$ typeglish check handover-shipped.tg ✓ 1 file - 0 error, 0 warning, 0 info $ typeglish score handover-shipped.tg handover-shipped.tg - A (96/100) proven errors: none tiers: base+z3 planes runtime 94 (what the model reads) · hygiene 100 (source only) facets enforceability 82 x.21 · hardness 100 x.12 · directness 95 x.08 consistency 100 x.17 · structure 100 x.12 · annotation 100 x.12 style 100 x.08 · security 100 x.08 lever enforceability 82/100 (up to +4 overall) $ typeglish test handover-shipped.tg --dry ✓ handover-shipped.tg coverage: 3/5 rules exercised · handover_has_no_guess - "This is the third time I have called about the sme" (not run) ✓ at most 3 sentences ✓ matches /^(?!.*(?:probably|likely|seems to be|must be)).*$/ · handover_promises_nothing - "Fine, just put me through to a person. How long wi" (not run) ✓ at most 3 sentences ✓ matches /^(?!.*(?:minutes|hours)).*$/ ✓ 1 prompt - 0 failed
F (59/100) to A (96/100), and the interesting movement is not the grade. annotation went 0 to 100 because every rule now carries the reason it exists, and hygiene went 50 to 100, which is the source plane: the part of the file a reviewer reads and the model never does.Then the part that makes the schema real. Build with --bundle and the same source emits the deployable prompt and the tool contract together, from one definition.
$ typeglish build handover-shipped.tg --bundle ✓ built .typeglish/dist/handover-shipped.txt ← handover-shipped.tg (0cda2703dee0, full) # .typeglish/dist/handover-shipped.txt - 332 bytes, everything the model reads # Constraints - MUST write at most 3 sentences. - ALWAYS call transfer_to_human before you leave a conversation. - ALWAYS quote a customer in their own words rather than a paraphrase. - NEVER state a cause a customer did not state. - NEVER state a callback time. # .typeglish/dist/handover-shipped.agent.json - the same file's other half { "name": "queue", "type": "string", "enum": ["billing","leaks","complaints"] } { "name": "asked_for", "type": "string" } { "name": "tried", "type": "string", "array": true } { "name": "blocked_by", "type": "string", "enum": ["no_permission","no_data","tool_failure","customer_refused"] } { "name": "verified", "type": "boolean" }
blocked_by as a closed set cannot arrive as an excuse, and tried as an array cannot arrive as a paragraph that mentions two of the four things the agent did.§4The three rules that survive the move
Typing the fields kills the shape problems. It does not kill the content problems, and there are exactly three worth a rule. Each one is a thing a model will do to a handover that a person would not.
- It will paraphrase. A model summarising my water smells like a swimming pool and I have a baby in the house produces customer reports water quality concern, which is true, useless, and strictly less information than the customer gave you.
ALWAYS quote a customer in their own words rather than a paraphraseis the rule, and theasked_forfield is where the quote goes. - It will diagnose. Handover notes attract invented causes: likely a mains issue, probably a billing sync problem. The next human reads that as a finding from the last person on the case, and starts one step down a road nobody checked.
NEVER state a cause a customer did not state, and pin it with the negative-lookahead assert rather than trusting the sentence. - It will promise. The agent does not know the queue depth, the shift pattern, or whether the leaks team is three people who are all out. Any duration in a handover, or in the line the customer sees while it happens, is invented.
NEVER state a callback time.
All three are pinned by asserts that check for an absence, which is the right shape for handover tests: you are not checking that the note says the correct thing, you are checking that it does not contain the three categories of thing that make it worse than nothing. matches /^(?!.*(?:probably|likely|seems to be|must be)).*$/ is not elegant and it works offline, which beats elegant. On --dry that is coverage: 3/5 rules exercised, honest about the two rules nothing touches yet. The trade between a regex like that and a judge-scored expectation is the subject of Bake-off: deterministic asserts vs. an LLM judge for agent tests.
One gotcha, found the hard way while writing this. Field names are snake_case and so are tool names, and the compiler reads a bare snake_case token in prose as a tool reference. The rule ALWAYS quote a customer's own words in an asked_for field does not check:
$ typeglish check handover-shipped.tg handover-shipped.tg:30:1 error structure/undeclared-tool Tool "asked_for" is used but never imported (add it to a $IMPORT tool line). ✗ 1 file - 1 error, 0 warning, 0 info
§5Common questions
- What should an AI agent tell the human agent when it escalates?
- Four things, and none of them is a summary. What the customer asked for, quoted rather than paraphrased. What the agent already tried, in order, so the human does not repeat it. Why the agent stopped, from a closed list such as
no_permission,no_data,tool_failure,customer_refused. And whether identity was verified in this conversation, because that is the one fact the human cannot re-derive from reading the transcript. Everything else the human can get by scrolling up. Write those four as typed parameters on the transfer tool rather than as an instruction to write prose, and the shape stops depending on how the model felt about the conversation. - Why does my handover summary rule conflict with my reply-length rule?
- Because both rules land on the same slot.
MUST write at most 3 sentencesandMUST write at least 5 sentences of context before you transferare one action given two bounds, and the checker proves they cannot both hold: 2 blockinglogic/numericerrors,F (59/100)withconsistency 0. The reason it is easy to miss is that you are thinking of two different audiences, the customer and the colleague, while the file names only one action. Scoping the handover rule withWHEN you transfer a conversation THENclears both errors and takes the file toC (75/100), but it fixes the arithmetic rather than the note. - Should the handover summary live in the system prompt or the tool schema?
- The schema. A handover is not something the agent says, it is an argument it passes, and a
$TOOLblock turns each thing the next human needs into a named parameter with a type.queueasone of billing, leaks, complaintscannot arrive as a sentence.blocked_byas a closed set cannot arrive as an excuse.triedas alist of stringsarrives as a list, not as a paragraph that mentions some of the steps. Build with--bundleand the same file emits the prompt and the tool schema together, so the note is enforced by a contract rather than by an instruction the model weighs against every other instruction in the file. - How do I test an agent handover without calling a model?
- Write
$TESTblocks with deterministic asserts and runtypeglish test --dry, which validates the suite and reports rule coverage with zero model calls and no API key. The useful asserts for a handover are negative ones, because the failures are things that should not be there:matches /^(?!.*(?:probably|likely|seems to be|must be)).*$/pins the absence of a guessed cause, andmatches /^(?!.*(?:minutes|hours)).*$/pins the absence of a promised wait. On the finished prompt in this post that iscoverage: 3/5 rules exercisedand0 failed, which is a floor rather than a verdict:--dryproves the suite is well formed and says nothing about what a model would actually write.
The cheapest way to find out whether your handover is any good is to stop reading the prompt and read twenty handovers. Pull them out of the desk tool, hide the transcripts, and hand them to somebody on the escalations team who was not on those conversations. Ask one question per note: what would you do first? A good note produces an answer in about four seconds. A bad one produces a scroll, and the scroll is the measurement. In every set we have looked at, the notes that failed failed the same way, by containing a competent paraphrase of the conversation and none of the four facts, which is exactly what summarise the conversation asks for.