Skip to main content
Version: 0.9.17

Call lifecycle (Stasis)

Delphi does not run voice logic inside Asterisk dialplan. Every call leg is routed into an Asterisk Stasis application and handed to TelPhi over the Asterisk REST Interface (ARI). TelPhi owns the entire conversation from the moment a channel enters Stasis until it is torn down. This page is the canonical reference for that lifecycle, including how the max-call-duration limit is armed and enforced.

For the surrounding media/SIP topology, see Voice service operations. For where the duration value comes from per flow, see Flow Settings — Call duration.

What Stasis is

Stasis is an ARI application — a named bucket (ARI_APP) that channels are sent to with the Stasis(<app>) dialplan application. While a channel is in a Stasis app, Asterisk emits events for that channel to the ARI client that has claimed the app, and the client can issue commands (play, record, hangup, bridge, …) on the channel. TelPhi is that ARI client.

The two events that bracket a call:

  • StasisStart — fired when a channel enters the Stasis app. This is TelPhi's "a call arrived" signal.
  • StasisEnd — fired when a channel leaves Stasis (typically because it was hung up). This is TelPhi's "clean up this call" signal.

Lifecycle

1. StasisStart — resolve and arm

On StasisStart, TelPhi identifies the flow by the channel's entry point and looks up the prepared flow in Redis (lookupByPhone / lookupByEndpoint / lookupByApp / lookupByExt). The result is a PreparedFlow: the flow definition, resolved providers, audio plan, and the resolved max call duration + warning message.

TelPhi then:

  1. Wires the media plane (an Asterisk ExternalMedia channel bridged over a WebSocket) and builds the per-direction audio config.
  2. Starts the AI provider session (realtime, or a modular STT → LLM → TTS pipeline).
  3. Arms the max-duration timers (see below).

2. Conversation

The AI provider drives the conversation: caller speech is transcribed, turns are generated, and responses are played back. TelPhi serializes speech output and runtime commands so callers hear complete prompts before the line drops (see Speech playback and hangup).

3. StasisEnd — teardown

When the channel is hung up — by the caller, the AI, a transfer, or the max-duration limit — Asterisk fires StasisEnd. TelPhi's cleanup handler clears any armed timers, tears down the media session, and flushes the per-channel state from its in-memory map. The stashed termination reason is forwarded so it lands on the call detail record.

Max call duration limit

Each call on a flow has a maximum duration. When the cap is reached the channel is force-hung-up and the termination is classified as max_call_duration. The limit has two parts: where the value comes from, and how the timer is enforced.

Where the value comes from

The effective duration is resolved once, at flow-load time:

  • Per-flow overridesessionConfig.maxCallDurationSeconds on the flow definition (set in Flow Settings → Call duration; defaults to 5400s / 90 min via the flow schema). This is the authoritative value when the flow author sets it.
  • Deployment fallback — when a flow leaves the duration unset, the env var MAX_CALL_DURATION_SECONDS (default 7200s / 2h) is used.

The per-flow value is not clamped against the env value at runtime — the per-flow value overrides the deployment default outright. The flow schema validates the per-flow value's bounds on save.

The warning message follows the same precedence: per-flow sessionConfig.maxDurationWarningMessage first, then the env var MAX_CALL_DURATION_WARNING_MESSAGE, then nothing (silent hangup).

NameSourceScopeDefaultDescription
MAX_CALL_DURATION_SECONDSenvvoice7200Fallback max call duration (seconds) when a flow does not set its own. TelPhi force-hangs-up any call older than this and classifies the hangup as max_call_duration. A per-flow sessionConfig.maxCallDurationSeconds value overrides this.
MAX_CALL_DURATION_WARNING_MESSAGEenvvoice(empty)Fallback warning text spoken ~10s before the max-duration hangup, used when a flow sets no warning of its own. Empty = silent hangup. A per-flow sessionConfig.maxDurationWarningMessage value takes precedence.

How the timer is enforced

When a channel enters Stasis, TelPhi arms two timers from the resolved duration:

  • a warning timer that fires roughly 10 seconds before the cap;
  • a final timer that fires at the cap.

When the warning timer fires, TelPhi speaks the warning message (if any) and mutes caller input for the reminder. When the final timer fires, TelPhi stashes the termination reason max_call_duration and hangs the channel up through the normal termination path, so the reason propagates to the CDR.

In-memory only

The timers and per-channel state live in TelPhi process memory (an in-memory map keyed by channel id), not in Redis. If TelPhi restarts mid-call, the timers for active calls are lost. On restart, TelPhi reconciles its in-memory map against Asterisk ARI and reaps stale entries for channels that no longer exist; calls that are still alive continue without an armed duration timer for the remainder of that process lifetime (a new call after restart arms timers normally).

See also