Handoffs and Shared State
A handoff moves work from one worker to another, and every handoff loses something. The engineering question is which state is shared through a store that both of them can read, and which is passed in the message — because passing everything defeats the point of the split, and passing too little makes the receiver start the ticket over from the customer's first sentence.
This applies identically whether the receiver is a specialist agent or a person on the support team, and the second case is the one that runs at volume. Sundry escalates 6% of 4,200 tickets a week: roughly 250 handoffs to a human, every week, each one an opportunity to issue a refund that already went out.
Two Channels
Structured task state lives in the database, exactly where Chapter 6 put it: the ticket row, the order it concerns, the subtask list with a status on each, the actions already executed with their timestamps and idempotency keys, and the policy references the decisions rest on. Whoever is working the ticket reads it there. Nothing about that record is agent-specific, so a human console can render it and a specialist agent can load it into an envelope.
The message is the other channel, and it carries only two things: what is being handed over, and why. It is short by design. When the two channels blur — when the message starts restating the state because the sender is not sure the receiver will look — you have rebuilt the transcript handoff with extra steps, and the receiver's context inherits every problem the sender's had.
Deciding which channel a fact belongs in takes one question: does it have to survive this run. The refund already issued is state, because a colleague picking the ticket up on Thursday needs it as much as the specialist does today. "I am passing this to you because the seller runs a supplement that overrides our own 30-day window" is the message, because it is the reason for this particular transfer and it stops mattering the moment the transfer is accepted.
What the Receiver Needs
Four things, and none of them is the transcript: the customer's actual request in their own words, the decisions already made with the policy passage each one cites, the actions already executed, and what remains. The transcript is how the sender arrived at those; it is 6,000 tokens of route rather than destination, and a receiver reading it will re-derive conclusions that are already settled while inheriting the sender's mistakes as premises.
{
"ticket_id": "T-40219", "order_id": "SU-88421",
"owner": "returns_specialist", # transferred, not shared
"customer_asked": "cracked side panel, charged twice for delivery,
does not want a replacement",
"decided": [{"what": "damage claim is valid",
"cites": "damage-in-transit procedure, s.4"}],
"completed": [{"tool": "get_order", "at": "09:14:02"},
{"tool": "track_parcel", "at": "09:14:05"}],
"remaining": ["refund the $9.95 duplicate delivery charge",
"settle the panel under the seller's return window"],
"customer_told": "We are looking into it. Nothing promised."
}
Read it as a shift handover between two people who will never speak. The request is in the buyer's terms, not in a classifier's label. Each decision carries the passage it rests on, so the receiver can disagree with a citation rather than with a vibe. The completed list is what stops the second worker from redoing an action that moved money, and the last field — what the customer has already been told — is the one that gets left out and the one every subsequent message has to stay consistent with.
The whole object is about 300 tokens against a transcript of 6,000, and it is assembled by code from the state rows rather than written by the model. That is the same rule as Chapter 8's escalation payload and it holds for the same reason: a summary produced by the run is a description of work the run did not watch finish, and the fields that matter most here are exactly the ones a model under a turn budget compresses first.
Handing Off to a Human
Same discipline, higher stakes, and 250 times a week. A colleague picking up an escalation sees a rendered version of that object rather than a transcript, because a human reading a twelve-turn transcript at 14:30 on a Friday will skim it, and the thing they skip is always the line about what was already done. Chapter 8 met that failure as an escalation sent without its completed-actions list; a handoff between two agents drops the same line by the same mechanism, and the receiver has even less reason to doubt what it was handed.
Two additions matter for people. Mark reversibility per completed action, so the colleague knows the courier pickup can still be cancelled and the refund cannot. And state the next action as a recommendation with its reason, not as an instruction — the colleague is being escalated to precisely because judgement is required, and a handoff that reads like a work order suppresses the judgement it was asking for.
Handoff Loops
Two agents pass a ticket back and forth because neither owns it. The returns specialist decides the damage half is not its problem and sends it to the damage specialist, which decides the refund half is the returns specialist's problem and sends it back. In the first week of Sundry's split, 23 tickets crossed more than twice and one crossed six times in 40 minutes, burning eleven model calls without a word reaching the customer.
Detection is the repetition logic from Chapter 8 with a different key: hash the ticket id with the sending and receiving agent, count it, and a second identical transfer inside one ticket is a cycle. On detection the run stops transferring and escalates, with the cycle in the payload so the person can see what the two agents were arguing about.
The fix is an ownership rule rather than better prompts. One owner at any moment, stored in a column on the ticket row, and a handoff is an update of that column inside the same transaction that writes the handoff record — so ownership and the reason for the change land together or not at all. An agent works only tickets it owns, and it may not transfer a ticket whose subtask list still has an item assigned to it. That is what makes the ping-pong structurally impossible instead of merely discouraged, and prompt wording was never going to reach it.
Shared State Hazards
Two agents writing the same task state concurrently produce lost updates in exactly the record that has to be trustworthy. The specific shape at Sundry: both specialists load the ticket's state object, modify their own part in memory, and write the whole object back. The returns specialist's refund_issued: true survives for four seconds until the damage specialist writes a copy that predates it, and the state now says a refund that happened did not happen. The next run reads that and issues it again.
The answer is the one from Chapter 6, unchanged: one lock per ticket, held for the duration of a run, with a lease so a crashed process releases it. A second arrival does not queue behind the lock — it appends to the ticket and exits, and the holder picks the new message up. Writes go to fields rather than to a whole blob where the store allows it, and the completed-actions list is append-only with an idempotency key per entry, so the worst outcome of a race there is a duplicate row you can detect rather than a missing one you cannot.
- Passing the full transcript as the handoff — the receiver's context inherits every oversized tool result the sender collected, and re-derives decisions that were already settled and cited.
- Handing off without the completed-actions list — the receiver repeats a write the sender had already made, which is Chapter 8's third route into the double refund reached through one more boundary.
- No ownership rule — the ticket ping-pongs between two specialists while the customer waits, and 23 tickets in one week cross more than twice with nothing sent to anybody.
- Read-modify-write on a shared state blob from two agents — a refund flag written at 09:16 is erased by a copy loaded at 09:15, and the next run pays the customer twice.
- Share structured state through the store both workers read, and keep the message to the summary and the ask.
- Include completed actions, with timestamps and reversibility, in every handoff — to an agent or to a person.
- Store a single owner on the ticket row and change it in the same transaction as the handoff record, so no ticket is ever between owners.
- Lock task state per ticket for writes, with a lease, and make the completed-actions list append-only with an idempotency key per entry.
Knowledge Check
What must a handoff message carry, whether the receiver is another agent or a person?
- The request in the customer's words, the decisions with their citations, the actions completed, and what remains
- The complete transcript of the sending run, so that nothing which was considered along the way is lost at the boundary
- The classification the triage agent assigned, along with the confidence score and the tool calls that produced it
- The trace id of the sending run, so that the receiver can look up whichever parts of the context it decides it needs
Two specialists pass one ticket back and forth six times in 40 minutes. What actually fixes it?
- A single owner stored on the ticket and transferred transactionally, plus cycle detection on the transfer key
- A firmer instruction in both system prompts telling each agent not to send work back to the other
- A hard cap of two transfers per ticket, after which the run simply ends and the customer is asked to write in again
- A supervisor agent sitting above the two specialists that reads both findings and decides which of them keeps it
Why does the human escalation get the same structured object rather than the run's transcript?
- Because a colleague skims twelve turns and misses the completed actions, and then refunds an order twice
- Because support colleagues cannot be expected to understand how the agent's reasoning is structured
- Because transcripts are discarded after the run ends and would not be available to the colleague anyway
- Because a transcript would expose customer data that the escalation policy requires to be redacted first
Both specialists load the ticket's state object, edit their part, and write the whole object back. What goes wrong?
- The later write is built on a stale copy and erases a refund flag, so the next run issues the refund again
- The second write fails a constraint and the specialist keeps retrying until it exhausts the ticket's turn budget
- The state object doubles in size as the two copies are merged, and later reads overflow the context budget for the run
- The two specialists deadlock on the ticket row, and the ticket stalls there until a person notices it is stuck
You got correct