# Creating a follow-up, end to end

A commitment is spoken or written; the guard checks the proposed time against what was actually said; `due_at` resolves it in the line's timezone; `follow_up_channel` picks the channel; `schedule` writes one row — unless that person already has one at that instant. The path is built to fail loudly rather than book a time nobody named.

- **Status:** Available
- **Audience:** both
- **Channels:** phone, email, whatsapp
- **In the app:** #/follow-ups, #/calls
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/workflows/create-follow-up/

## The thirteen stages

1. TRIGGER — somebody makes a commitment. "I'll ring you back at three", "I'll send the quotation tomorrow", or a written promise on a thread.
2. USER / EXTERNAL EVENT — the words arrive: a caller speaking on a line, a message delivered by a provider, or a person filling in the form on the follow-ups screen.
3. AUTH / WORKSPACE RESOLUTION — the line, mailbox or session decides the workspace. Every downstream record is scoped to it, and row-level security backs that boundary beneath the application.
4. INGEST / REQUEST — on a call, `schedule_follow_up` carries the caller's last lines and the voice's own alongside the proposed booking. Without that evidence the next stage has nothing to check against.
5. CANONICAL RECORD — the commitment attaches to the canonical Person, not to an address. A contact created by lead capture carries `phone_key` from the start, so a call-back has a number to reach.
6. CLASSIFICATION / RESEARCH / REASONING — `follow_up_channel` reads what was promised: a call-back becomes `phone`, a person's own work — "send the quotation", "review the recording" — becomes `task`, and "no follow-up needed" schedules nothing at all.
7. KNOWLEDGE + MEMORY + RULES — the time guard runs. `record_event` refuses a `when` that shares no time word with the caller's latest time-bearing line — `conversation.when_mismatch`, in English, Telugu and Hindi — unless the caller has just agreed to a time the voice said aloud, which is `when_agreed`. A refusal answers **NOT recorded**, in those words, so nothing downstream can claim a booking. A read-back is asked for only when the caller neither named a time nor agreed to one.
8. AUTONOMY / APPROVAL — none is needed to create a row. Creating a commitment is not acting on it; the approval question belongs to execution, later.
9. ACTION / PROVIDER — `due_at` turns the promised words into a time in the line's timezone: "in 10 minutes", "tomorrow 11am", "Friday 3pm", "today evening". Nothing usable falls back to 11:00 tomorrow. Then `schedule` writes the row.
10. RESULT — one row, with a person, a when, a channel and a reason. Or no row: the duplicate test refuses a second follow-up with the same *when* for the same person, and the guard refuses a time nobody said.
11. RELATIONSHIP / TIMELINE / MEMORY — the commitment appears on the person's record and is counted in what is open against them. It survives a merge, because a merge preserves follow-ups from both sides.
12. AUDIT / USAGE / NEEDS YOU — the creation is recorded with who or what made it. `promised_unbooked` is the finding when a promise was made and nothing was captured behind it — a defect worth reviewing, not a silence.
13. NEXT — the row waits for its due time, and then [Executing a follow-up, end to end](/docs/workflows/execute-follow-up/) takes over.

## Stage by stage

| Stage | What you see | What changes | What can fail |
|---|---|---|---|
| Commitment | A conversation | Nothing yet | A promise so vague nobody could act on it |
| Evidence | Nothing | The caller's and the voice's lines travel with the booking | A booking attempted without them cannot be checked |
| Channel | The channel on the row | `phone`, `task` or a message channel | Human work classified as a call, which then rings somebody about a document that does not exist |
| Time guard | A read-back, or a refusal | Nothing on a refusal | A time the caller never said — refused, and correctly |
| Due time | A timestamp in the line's clock | The row's `when` | A weekday resolving forward when the caller meant today |
| Duplicate test | A refusal, or the row | Nothing when it refuses | Two different promises due at the same instant collide |
| Record | The row on the queue and the person | The open-commitment count | Nothing: the row and its history are written together |

## Where this flow refuses on purpose

- **The time was not said.** A mismatch between the proposed `when` and the caller's own time-bearing line is refused rather than parsed generously. Before that rule existed, one production call looped three times trying to book a time nobody had named.
- **The tool says NOT recorded, literally.** The wording exists so a model cannot go on to tell a caller their appointment is booked when it is not.
- **No follow-up was needed.** An empty queue after a call that required no chasing is the correct outcome, not a missed booking.
- **The same commitment already exists.** Same person, same `when`. The older row stands, with the reason written closest to the promise.

> **Note** Each of those refusals is visible at the moment it happens. None of them is a silent drop, which is the failure mode this flow is designed around: a commitment that exists in a customer's memory and nowhere in your records.

## Doing it by hand

1. Start from the person or the thread.
   - Result: The row attaches to the relationship and appears in the open commitments against them.
2. Write the reason as a promise, a dependency and an exit condition.
   - Result: Whoever inherits it can act without reconstructing the conversation.
3. Set the time you actually said, then pick the channel that keeps the promise.
   - Result: The duplicate rule protects you, and the right drain — or no drain, on `task` — picks it up.

## Questions

### Why would a call end with no follow-up when I heard a promise?

Either the promise named no time and the guard refused the booking, or the classification decided nothing needed scheduling. `promised_unbooked` is the finding that distinguishes the two: it flags a promise with nothing behind it, which is worth reviewing.

### Does creating a follow-up need anybody's approval?

No. Recording a commitment is not acting on one. The approval question arrives when the row comes due and the drain attempts something through the channel's own gates.

### What if the caller says a time in a language other than English?

The time-word check runs in English, Telugu and Hindi, so a time named in any of those can be matched against the proposed booking rather than refused for looking unfamiliar.

## Related

- [Creating a follow-up](https://connectbyjbrh.com/docs/follow-ups/creating/)
- [Due dates and times](https://connectbyjbrh.com/docs/follow-ups/due-time/)
- [Duplicate follow-ups](https://connectbyjbrh.com/docs/follow-ups/duplicates/)
- [Executing a follow-up, end to end](https://connectbyjbrh.com/docs/workflows/execute-follow-up/)
- [Choosing the channel](https://connectbyjbrh.com/docs/follow-ups/channel-choice/)
- [The call promised a call-back that never happened](https://connectbyjbrh.com/docs/troubleshooting/follow-up-not-booked/)

## What this page is based on

- `docs-source/sources/PHONE.md` §10 — follow-up integrity, `due_at`, `follow_up_channel` and `promised_unbooked`
- `docs-source/sources/GENERAL.md` §7 — the follow-up channels and operations
- `docs-source/sources/CHANNELS.md` §7 — the reason and duplicate protection
- Connect capability registry (docs-source/facts.py)
