The agent tells the customer that a colleague will call back within the hour. No colleague is told, because the escalation went into a shared inbox that nobody checks after six. An agent on a logistics firm's WhatsApp support line will meet tasks it should not finish alone within its first day, and what happens at that moment decides whether anyone trusts it. The handoff from agent to person needs the same design and testing as the rest of the feature.
Stop conditions belong in code
A model can say it is unsure, but it will not say so at the right moments often enough to rely on. Define the stop conditions in the code that runs the agent's loop, and treat the model's own signal as one input among several.
Each condition should be something you can write a test case for. For a shipment-tracking agent, these are the inputs I would start with:
- The hub system says AWB 4182 is out for delivery at 09:14; the rider app says delivered at 09:02. Records disagree, so the agent stops.
- The customer wants delivery to a pin code outside the service area. The request is outside written policy.
- The AWB lookup returns nothing for the number the customer typed.
- The customer types "give me a person" or "insaan se baat karao".
- The step budget runs out before the task is resolved.
- The proposed action, such as rescheduling a delivery that already has a failed attempt, is on the list that needs approval.
Alongside these, give the model an escalate tool it may always call, with a required reason field. A model that has a clean way to stop uses it more often than one that has to improvise.
Do not build the handoff on a confidence number the model produces about its own answer. Language models do not report calibrated confidence. Use things you can measure: whether the required facts were found, whether the checks passed, and whether two runs of the same task reached the same conclusion.
- Records disagree
- Outside policy
- Nothing found
- Customer asks
- Step budget spent
- Needs approval
- Model escalates
Each trigger is a condition in code, plus the model's own escalate call.
What the person receives
The package decides whether the handoff takes one minute or twenty. A fixed structure, filled by code from the agent's state, works better than a free-text summary written by the model. Reviewers learn where to look in a fixed structure. They cannot learn where to look in a narrative that changes shape every time.
{
"trigger": "records_disagree",
"request": "Tracking says delivered but parcel nahi aaya",
"facts": [
{"source": "hub", "awb": "4182", "status": "out_for_delivery", "at": "09:14"},
{"source": "rider_app", "awb": "4182", "status": "delivered", "at": "09:02"}
],
"stopped_before": "send delivery confirmation to customer",
"proposed": {"action": "open_non_delivery_case", "awb": "4182"},
"links": ["/shipments/4182", "/tickets/77310"]
}
The request as the customer sent it. The facts the agent found, each with its source and time. What it was about to do and which trigger stopped it. The proposed action, in a form the person can approve or edit. Links to the records. The model's full reasoning trace stays behind a link for the rare case that needs it.
A queue with an owner and a response time
An escalation nobody owns is a dropped task, and the customer has no idea it was dropped because the agent said it was being handled. Route each handoff by its trigger to a named queue: policy questions to the team that owns the policy, data conflicts to the team that owns the data, "give me a person" to support. Give each queue a response time someone has actually agreed to. Tell the customer that a person has the task and when to expect an answer. When that time passes, the task is reassigned or a lead is notified.
Do not send escalations into the general support queue. There they wait behind every other ticket while the customer believes the agent is on it.
Track two things per trigger: how often it fires and how long resolution takes once it does. A trigger that fires on most tasks means the agent is being asked to do something it cannot, or the policy is missing a clause.
Resuming after the person decides
The person's decision takes one of three forms. Approve, and the agent resumes and completes the proposed action. Correct, and the agent resumes with the correction as the result of its escalate call. Take over, and the agent closes its part while the person finishes by hand.
The first two only work if the agent can resume. Its state, meaning the task, the facts found so far and the pending action, has to be stored somewhere durable at the moment it stops. A process that held it in memory may be gone by the time the support lead gets to it after lunch. Resuming is then a matter of loading the state and passing the decision in as the tool result the agent was waiting for. The approval part of this is the same "ask a person" path described in our post on what an agent may read, write and ask about.
- 01Stop conditioncode or escalate call
- 02Packagefilled from state
- 03Queue by triggerowner, response time
- 04Person decidesapprove, correct, take over
- 05Agent resumesdecision as tool result
State is stored at the stop so the agent can resume with the person's decision.
Each resolved escalation is also a recorded task with a known right answer. Add it to the regression set. Over a few months the set fills with exactly the cases the agent found hardest, and those are the ones to replay before the next prompt change goes out.
How handoffs fail
One escalation bucket with no trigger recorded, so every reviewer starts each task from nothing.
Handoff messages written as the model's narrative. The reviewer reads three paragraphs to find the one timestamp that matters.
No response time. Escalations wait while the customer believes the agent is on it.
Agents that cannot resume. Every escalation restarts the task and the person's answer is lost.
Tuning the agent to escalate less without checking what happens to the tasks it now completes alone. Expected: fewer handoffs on the cases it handles well. Actual, often: fewer handoffs on the cases it used to get wrong, now finished wrong without anyone seeing them. The only way to tell the difference is to replay the old escalations against the new prompt.
Test the handoff before release
Write a test case for each trigger and run them the same way as every other agent test. For each, pass means three things: the agent stopped before any write, the package contains the right trigger and the facts with sources, and the customer was told a person has the task.
- Stop conditions listed in code, plus an escalate tool with a required reason.
- A fixed package: request, facts with sources, trigger, proposed action, links.
- Queues by trigger, each with an owner and an agreed response time.
- The customer is told who has the task and when to expect an answer.
- Agent state stored at the stop, and the loop can resume with the decision.
- Every resolved escalation added to the regression set.
If you are deciding whether a task needs an agent at all, a fixed workflow is sometimes enough. If it does need one, write the stop conditions before the prompt. Until they exist, the agent decides on its own when it is unsure, and that is the decision you least want to leave to it.

