
Articles · Inbox · 18 min
How do I hand an AI agent ticket to a human without dropping context?
Hand a ticket to a human when the finish line needs a grant the agent does not have, or when a required id is still missing after the tools ran. Write a packet onto the ticket: the original finish line, evidence ids, the one next action, and the limits left. The human continues from that packet. A chat log is not a handoff.
By Eric · Rome · Aug 29, 2026
I have watched an inbox agent find the order, name the policy, then dump forty messages on a person who still opened the CRM from scratch. The agent called that a handoff. It was a paste.
A ticket that leaves an agent without a packet will be re-litigated. The person rereads and re-asks what done looks like. You paid the model to work, then paid a human to start over.
Hand the ticket when the finish line needs a grant the agent does not have, or when a required id is still missing after the tools ran. Write four fields onto the ticket and stop. The human continues.
An inbox agent that cannot finish writes a packet onto the ticket and stops. The packet holds the finish line, the evidence ids, the next action, and the limits left. Pass a chat log instead and the person starts the job over.
When the agent should hand the ticket
Hand the ticket when the finish line needs a grant the agent does not have, or when a fact it needs is missing after the tools already ran. Do not hand it because the model feels unsure. Unsure with a complete record is a draft, while a missing id, a policy exception, or a payout change is a person.
An inbox agent is software given a job on a queue: search, draft, file, close. Auto-triage is the lane, not the exit. The agent works the lane until the job is done or until the next step is a grant it was never given.
I keep people out of every click. That job is how to run AI agents without a human. This guide is the edge of that loop: the ticket that still needs a person.
Escalate when the next action would spend, send, refund, change a payout destination, or bind the company in a way the runtime forbids. Escalate when identity is not verified, when two records disagree and a tool cannot pick, or when the user asked for a person. Escalate when legal, safety, or HR is the actual job.
Hold the ticket in the agent when the policy is clear, the ids are in hand, and the next tool would finish the job. A model that narrates doubt is not a reason to page someone. Doubt with a complete file is a draft waiting for the send grant, or a close.
| Signal | Keep the agent | Hand to a human |
|---|---|---|
| Policy | Fits a written rule the agent may apply | Needs an exception, a verbal promise, or a grant above the cap |
| Evidence | Order, invoice, and thread ids are in the trace | A required id is missing after the retry budget |
| Action | Draft, file, tag, close, or a write it is allowed to make | Send, refund, payout change, legal hold, or bind |
| Identity | Requester matches the account on file | Unknown sender, spoofed domain, or a bank-detail change |
| User | Wants the job finished | Asked for a person, or the thread is a threat |
Bank-detail changes, refunds over the agent's cap, and anything that moves money to a new destination always leave the loop. I do not let the model talk itself into those. The runtime refuses the tool, and the packet is how the refusal becomes a job for a person instead of a dead thread.
False escalations are how unsupervised inboxes rot. If the agent can close the ticket under its grants, it should. A human queue full of complete, in-policy work is a triage failure, not a safety feature.
The packet is four fields
Four fields, always: the original finish line, evidence ids, the one next action, and the limits left. If a field is empty, it is not a packet. The outgoing agent is not done until those fields sit on the ticket the human will open.
The finish line does not change at the door. If the job was refund posted or written refusal, ticket closed, that is still the job. The human inherits it rather than a new conversation about how everyone feels.
Evidence is ids: ticket, thread, order, invoice, CRM record, attachment hash, policy URL with an anchor, the tool-call ids that already ran. A person with those ids can open the record. A person with a paragraph titled summary has to trust a model that just failed the job.
If an id is missing, name the miss. CRM lookup timed out after one retry, account id unknown is evidence. Looks like a long-time customer is a vibe.
The incoming human fetches the missing id. They do not invent a recap.
Next action is one step: approve the exception, send the refusal with the policy cite, call the number on the account, or fetch the account id. "Please look into this" is how the baton hits the floor. If you cannot name the step, the agent has not finished thinking, and it should not have stopped.
Limits are what the agent already spent and what it must not do: spend left, retries already used, grants it lacked, deadline on the SLA. Draft only, no send, no refund over the cap, CRM retried once. The human should not have to reverse-engineer the runtime from a transcript.
- Finish line: the original done condition, copied, not rewritten as a mood.
- Evidence ids: records a person can open. Missing ids stated as missing.
- Next action: one verb the human must take. Not a reading list.
- Limits: spend, retries, grants refused, deadline, and the tools that are still forbidden.
| Field | Passes | Fails |
|---|---|---|
| Finish line | Refund posted or written refusal with policy cite, ticket closed | Help the customer / see below |
| Evidence ids | T-1842, order 9f3c, INV-441, policy /refunds#exceptions, tool-call 7c2 | The thread above, customer seems loyal |
| Next action | Approve the $740 exception or send the refusal. Do not re-fetch the order. | Please advise / can you take a look |
| Limits | Draft only. No send. No refund over $200. CRM retried once. SLA 16:00. | Be careful / use your judgment |
I use the same shape between agents. Relay is that packet on a desk that happens to be another model. A person is a successor who will not scrape a JSON blob out of a chat, so put the four fields on the ticket.
The outgoing agent writes the packet. That is part of the job, not a courtesy. If the runtime can stop on a finish line, it can stop on a written packet, and a handoff without those fields is an incomplete run you score as a miss.
Routing is not orchestrator-workers
Routing sends a known class of ticket to a known desk, including a named human desk. Orchestrator-workers invents subtasks at runtime and stitches them. Inbox escalation is routing, so do not let an orchestrator invent a person as a surprise worker.
Anthropic's Building effective agents, published 19 December 2024, draws the line I use. Routing classifies an input and directs it to a specialized followup task. It exists so you can separate concerns, because optimizing one kind of input in a single prompt hurts the others.
Their routing examples are customer-service shaped: general questions, refund requests, technical support, each with its own process, prompt, and tools. A human queue is one more specialized followup. You name it in advance rather than discovering it mid-trace because the model got tired.
Orchestrator-workers is a different machine. A central model breaks the job into subtasks it did not know in advance, delegates to workers, and synthesizes. Anthropic, on 19 December 2024, puts that pattern on work where you cannot predict the parts: multi-file coding, search that fans out.
The orchestrator stays the owner of the result. A ticket inbox is not that job. You can name the classes before the mail arrives: in-policy refund, out-of-policy refund, billing identity, legal hold, sales, vendor bank change, everything else.
Auto-triage is routing into those classes. The human desk is a class, and so is the agent that works an in-policy refund. Do not promote the inbox to an orchestrator so it can decide who to involve.
Routing classifies an input and directs it to a specialized followup task.
Anthropic also describes agents that pause for human feedback at checkpoints or when they hit a blocker. That pause is useful. It is not a substitute for a ticket packet.
A blocker with no packet is a person staring at a stopped trace. A checkpoint with four fields is a job.
I treat human as a destination in routing, with the same narrowness I give a billing specialist. The escalate conditions are the classifier. The packet is the input the destination is allowed to see.
If you cannot write the conditions as a list, you do not have routing. You have a model that shrugs.
| Pattern | Who owns the next reply | Inbox use |
|---|---|---|
| Routing | The specialist path you named, including a human desk | Classes you can list before the mail arrives |
| Orchestrator-workers | The orchestrator, after workers return | Rare on tickets. More for research you cannot pre-split. |
| Handoff | The specialist that takes the conversation | The ticket leaves the current agent for good |
| Agents as tools | The manager agent | A bounded lookup. The agent stays on the ticket. |
Start with one inbox agent. Split a human destination out when policy isolation requires it: the model must not hold the refund-over-cap grant, the payout-change grant, or the legal-hold grant. That is the same reason I split agents at all, not because a slide said multi-agent.
What OpenAI transfers on a handoff
OpenAI treats a handoff as a tool the model calls, then control moves. The orchestration docs ask who owns the next user-facing answer. For a person that owner is the human, so attach a small structured payload rather than shipping the raw thread.
The OpenAI Agents SDK handoffs documentation, current in 2026, is blunt about the mechanism. Handoffs let an agent delegate to another agent. They are represented as tools to the model, so a handoff to Refund Agent shows up as transfer_to_refund_agent.
The model is not writing a novel. It is picking a tool. That is the right shape for an inbox: the escalate path is a tool with a schema, not a paragraph the model is free to improvise.
If the human desk is a destination, register it as a destination and give it a short description of when to pick it. OpenAI's orchestration and handoffs guide, also 2026, says to keep that description concrete, give each specialist a narrow job, and split only when the next branch needs different instructions, tools, or policy.
The same guide makes ownership the first design choice: who owns the final user-facing answer at each branch. Handoffs move control to the specialist. Agents as tools keep the manager in control and treat specialists as bounded helpers.
A human taking a ticket is a handoff. The person owns the next reply. The agent does not stay in the thread as a narrator.
OpenAI's handoff helper lets the model attach a small structured payload at the moment it chooses the destination. In the Python SDK that is input_type plus on_handoff. In TypeScript it is inputType plus onHandoff.
The 2026 docs give the example of an escalation with a reason. I put the four packet fields in that payload. Reason alone is not enough, because reason is how you get customer is upset instead of an invoice id.
The payload is metadata the model decides at handoff time, not a dump of application state you already have. OpenAI says to keep existing state in the run context, and to use an input filter when you want to change what history the receiver sees.
I use the filter. The human does not need every tool call. The engineer does, in the trace, because those are different readers.
on_handoff is the moment to persist. Write the packet onto the ticket, set the owner, and start the human SLA if that is a different clock. The SDK will transfer conversation history unless you filter it.
For a person, the ticket record is the transfer. The chat history stays in the log.
OpenAI also notes that input_type does not pick the destination. You register one handoff per destination and let the model choose among them. That matches routing.
Human-refund-exception, human-identity, and human-legal are separate doors if they are separate desks. One ask-a-human tool that takes a free-text essay is how context dies.
The orchestration guide says to start with one agent and add specialists only when they improve capability isolation, policy isolation, prompt clarity, or trace legibility. A human desk is policy isolation. The model that drafts refunds should not also be the model that can talk itself into posting one over the cap.
Hand the ticket, not the chat
Keep the ticket and write the packet on it so the queue, the owner field, and the clock stay put. A new chat with the transcript pasted in is how context dies, even if every original sentence is still in the paste.
People do not read forty model turns before they act. They scan for the job, the ids, and what they are supposed to do in the next five minutes. If those three things are buried under a recap, you did not hand off: you outsourced reading.
The ticket is already the unit of work: status, assignee, SLA, audit trail. An inbox agent that opens a side channel in Slack and pastes the thread has created a second object with no clock.
The original ticket sits idle, the paste has no owner, and two days later nobody can say which object is true.
I write the packet as fields on the ticket the human already opens: custom fields, a pinned comment, a structured note at the top. The rest of the thread can remain. It is evidence, not the baton.
The baton is the four fields. If the helpdesk cannot store four fields, the helpdesk is the wrong runtime for an agent.
- 01
Stay on the same ticket id
The agent worked T-1842. The human opens T-1842. A new conversation, a new Slack thread, or a forwarded blob with a new subject is a drop.
- 02
Write the four fields first
They sit above the transcript. The human can act without scrolling. The transcript is optional reading, not the handshake.
- 03
Set the owner and keep the clock
Queue membership does not reset because a model touched the ticket. If the SLA started at inbound, it is still running. The packet names the deadline in limits.
- 04
Stop the agent
After the packet is written, the agent does not keep drafting into the thread. Two writers on one ticket is how you send the refusal and the apology.
A chat dump fails in a specific way. The second reader cannot tell which sentences are facts, which are tool output, and which are the model guessing so the recap looks complete. Ids make that split visible.
If the invoice id is in the packet, the human opens the invoice. If the invoice is only described, the human is reading fiction that happens to be formatted like support.
Shared memory does not fix this. Memory is a pile of files, embeddings, prior tickets. A handoff is a decision.
Agents and humans can read memory. They should not have to mine it to know what to do next. Keep both: memory holds the records, and the packet holds the baton.
Approval pauses, handoff changes owner
An approval is a pause before a side effect. A handoff is a change of owner. You can have memory, a pause, and a packet, but the human should not have to mine a pile to learn the next action.
OpenAI's guardrails and human review guide, 2026, treats human review as a pause so a person or policy can approve or reject a sensitive action. Approvals are the human-in-the-loop path for tool calls. The model still decided the action, the run waits, and you approve or reject, then resume the same run from saved state.
That is the right tool when the agent is still the owner and the next step is a side effect you will not let it take unsupervised: cancel an order, send a refund under cap, edit a record. The person is a gate. They are not taking the ticket home.
YouTube
Open originalLangChain walked the same pause-and-resume handoff when they shipped LangGraph 1.0 on 22 Oct 2025.
A handoff is the other case. The agent is no longer the owner. The next user-facing answer belongs to the human.
OpenAI's orchestration guide says handoffs are the fit when a specialist should own the next response rather than help behind the scenes. A person on an exception refund is that specialist.
Do not mix them because both involve a person. If you pause for approval and also paste the thread into a queue, you now have two humans, two clocks, and one model waiting. Pick: gate the tool, or transfer the ticket.
If you transfer, write the packet and end the run. If you gate, resume the same run and let the agent finish.
OpenAI notes that the same interruption pattern still applies after a handoff, or inside a nested agent-as-tool call. Fine for agent-to-agent graphs. For a human destination I do not resume the inbox agent on that ticket.
The person owns the reply. Restarting the model so it can help is how you get a second draft sent while the human is still reading.
| Control | Person's job | When I use it |
|---|---|---|
| Tool approval | Yes or no on a proposed side effect, then the agent continues | The agent is still the right owner. The grant is the question. |
| Ticket handoff | Take the next action on the original finish line | The agent is the wrong owner. Policy, identity, or scope broke. |
| Guardrail trip | Nothing. The run stops or the request is blocked. | The input or output is disallowed. There is no job to inherit. |
Taking the human out of every loop does not mean there is no human at the edge. It means they are not clicking search_mail.
People set grants and take the tickets the grants cannot finish. The packet is how those two jobs meet without a paste.
Score the packet, not the apology
The score is whether the person acted from the packet without a clarifying ping. Missing ids, false escalations, and tickets the agent could have closed are misses. A polite dump of the thread is still a miss.
I freeze a set of tickets that should escalate and a set that should not. The escalate set needs a complete packet. The hold set should never reach a person.
If you only measure whether a human got involved, you will train the agent to bounce everything it finds slightly annoying.
The primary score on an escalate ticket is whether the human took the next action without asking what the job was. If they asked, the finish line was missing or rewritten. If they re-fetched an order that was already in the packet, the ids were missing or untrusted.
If they sent a duplicate of a draft the agent already wrote, the limits field did not say what had been done.
- Packet complete: all four fields present, ids resolve, next action is one verb.
- Human time-to-next-action from open, not from first reply in a side chat.
- Clarifying pings: what was the original job is a packet fail.
- False escalations: in-policy tickets that hit a person.
- Missed escalations: out-of-policy tickets the agent tried to finish.
- Reopens caused by a guessed recap rather than an id.
LLM-as-judge on the courtesy of the dump will pass a failure. The artifact is the packet on the ticket plus the human's first action. A beautiful explanation of a missing invoice id is a zero, the same rule I use for any other agent: grade the artifact, not the essay.
Log the handoff like a tool call: destination, payload, ticket id, timestamp, whether the human later marked the packet usable. If you cannot replay the payload, you cannot tell whether the model omitted the order id or the helpdesk dropped the field.
A refund that should have been a packet
A $740 refund request hits the shared inbox. The agent's cap is $200 and the buyer cites a verbal promise from sales. The agent can look up the order and cannot post the refund, which makes this a human ticket, and a boring one if the packet is clean.
The agent searches mail and the order system. It finds order 9f3c, invoice INV-441, ticket T-1842, policy /refunds#exceptions. It drafts a refusal that cites the cap, because that is the in-policy move, and it does not send.
It writes the four fields onto T-1842 and stops. That write is the close of the agent run, not a courtesy.
- Finish line: refund posted or written refusal with policy cite, ticket closed. Unchanged from intake.
- Evidence ids: T-1842, order 9f3c, INV-441, /refunds#exceptions, tool-call 7c2 (order lookup), tool-call 7c3 (policy). Amount 740. Cap 200. Claimed verbal promise from sales, no record in CRM after one retry.
- Next action: approve the exception or send the refusal. Do not re-fetch the order. If you need the sales promise, ask sales. The CRM already missed.
- Limits: draft only, no send, no refund over 200, one CRM retry used, spend left under a dollar, SLA 16:00.
The human opens T-1842 and either posts the exception or sends the draft. They do not ask what the job was, open a side thread, or grep a forty-turn recap for the invoice number.
The failure mode I still see is the other object. The agent pastes the thread into Slack with customer wants a refund, seems upset, please advise. Someone in Slack asks for the order id, and someone else closes T-1842 as waiting.
The buyer writes again. Now you have three conversations and one missing packet.
If the CRM timeout had been the only problem, the packet would say so and the next action would be fetch the account id, then apply the cap. That is still a human ticket if the agent is not allowed a second integration. It is not a reason to restate the buyer's biography.
Rules that keep the baton from dropping
The outgoing agent is not done until the packet is on the ticket. The incoming human's first job is the next action, not a reread. If evidence is missing they fetch it, they do not invent a recap, and the agent does not keep talking after it stops.
- Name escalate conditions as grants and missing ids, not as feelings.
- Copy the original finish line. Do not rewrite the job as you leave.
- Ids over summaries. A missing id is written as missing.
- One next action. If you need two, you are not ready to hand off.
- Limits include what already ran, what is forbidden, and the deadline.
- Same ticket id. No side channel as the system of record.
- Filter the history the person sees. Keep the full trace for the engineer.
- Do not resume the inbox agent on a ticket a human now owns.
- Score packet completeness and false escalations on a frozen set.
- Register one destination per desk. Do not use a free-text ask-a-human essay.
Fleets die at handoff. Inboxes die there too, only the second agent is a person with a queue already too long. The fix is not a longer recap: it is a packet the successor can act on without asking what the job was.
Questions
No. Once the packet is on the ticket, the human owns the next reply. Leaving the model in the thread is how you send two answers. If you only needed a yes on a side effect, that is a tool approval, not a handoff.
No. A packet is four fields: finish line, evidence ids, next action, limits. A summary is still prose. If an id is not in a field a script could read, the human will re-ask.
Use the handoff tool as the model's way of choosing the human destination, with a structured payload for the four fields. Persist that payload onto the ticket in on_handoff. Do not transfer the raw conversation as the thing the person is supposed to read.
The ticket stays the system of record. Slack can notify. If the packet only exists in Slack, you have a second object with no SLA. Write the four fields on the ticket, then ping the owner with the ticket id.
Next

