Skip to content
Expedify
Building AI Agents for Sales, Onboarding and Support

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:

Nobody owns it, nothing is due, and it is attached to no record.

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

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.

YesNo

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."

A colleague can act on this without opening the conversation.

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

  1. The deadline lost its hours. Casting to ::date produced midnight, so a 4-hour SLA and a 20-hour one landed on the same value. Keep the timestamp.
  2. 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.