previous: none
The static welcome played successfully. This row proves the call audio path was working at the start.
Post-call diagnostic
An implemented post-call artifact that makes every state explain what it intended to say, what the model generated, what the recording proves, and why a caller heard silence or heard audio after the state boundary.
This is the object future agents should read before opening logs.
{
"verdict": "broken",
"primary_failure": {
"code": "BROKEN_AUDIO_CONTRACT",
"state_id": "replicant_confirm_identity",
"previous_state_id": "welcome_static",
"human": "The identity prompt was generated but never sent to Telnyx.",
"owner": "compiler/playback",
"fix": "Use deterministic playback or skip this prompt."
},
"audio_totals": {
"model_generated_states": 1,
"audio_sent_states": 1,
"silent_prompt_states": 1,
"caller_spoke_during_silence_states": 1
},
"problem_frame": {
"previous_state": {
"state_id": "welcome_static",
"verdict": "audible",
"telnyx_out_duration_ms": 4280,
"agent_energy_ms": 4010,
"audio_completed": true
},
"problem_state": {
"state_id": "replicant_confirm_identity",
"verdict": "silent",
"model_generated": true,
"telnyx_out_duration_ms": 0,
"agent_energy_ms": 0
},
"boundary": {
"silent_prompt_window_ms": 5840,
"caller_whisper": "I have a maintenance request.",
"routing_result": "no_match_yes_no_only"
}
}
}
BrokenCaller heard silence after the welcome.
The flow entered replicant_confirm_identity. Gemini generated the identity prompt, but Live audio was suppressed and no deterministic audio was available. Telnyx sent zero prompt chunks.
Owner: compiler/playback contract
Fix: deterministic playback for suppressed prompts, or skip this prompt and route to the property menu.
jq '.summary.primary_failure' \ logs/post_call/<sid>/state_audio_timeline.json
| Question | Previous state: welcome_static |
Problem state: replicant_confirm_identity |
|---|---|---|
| What was expected? | Static welcome line. | Dynamic identity confirmation prompt. |
| Was audio allowed? | Yes, deterministic playback. | No, Live audio was suppressed by state policy. |
| Was audio sent? | Yes Telnyx outbound duration and agent-channel energy present. | No Model generated output, but Telnyx outbound chunks were zero. |
| What did the recording prove? | Agent channel active during the welcome. | Agent channel silent while the state waited for yes/no. |
| What did Whisper add? | No caller correction needed. | Caller spoke during the silent prompt window: "I have a maintenance request." |
| What should an AI conclude? | The phone path was alive before the transition. | The failure is the state audio contract, not general Telnyx outage or caller silence. |
BROKEN_AUDIO_CONTRACTreplicant_confirm_identity
Session v3:uXdsR2ht5Zj71T7XcK-4aHjk0o8agpSVFO1o8pB8yTnJf7KPay6Nuw, started 2026-07-09T12:37:36.200Z / 07:37:36 CDT.
| Previous audible state | welcome_first, 0.001s-4.407s, 4000 ms agent energy |
| Problem state | 4.409s-30.132s, 57 suppressed chunks, 0 ms agent energy |
| Caller evidence | 2850 ms caller energy; Gemini first heard ¿Qué?; Whisper captured the caller trying to start a maintenance request and asking if the agent was there. |
| Artifact | logs/post_call/v3:uXdsR2ht5Zj71T7XcK-4aHjk0o8agpSVFO1o8pB8yTnJf7KPay6Nuw/state_audio_timeline.json |
AUDIO_AFTER_STATE_EXITmenu_full_announce
Session v3:IxN4WywfRrjSK08g2XMOgNuYJCgKLEYh08sP91i-9dcC9Lqx65YPGw, started 2026-07-09T15:55:34.759Z / 10:55:34 CDT.
| Problem state | 107.871s-110.512s, expected full numbered menu, 14 suppressed chunks, 0 ms agent energy |
| Next state | menu, 110.512s-136.080s, 13618 ms agent energy and 14668 ms caller energy |
| Number evidence | Timed Whisper later captured agent-channel menu language for Press 0 through Press 6, proving transcript text and state-owned audio must be inspected separately. |
| Artifact | logs/post_call/v3:IxN4WywfRrjSK08g2XMOgNuYJCgKLEYh08sP91i-9dcC9Lqx65YPGw/state_audio_timeline.json |
| Surface | Current per-state evidence | Gap exposed by the maintenance call |
|---|---|---|
/api/debug/state_trace |
State id, entered/exited timestamps, duration, exit target, guard evaluations, tool calls, locked-choice cascade, match status, turn latency, diagnosis tags. | Does not prove what audio the caller heard. It can show a state was active and choices failed, but not that the prompt was silent. |
| Section-6 trace | state_enter, audio_pushed, audio_done, barge_in, route_decision, tools, stalls, call end. |
Shows positive playback when it happens. It does not currently emit an explicit "model audio was generated but suppressed" event. |
| Post-call files | Rendered transcript, Whisper comparison, FSM trace, session audio files, local logs. | These are separate lanes. A future AI has to join logs, WAV energy, trace rows, and generated transcript by hand. |
| Live / phone logs | Useful low-level lines such as model_output: suppressed, audio_out, and telnyx_out. |
Correct but not structured as a per-state post-call artifact. Easy to miss and hard to compare against the transcript. |
The state trace and transcript make it look like the assistant spoke, but the audible path is not joined to the state.
state: replicant_confirm_identity prompt: "Based on your caller ID..." locked_choice: confirm_yesno match_status: no_match caller: "I have a maintenance request." transcript: Assistant line appears
ProblemThe reviewer must separately inspect phone logs and WAV energy to realize the assistant line was generated but not heard.
The post-call artifact directly separates generated text from sent audio and heard audio.
state: replicant_confirm_identity expected: identity confirmation prompt live_audio_allowed: false deterministic_audio_available: false model_generated: true telnyx_out_chunks: 0 agent_energy_after_welcome: 0 ms diagnosis: BROKEN_AUDIO_CONTRACT
FixThe row says why the caller heard blank audio and points to the compiler or playback contract.
The static welcome played successfully. This row proves the call audio path was working at the start.
The flow expected a yes/no identity prompt, but the prompt was a suppressed Live output with no deterministic playback. The caller heard silence and spoke a maintenance intent.
{
"session_id": "v3:example",
"flow_id": "property_management",
"summary": {
"verdict": "broken",
"worst_severity": "error",
"findings": [
{
"code": "BROKEN_AUDIO_CONTRACT",
"state_id": "replicant_confirm_identity",
"message": "State suppressed live audio but had no deterministic playback."
}
]
},
"states": [
{
"state_id": "replicant_confirm_identity",
"previous_state_id": "welcome_static",
"started_ms": 4280,
"ended_ms": 10120,
"expected_speech": {
"kind": "locked_choice",
"redacted_text": "Based on your caller ID, I see your unit. Is that right?",
"asset": null
},
"audio_policy": {
"live_audio_allowed": false,
"deterministic_audio_required": true,
"deterministic_audio_available": false,
"suppression_reason": "business_profile.suppress_live_audio_states"
},
"model_generation": {
"output_transcription_chunks": 12,
"generated_text_present": true,
"suppressed": true
},
"transport_output": {
"telnyx_out_chunks": 0,
"telnyx_out_duration_ms": 0,
"audio_pushed": false,
"audio_done": false
},
"recording_energy": {
"agent_active_ms": 0,
"caller_active_ms": 1450,
"silent_ms": 4390
},
"routing": {
"expected": "yes_or_no",
"match_status": "no_match",
"transition": null
},
"diagnosis": [
{
"severity": "error",
"code": "BROKEN_AUDIO_CONTRACT",
"suggested_fix": "Use deterministic playback for suppressed prompts or skip this prompt."
}
]
}
]
}
After an OTA or local run, execute scripts/post_run_audio_gate.py with the session id.
The command writes logs/post_call/<sid>/state_audio_timeline.json and Markdown.
The frame names the previous state, problem state, next state, caller audio, and prompt evidence.
If model generated but Telnyx sent zero audio, fix playback policy or compiler gates, not STT.
.venv/bin/python scripts/post_run_audio_gate.py <sid> jq '.summary.findings[] | select(.severity == "error")' \ logs/post_call/<sid>/state_audio_timeline.json
The gate fails on every error finding and, by default, on AUDIO_AFTER_STATE_EXIT. It also fails when retained data/sessions artifacts are missing unless --allow-unavailable is passed. That is the change detector for the MARS failure class: simulation can pass while the recorded phone path proves the menu audio crossed the state boundary.
| Code | Meaning | Likely owner |
|---|---|---|
BROKEN_AUDIO_CONTRACT | A state required caller-facing audio, but the configured audio path could not produce audible output. | Compiler or playback runtime |
MODEL_GENERATED_BUT_NOT_SENT | Gemini produced output, but RIFF suppressed it before Telnyx. | Live session / transport policy |
STATE_ENTERED_NO_AUDIO | A prompt state entered and no outbound audio occurred inside the allowed window. | Runtime or asset manifest |
AUDIO_AFTER_STATE_EXIT | A prompt state was silent, but agent-channel audio began in the next state. | Playback boundary / trace attribution |
CALLER_SPOKE_DURING_SILENCE | Caller speech occurred after a silent prompt window, usually meaning they were waiting or confused. | Flow UX and playback |
LOCKED_CHOICE_REJECTED_INTENT | A locked-choice state rejected a task intent because only narrow answers were accepted. | Flow compiler / choice spec |