# Phone line health

`line_health` reports four things about a phone line, and each clears by itself once its cause goes away: no voice worker has checked in, the fleet has no capacity left, too many calls inside the window never rang, and the model refused too many sessions. A line that read "ready" while no worker was alive is the defect that made all four necessary.

- **Status:** Available
- **Audience:** both
- **Channels:** phone
- **In the app:** #/phone
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/phone/phone-health/

## The four signals

| Signal | What a caller experiences | How it clears |
|---|---|---|
| No worker has checked in | An inbound call reaches nothing at all — the number connects and there is nobody behind it | A worker heartbeats again |
| The fleet has no capacity left | New calls are refused rather than queued behind saturated workers | Load falls back under the threshold, or the fleet grows |
| Calls that never rang (`not_reached`) | Outbound attempts that produced no ring — the dial, the dispatch or the worker failed | The count of failures inside the window falls back below the minimum |
| Sessions the model refused (`engine_error`) | A call that connected and then could not start a conversation | Refusals stop, and the window rolls forward past them |

They are four separate signals rather than one status because they have four different answers. A saturated fleet needs capacity; a dead worker needs a deploy looked at; a run of `not_reached` usually means the carrier side; a run of `engine_error` usually means the model configuration. Collapsing them into one amber light would throw away the only part that tells you what to do.

## Why a line that says "ready" is not a line that answers

The first signal exists because of a specific and quiet failure: no voice worker had checked in, an inbound call therefore reached nothing at all, and the Phone screen still said ready. The screen was reporting configuration — the line is set up, the number is attached, the engine is selected — and a caller does not experience configuration.

Health is now reported from evidence that something is alive and working, which is a different question from whether it is switched on. The heartbeat file forgets a process it has not seen for an hour, so a worker that died without saying so stops counting as present rather than propping up a ready badge forever.

## Draining by itself

Nothing here is acknowledged, dismissed or cleared by a person. Each signal is computed from current evidence, so it goes away when the evidence does — a worker that heartbeats, load that falls, a failure window that rolls forward past the failures in it.

The failure signals are windowed rather than absolute: a line is unhealthy once it has passed `HEALTH_MIN_FAILURES` inside `HEALTH_WINDOW_HOURS`, not on the first bad call. One failed dial is an event; five inside a few hours is a condition. Windowing is also what lets the signal recover without anybody pressing anything — old failures simply age out.

> **Note** A health signal that needs a human to clear it becomes a permanent amber light nobody reads. Deriving the state from evidence and letting it drain is the same principle the supervisor panel uses for its control state, applied to operations.

## Health during a deploy

A restart is the most common reason to see a worker signal, and it is designed to be brief. A draining worker is given 180 seconds to finish the calls it is holding, and the service unit allows 210 before it is killed — the margin exists so that a call in progress ends properly rather than being cut mid-sentence by a timer.

During that window, calls in progress continue and new calls go to other workers. The signal you should not see is a worker absence that outlasts a deploy: that is no longer a rolling restart, it is a fleet with nobody in it.

## What health does not tell you

It is about reachability, not about quality. A line can be entirely healthy and still hold slow, awkward conversations — that is what the call review is for, and its findings are a completely separate reading. It also says nothing about whether the number is correctly routed at the carrier, which is visible by reading the carrier's own call log against the `calls` table.

## Questions

### The line shows unhealthy but calls are being answered. Which do I believe?

Both, usually. The failure signals are windowed, so a line that had a bad half hour reads unhealthy for the rest of the window while working normally now. Look at what is being counted — recent `not_reached` or `engine_error` calls are listed, and if the newest is an hour old the condition is already over.

### Can I dismiss a health signal?

There is nothing to dismiss. Every signal is computed from current evidence rather than stored as a flag, so the only way to clear one is to remove its cause — which is the intended workflow.

### Does a customer workspace see the same signals?

Yes. Line health is reported for the line, and a tenant reads its own line's health on the same Phone screen over the same implementation. What a tenant does not see is the fleet-wide operator view of every workspace's calls, which is the platform operator's job rather than a customer's.

## Related

- [Capacity and admission control](https://connectbyjbrh.com/docs/phone/worker-capacity/)
- [The voice worker](https://connectbyjbrh.com/docs/phone/voice-worker/)
- [Stuck calls and the sweep](https://connectbyjbrh.com/docs/phone/stuck-calls/)
- [Calls that were never a conversation](https://connectbyjbrh.com/docs/phone/not-a-conversation/)
- [Why 'connected' is not enough to prove mailbox health](https://connectbyjbrh.com/research/mailbox-health-beyond-connected/)
- [Executing phone follow-ups](https://connectbyjbrh.com/docs/phone/phone-followup-drain/)

## What this page is based on

- Connect phone source pack — `docs-source/sources/PHONE.md` §5, line health and capacity
- Connect phone source pack — `docs-source/sources/PHONE.md` §9, refusal and not-reached
- Connect capability registry — `docs-source/facts.py` (`line_health`, `MEASURED.drain_timeout_s`)
