# A promised call-back, end to end

The voice says it will ring back. That sentence only becomes a commitment if the time in it can be tied to something the caller actually said or agreed to; otherwise the tool answers **NOT recorded** and the model cannot claim a booking. A recorded promise gets a due time in the line's timezone, a channel, and a drain that places it — or gives up honestly when it is too late.

- **Status:** Available
- **Audience:** both
- **Channels:** phone
- **In the app:** #/follow-ups, #/calls
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/workflows/phone-follow-up-execution/

## From a sentence to a ring

1. Trigger — the caller and the voice agree on a time, or the voice offers one.
2. User event — the caller's own words are the evidence; the voice's words alone are not.
3. Authentication and workspace resolution — the call is already inside its workspace; the follow-up inherits it.
4. Ingest — `record_event` receives the promise and the `when` phrase.
5. Canonical record — a follow-up with a reason, a channel and a due time, or nothing at all.
6. Reasoning — `voice_engine.due_at` turns "in 10 minutes" or "Friday 3pm" into a real instant in the line's timezone.
7. Knowledge, memory and rules — `follow_up_channel` decides whether this is a call-back or a person's task.
8. Autonomy and approval — the drain obeys the channel's autonomy exactly as any other outbound action.
9. Action through a provider — `chase_phone` places the call when it falls due.
10. Result — answered, `no_answer`, `not_reached`, or refused by a gate.
11. Relationship, timeline and memory — the attempt lands on the person whether or not it connected.
12. Audit, usage and Needs You — an overdue or undeliverable promise becomes something a person is asked about.

## Why a promise is sometimes refused on the call

`record_event` refuses a `when` that shares no time word with the caller's latest time-bearing line. The check (`conversation.when_mismatch`) reads English, Telugu and Hindi. The exception is `when_agreed`: if the caller has just agreed to a time the voice said aloud, the agreement is the evidence and the booking stands.

The refusal is worded as **NOT recorded** on purpose. A tool that answers vaguely lets the model tell the caller it has booked something it has not. One call looped three times over this before the rule existed. A read-back is asked only when the caller neither named nor agreed a time — asking for confirmation of something the caller just said is its own irritation.

| Stage | What you see | What changes | What can fail |
|---|---|---|---|
| Caller names a time | Normal conversation | The phrase is captured with the turn | A garbled line carries no usable time word |
| `record_event` validates | Nothing audible | A follow-up, or an explicit refusal | Time word mismatch — the tool says NOT recorded |
| `due_at` converts | The follow-up shows a date | An instant in the line's timezone | Nothing usable in the words at all; the fallback is 11:00 the next day |
| `follow_up_channel` routes | Either a call-back or a task | Channel set on the follow-up | A person's work ("send the quotation") mis-read as a call-back |
| `chase_phone` drains | The call goes out near the promised time | A `Call` row and an outcome | Gate refusal, no worker, or the promise is now too old to keep |

## Late is a decision, not a drift

A promised call-back that could not be placed does not queue for ever. Past the late cut-off — 24 hours — placing it stops being helpful: a call that arrives a day after "I'll ring you in ten minutes" reads worse than no call. The commitment stops being a dial and becomes something a person is shown, with the reason it was not kept.

"No follow-up needed" schedules nothing, which is worth saying out loud because the alternative — a system that always books something — trains everyone to ignore the queue.

> **Note** A promise the drain never turned into anything has its own finding, `promised_unbooked`, and it appears in the call review rather than waiting to be noticed. That is the difference between a system that forgets quietly and one that reports its own gaps.

## What the follow-up carries with it

- The caller's last lines and the voice's own, so whoever picks it up is not reading a one-line summary of a five-minute conversation.
- A reason, because a dated commitment with no reason is an alarm clock.
- The contact, carrying `phone_key` from the moment lead capture created it — a number is only useful as an identity if it is stored as one.
- The channel, so "review the recording" does not become an outbound call to a customer.

Where the same number resolves to two contact rows, `identify_caller` takes the oldest. That is a deliberate bias towards the relationship that already has history rather than the row created most recently.

## Questions

### The caller agreed to a time but no follow-up was created. Why?

Most often the agreement was to a time the voice never said aloud and the caller never named — for example the caller said "yes, fine" to a question that did not contain a time. `when_agreed` needs the time to have been spoken by one of them. The tool's refusal is in the call record, so you can see which of the two rules did not hold.

### Does a follow-up always mean a phone call?

No. `follow_up_channel` puts a call-back on `phone` and a person's own work — sending a quotation, checking an account — on the `task` channel. The point is that not every commitment made on a call is a commitment to call again.

### What happens to a follow-up whose dial keeps failing?

It is retried while it is still worth keeping, and after the late cut-off it stops being placed and becomes a decision for a person, with the failure reason attached. The alternative — an indefinite retry — produces a call at a time nobody expects.

## Related

- [Outbound phone call, end to end](https://connectbyjbrh.com/docs/workflows/outbound-phone-call/)
- [The call promised a call-back that never happened](https://connectbyjbrh.com/docs/troubleshooting/follow-up-not-booked/)
- [Follow-ups in Connect](https://connectbyjbrh.com/docs/follow-ups/)
- [Preventing a voice agent from promising a call-back it cannot keep](https://connectbyjbrh.com/research/callback-promises/)
- [The same call-back was booked twice](https://connectbyjbrh.com/docs/troubleshooting/duplicate-follow-up/)

## What this page is based on

- AGENTS.md §9a — follow-up integrity and the drain
- `backend/app/voice_engine.py` due_at, `runner.py` chase_phone
- `backend/voice_worker/conversation.py` — when_mismatch, when_agreed
- Connect capability registry (docs-source/facts.py)
