Module · M3 · Make it count: the pipeline
Know when to stop: handing off to a human
Lesson 10 of 16 · 8 min
The handoff is not what happens when your desk fails. It is the feature that makes it deployable. Without a way to stop, an agent has only one move left when it reaches the edge of what it knows — keep going.
But a handoff is only worth having if a human can actually pick it up. Here is one that nobody can.
The handoff that quietly does nothing
This task is real. Lesson 9 created it — the ask_human branch, when the desk was not certain enough to move a deal:
| Field | Value |
|---|---|
| title | Confirm pipeline stage from a conversation |
| priority | medium |
| assigned_to_email | — empty — |
| due_date | — empty — |
| contact_id | — empty — |
| deal_id | — empty — |
title
- Value
- Confirm pipeline stage from a conversation
priority
- Value
- medium
assigned_to_email
- Value
- — empty —
due_date
- Value
- — empty —
contact_id
- Value
- — empty —
deal_id
- Value
- — empty —
It will sit in a shared list until somebody scrolls past it, decides it is probably someone else's, and moves on. Then it will sit there forever. Nothing errored — a task was created, the workflow reported success, and the customer was never contacted.
The one idea. An unassigned, undated task is how an ops function quietly dies. Not with a failure — with a growing list nobody feels responsible for.
What a handoff has to carry
| It needs | Because without it | Where it comes from |
|---|---|---|
| An owner | It belongs to everyone, so it belongs to no one. | The account's owner — a fact in the CRM, not a guess. |
| A due date | There is no such thing as late, so nothing is ever late. | Computed from urgency, in the query. |
| The record | The colleague has to search for who this is. | The contact and deal ids from the lookup. |
| The context | They have to read the whole conversation to start. | A written summary — the one thing the model is genuinely good at here. |
| The ask | "Customer has a query" tells them nothing. | What was requested, in the customer's own words. |
An owner
- Because without it
- It belongs to everyone, so it belongs to no one.
- Where it comes from
- The account's owner — a fact in the CRM, not a guess.
A due date
- Because without it
- There is no such thing as late, so nothing is ever late.
- Where it comes from
- Computed from urgency, in the query.
The record
- Because without it
- The colleague has to search for who this is.
- Where it comes from
- The contact and deal ids from the lookup.
The context
- Because without it
- They have to read the whole conversation to start.
- Where it comes from
- A written summary — the one thing the model is genuinely good at here.
The ask
- Because without it
- "Customer has a query" tells them nothing.
- Where it comes from
- What was requested, in the customer's own words.
The build
Desk — hand off to a human, properly
The model decides IF and writes the summary. The database decides WHO and WHEN. Those are different kinds of question.
Scroll for all 7 steps →
The division of labour is the design. The model decides whether to hand off and writes the summary — both genuinely language jobs. It does not decide who owns the account or when the work is due, because neither is a judgement. Ownership is a fact in the CRM. The deadline is arithmetic:
now() + CASE WHEN urgency = 'high' THEN interval '4 hours' ELSE interval '24 hours' END
That is your SLA, written where it can be read and audited, rather than a date a model was asked to imagine.
It hands off
"This is the third time I'm writing about this. I was told the agents course would be ₹18,000 but the invoice says ₹21,240. Nobody has explained the difference and I need this sorted before Friday or I'm asking for a refund."
| Field | What the desk produced |
|---|---|
| priority | high |
| assigned_to_email | rohit.deshpande@expedifyacademy.in |
| due_date | 15:05 — four hours after the message arrived |
| contact / deal | both linked ✓ |
| description | "The customer has contacted multiple times about a discrepancy between the quoted price of ₹18,000 and the invoiced amount of ₹21,240… They demand an explanation and resolution before Friday, threatening to request a refund if not addressed." |
priority
- What the desk produced
- high
assigned_to_email
- What the desk produced
- rohit.deshpande@expedifyacademy.in
due_date
- What the desk produced
- 15:05 — four hours after the message arrived
contact / deal
- What the desk produced
- both linked ✓
description
- What the desk produced
- "The customer has contacted multiple times about a discrepancy between the quoted price of ₹18,000 and the invoiced amount of ₹21,240… They demand an explanation and resolution before Friday, threatening to request a refund if not addressed."
And the boring case matters just as much. Asked "does the course assume I already know Python?" the same workflow routed to keep_going and created no task at all. A desk that escalates everything is as useless as one that escalates nothing — it just fails more visibly.
Two things this build got wrong first
- The deadline lost its hours. Casting to
::dateproduced midnight, so a 4-hour SLA and a 20-hour one landed on the same value. Keep the timestamp. - to_char() is blocked. The read-query guard rejects it —
Query contains prohibited operation pattern: char(. Return the raw timestamp and let it serialise.
A test worth running on your own desk. Open your task list and find the oldest open item. If nobody owns it, you have found the leak — and it is almost certainly not the only one.
Next: the money arrives. Lesson 12 is the first thing in this course that cannot be undone — a message that has been sent.

