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.
The thirteen stages#
- 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.
- 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.
- 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.
- INGEST / REQUEST — on a call,
schedule_follow_upcarries 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. - CANONICAL RECORD — the commitment attaches to the canonical Person, not to an address. A contact created by lead capture carries
phone_keyfrom the start, so a call-back has a number to reach. - CLASSIFICATION / RESEARCH / REASONING —
follow_up_channelreads what was promised: a call-back becomesphone, a person's own work — "send the quotation", "review the recording" — becomestask, and "no follow-up needed" schedules nothing at all. - KNOWLEDGE + MEMORY + RULES — the time guard runs.
record_eventrefuses awhenthat 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 iswhen_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. - AUTONOMY / APPROVAL — none is needed to create a row. Creating a commitment is not acting on it; the approval question belongs to execution, later.
- ACTION / PROVIDER —
due_atturns 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. Thenschedulewrites the row. - 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.
- 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.
- AUDIT / USAGE / NEEDS YOU — the creation is recorded with who or what made it.
promised_unbookedis the finding when a promise was made and nothing was captured behind it — a defect worth reviewing, not a silence. - NEXT — the row waits for its due time, and then Executing a follow-up, end to end 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
whenand 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.
Doing it by hand#
Start from the person or the thread.
Result The row attaches to the relationship and appears in the open commitments against them.
Write the reason as a promise, a dependency and an exit condition.
Result Whoever inherits it can act without reconstructing the conversation.
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.