# Placing outbound calls

An outbound call passes five gates before a number is dialled: consent to be called, any memory block against the contact, the autonomy mode for the voice channel, the budget, and the line's own permission and hours. Both engines dispatch through the same call, so an engine-placed call, a follow-up call and a browser call are gated identically, and a refusal is recorded with its reason.

- **Status:** Available
- **Audience:** both
- **Channels:** phone
- **In the app:** #/calls, #/follow-ups
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/phone/outbound-calls/

## The gates, in the order they refuse

| Gate | What it checks | What you see |
|---|---|---|
| Consent | Whether this person may be called at all on this channel | `refused: consent` against the follow-up or the request |
| Memory block | A `block:phone` directive stored against the contact | The call is not attempted; the block is visible in Memory and can be forgotten |
| Autonomy | The mode on the `voice` channel at the narrowest applicable scope | `autonomous` places it; `ask_before_send` queues an approval; `draft_only` and `off` do not ask |
| Budget | Whether the workspace can pay for the call at audio-token rates | `refused: budget`, before any dial |
| Line | Outbound permission and hours on the line the call would use | A line that is not ready is retried in an hour rather than failed |

Everything reaches the carrier through `comms_providers.dispatch_call` with a `placer` naming what asked for the call, which is what makes the gating uniform. There is no path that dials a number without passing through it — not the realtime engine, not the follow-up drain, not a person clicking dial in the browser.

## Blocking is a memory, not a checkbox

A block against a contact is a `connect_memory` row tagged `block:phone` in its tag list, not a column on the contact. That looks like an implementation detail and is not: because `directives()` reads only the tag list, a block written once holds across channels, across conversations and into the future, and a person can read it and remove it in the same place they read everything else Connect knows.

It also means a block cannot be quietly lost by a record being merged or recreated, and it cannot be set by accident in the body of a note. If a contact is not being called and consent looks fine, the tag list is the next place to look.

## Call-backs the engine places by itself

Most outbound calls in a working system are not somebody deciding to ring a customer — they are follow-ups coming due. `runner.chase_phone` drains `phone` follow-ups on every tick, two per tick, through the same place call path and therefore the same gates. Each attempt is recorded as "call placed", "awaiting approval" or `refused:` with the reason.

**Line not ready** — Retried in an hour. Readiness is transient — a worker restart, a saturated fleet — so failing the follow-up would throw away a commitment for a temporary condition.
**More than 24 hours late** — Closed as `missed:` and never rung. A call-back a day after it was promised is worse than none, and pretending otherwise makes the record dishonest.
**Held for approval** — The follow-up stays open and the approval appears in Needs You. Nothing dials until a person says yes.
**Work for a person** — Follow-ups on the `task` channel are never dialled by anything. They appear as work for a human and drain only when a human does them.

## When the dial itself fails

A dial that the carrier answers with a timeout — 408, 503 or 504 — is dialled once more, because a timeout is not evidence that anything happened. A leg the carrier reports as answered but which never joins the room is written off after the join timeout instead of being waited for; holding capacity open for a leg that is never coming costs other callers their admission.

A call that never rang at all is `not_reached`, which is a different outcome from `no_answer` — the phone ringing with nobody picking up. Neither creates a lead, and the distinction matters when a line's health is being judged: a run of `not_reached` results points at the dial, the dispatch or the worker, while a run of `no_answer` results points at the list being called.

> **Careful** An outbound call cannot be transferred to a colleague once it is connected. Completed human transfer is `foundation` — the escalation phrase queues a transfer and the supervisor panel can act on the live call, but the handover depends on a provider capability that is not enabled on the live carrier.

## Questions

### Can I stop Connect calling one specific customer without turning the channel off?

Yes — that is exactly what a block is for. A `block:phone` memory against that contact stops calls to them while the rest of the channel carries on, and it survives the contact being updated or the conversation ending.

### What does `ask_before_send` mean on a phone call, when there is nothing to read?

It holds the decision to place the call, not the words. The approval in Needs You says who would be rung and why, and nothing is dialled until somebody agrees. `draft_only` on the voice channel prepares the intent and deliberately does not even ask.

### Why does a refused call still appear anywhere?

Because a refusal is a decision, and the audit trail records decisions including the ones that produced no action. Without that, a follow-up that was blocked and a follow-up that was forgotten look identical a month later.

## Related

- [Consent on calls](https://connectbyjbrh.com/docs/phone/call-consent/)
- [Business hours and the closed line](https://connectbyjbrh.com/docs/phone/business-hours/)
- [The budget that stops a call](https://connectbyjbrh.com/docs/phone/voice-budget/)
- [What Connect may do](https://connectbyjbrh.com/docs/autonomy/)
- [Follow-ups in Connect](https://connectbyjbrh.com/docs/follow-ups/)
- [Memory in Connect](https://connectbyjbrh.com/docs/memory/)

## What this page is based on

- AGENTS.md §9a — outbound dispatch and its gates (via docs-source/sources/PHONE.md)
- AGENTS.md §7 — the phone follow-up drain (via docs-source/sources/GENERAL.md)
- `backend/app/comms_providers.py` — `dispatch_call` and the placer
- `backend/app/connect_memory.py` — `directives()` and block tags
