# Steering a live call

`supervisor.py` lets a colleague steer a call while it is happening. Two rules hold it together: an instruction goes into the model's system prompt and nowhere else, so the caller never hears it read out; and the control state — running, paused, taken over — is derived from the guidance log rather than stored beside it.

- **Status:** Available
- **Audience:** both
- **Channels:** phone
- **In the app:** #/calls
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/phone/supervisor/

## Guidance never reaches the caller

When somebody types "do not quote a date, we have not confirmed the stock" into the panel, that sentence has exactly one destination: the model's instructions. It does not go into the spoken reply, it is not written into `call_turns`, and it never touches the wire. The caller hears the next reply, shaped by the guidance, and nothing else.

This is easier to get wrong than it sounds. The natural implementation of "tell the agent something" is to put the text where the agent's text goes, and the natural failure is a voice that reads its own instructions out loud to a customer. Keeping guidance to one path — `update_instructions`, which resends the whole prompt — is what makes that impossible rather than unlikely.

> **Careful** Mid-call notes and supervisor guidance cannot reach a session the SDK has marked immutable. On the default model, guidance is recorded as `guidance_undeliverable` and the call carries on unchanged. If a line exists so that a person can steer it live, choose the model that takes notes and accept the extra second per reply.

## Why the state is derived rather than stored

A supervised call moves through states a single column cannot hold. Somebody pauses the voice, then takes the call over themselves, then releases it — and "released from a take-over that began during a pause" is not the same as "running", but a `state` column would say it was.

So there is no state column. Every control action is an entry on the guidance log, and the current state is computed from the entries in order. The log is the record and the state is a view of it, which means the state can never disagree with the history — and an argument about what happened on a call is settled by reading, not by guessing which write landed last.

The same reasoning is written up as an engineering note; the phone panel is its most demanding user, because the events arrive from a browser and a worker at the same time.

## Silence has to be genuine silence

A paused or taken-over call replies with empty text and a `LISTEN` instruction. On the carrier path that has to render as **no `<Speak>` element at all**, not as a `<Speak>` containing nothing: an empty element still occupies the turn, and the caller hears the line do something odd instead of hearing a colleague talk.

The listening continues while the voice is quiet. Turns are still heard and still recorded, so a person who takes over mid-sentence gets the transcript up to that point rather than an empty screen.

## Live control rides the same log

Actions travel as entries the worker polls for, never as spoken text:

**`engine:hangup`** — End the call now. The ending is attributed to `owner`, not to the caller and not to the model, so the record says who did it.
**`engine:transfer`** — Queue a transfer. What that does today depends on the provider's capability, which is not enabled on the live carrier — see the transfer and escalation page for what actually happens.
**`engine:dtmf`** — Send tones on the line, for an IVR on the far end.
**`engine:mute`** — Stop the voice speaking without ending anything.

Because they are log entries rather than direct calls into the worker, a control action survives the poll being late, and a worker that restarts mid-call reads the log and finds the call already paused. The cost is that control is not instantaneous — it is as fast as the next poll, which is the trade a distributed system makes on purpose.

## Questions

### Can the caller tell that somebody is supervising?

Not from anything Connect says. Guidance changes what the voice says next, which is the point, but no announcement is made and nothing is played to indicate a listener. Whether your business is obliged to say so is a question about your jurisdiction and your own policy, not about the panel.

### What happens to guidance the model never received?

It is recorded as `guidance_undeliverable` against the call. That is deliberately visible rather than silent: a supervisor who typed an instruction and saw no change needs to know the instruction was refused by the session, not ignored by the model.

### Who can supervise a call?

A workspace member with access to the call. The panel runs on the same implementation for the Owner and for a customer workspace — a tenant supervises its own calls exactly as the operator supervises theirs, over its own workspace's data.

## Related

- [Transfer and escalation](https://connectbyjbrh.com/docs/phone/live-transfer/)
- [Deriving control state from the log instead of storing it](https://connectbyjbrh.com/research/derived-control-state/)
- [What a setting cannot change](https://connectbyjbrh.com/docs/phone/model-limits/)
- [The voice worker](https://connectbyjbrh.com/docs/phone/voice-worker/)
- [Who ended the call](https://connectbyjbrh.com/docs/phone/hangup-attribution/)
- [Call transcripts](https://connectbyjbrh.com/docs/phone/transcript/)

## What this page is based on

- Connect phone source pack — `docs-source/sources/PHONE.md` §11, the live supervisor panel
- Connect phone source pack — `docs-source/sources/PHONE.md` §7 and §9, immutable sessions and hangup attribution
- Connect capability registry — `docs-source/facts.py` (`live_supervisor`, `call_transfer`)
