# Executing a follow-up, end to end

A row falls due, the drain takes it — two per tick on the phone channel — and runs it through the same gates as any outbound action on that channel. One of three outcomes is written back: placed, awaiting approval, or refused with a reason. More than twenty-four hours late and it is closed as `missed:` instead of rung.

- **Status:** Available
- **Audience:** both
- **Channels:** phone
- **In the app:** #/follow-ups, #/needs-you, #/calls
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/workflows/execute-follow-up/

## The thirteen stages

1. TRIGGER — the row's due time passes. Nothing external happens; time is the trigger, which is what makes this flow different from every other one in this section.
2. USER / EXTERNAL EVENT — none at the start. The customer has done nothing; they are simply waiting for something they were promised.
3. AUTH / WORKSPACE RESOLUTION — the row belongs to exactly one workspace, and the drain acts within it. There is no cross-workspace queue and no privileged operator pass through this path.
4. INGEST / REQUEST — `runner.chase_phone` picks up due `phone` rows on the engine's tick, **two per tick**, so a batch of call-backs is worked through over several passes rather than seizing every line at once.
5. CANONICAL RECORD — the row resolves to its Person and to a reachable identity: a number for a call, a mailbox or handle for a message. A row whose person has no usable identity cannot be executed.
6. CLASSIFICATION / RESEARCH / REASONING — the channel decides the action. `task` rows are never picked up at all; nothing drains them, at any tick.
7. KNOWLEDGE + MEMORY + RULES — what the action will say is grounded the same way any outbound message is. On a call, `safe_sales` still refuses a commercial term Knowledge does not support, follow-up or not.
8. AUTONOMY / APPROVAL — the channel's autonomy rule decides whether the action may leave. A held attempt is recorded as `awaiting approval <id>` and joins Needs You, ranked with everything else waiting on a person.
9. ACTION / PROVIDER — a permitted phone row goes through `voice_engine.place_call`, with the same gates as any outbound call: suppression and do-not-contact, line readiness, and what the workspace's allowances permit. A line that is not ready is retried in an hour.
10. RESULT — one of three strings against the row: `call placed <id>`, `awaiting approval <id>`, or `refused: <reason>`. A placed call becomes an ordinary call with its own transcript, summary and cost.
11. RELATIONSHIP / TIMELINE / MEMORY — the attempt appears on the person's timeline, and a successful action stops the commitment being owed. Anything durable learned on the resulting call can be remembered against that contact.
12. AUDIT / USAGE / NEEDS YOU — the attempt is recorded with its outcome and its reason, usage is metered against the workspace, and anything held sits in front of a person while the clock keeps running on it.
13. NEXT — the row is completed, retried, or — past twenty-four hours late on the phone channel — closed as `missed:` and never rung, because a very late call-back is worse than none.

## Stage by stage

| Stage | What you see | What changes | What can fail |
|---|---|---|---|
| Due | The row goes overdue | Nothing yet | Nothing — a minute between due and picked up is the tick, not a fault |
| Pick-up | Two rows moving per tick | The rows being attempted | A long queue means later rows wait, and ageing rows can pass the cut-off |
| Identity | Nothing | Nothing | A person with no reachable identity on that channel |
| Autonomy | An item in Needs You | The row shows `awaiting approval` | Nobody works the queue, and the held row ages towards `missed:` |
| Gates | A refusal with its reason | The refusal on the row | Suppression, an unready line, or an exhausted allowance |
| Action | A call in progress | A call record with its own id | The call itself fails; that is the call's own failure path, not the follow-up's |
| Outcome | One of three strings | The row's recorded result | Reading `awaiting approval` as `refused`, and fixing the wrong thing |
| Lateness | A closed row reading `missed:` | The row closes | Nothing was rung, deliberately |

## Reading the outcome correctly

**`call placed <id>`** — The commitment to ring was kept. Follow the id for what was said; the follow-up's part is done.
**`awaiting approval <id>`** — Nothing has been dialled. A person must decide, and the row keeps ageing while they do.
**`refused: <reason>`** — A gate decided. Some reasons clear themselves — an allowance resets, a line recovers — and some are the correct permanent answer, such as a do-not-contact entry.
**`missed:`** — The twenty-four-hour cut-off. Nobody abandoned this; the clock ran out, and the row still says what was promised.

> **Careful** The one to act on fastest is `awaiting approval`. A refusal has already been decided; a held row is a customer waiting on a decision that takes seconds, and it is the commonest way a phone follow-up reaches the lateness cut-off without anyone intending it.

## What this flow will not do

- **It will not ring somebody who has asked not to be rung.** A scheduled action is still an action, and suppression applies unchanged.
- **It will not execute a `task` row.** Nothing drains that channel, so the wait is the whole answer.
- **It will not retry forever.** A specific case — an unready line — is retried in an hour. Everything else is recorded and left for a person.
- **It will not ring a very late row.** Past twenty-four hours the call is not made at all, which is the deliberate outcome rather than a failure to reach the customer.

## Questions

### Why is a follow-up subject to the same gates as an ordinary call?

Because it is an ordinary call. Placing it through `voice_engine.place_call` rather than through a private path is what guarantees a scheduled action cannot bypass suppression, allowances or the line's health.

### Can the drain be made faster than two rows per tick?

The rate is a property of the drain rather than a per-workspace setting, and it exists to stop a queue of due rows seizing every line at once. A queue consistently outrunning it is a signal about how many call-backs are being promised.

### What happens to the row while an approval is waiting?

It stays open, still owed, and still ageing against its due time — including against the twenty-four-hour phone cut-off. Approvals are not a pause on the clock.

## Related

- [Executing a follow-up](https://connectbyjbrh.com/docs/follow-ups/execution/)
- [Approval on a follow-up](https://connectbyjbrh.com/docs/follow-ups/approval/)
- [A follow-up that is too late](https://connectbyjbrh.com/docs/follow-ups/stale/)
- [Creating a follow-up, end to end](https://connectbyjbrh.com/docs/workflows/create-follow-up/)
- [A promised call-back, end to end](https://connectbyjbrh.com/docs/workflows/phone-follow-up-execution/)
- [A follow-up did not go out](https://connectbyjbrh.com/docs/troubleshooting/follow-up-not-executed/)

## What this page is based on

- `docs-source/sources/GENERAL.md` §7 — `chase_phone`, its rate, outcomes and the lateness rule
- `docs-source/sources/CHANNELS.md` §7 — follow-ups and support
- `docs-source/sources/CHANNELS.md` §6 — `safe_sales` on any outbound line
- Connect capability registry (docs-source/facts.py)
