← Choice Health 2026-07-10-browser-playback-debug-replay-plan.md raw .md

Browser Playback Debug and Replay Plan

Status: implementation plan
Priority: debug and replay first; do not change property-management flow behavior until the next call can prove what browser audio played.

Problem

The caller heard the agent audio play and immediately say the same thing again. The browser-session debugger can reconstruct FSM visits, but it cannot prove a browser double-play because the browser path currently retains neither the played agent PCM nor durable playback lifecycle events. A repeated state and a repeated audible prompt are different failures.

Objective

For every browser call, the retained artifact and debugger must answer:

  1. Which agent audio was requested, generated, started, stopped, and completed?
  2. Which state visit owned each playback span?
  3. Was a prompt played twice, and was the duplicate static, model-generated, re-entry, nudge, or client replay?
  4. At what exact recorded timestamp did the first duplicate begin?
  5. Can an operator pause, rewind, and fork simulation immediately before that event?

Current Reality

The repository does not yet satisfy the full objective:

Capability Current state
Recorded event step/reverse implemented in the read-only cursor engine
State visits and redacted variable boundaries implemented, with known fold-mismatch findings on the real call
Browser playback lifecycle evidence implemented for static audio at actual client callbacks; streamed model-audio completion/fingerprint remains
Agent played-audio recording on browser calls unavailable/empty on the reported call
Millisecond synchronized media replay implemented for retained local WAV plus canonical events/transcript; carrier alignment remains
Duplicate playback detector implemented for matching lifecycle fingerprints within the bounded gap
Shareable issue point implemented with session, time, event, confirmation, and deep-link URL
State capsule export specified, not implemented
Variable/audio/input fork execution not implemented; fork is intentionally rejected by the current engine

Implementation must follow the causal dependency order: capture truthful playback evidence, normalize the shared clock, deliver recorded media replay, export restorable checkpoints, then enable S1 state simulation. A polished fork button before those layers exist would be a mock, not a debugger.

Playback Ledger Contract

Add canonical durable events, emitted at the browser playback boundary rather than inferred from FSM intent:

Event Required fields
audio_requested session_id, visit_id, state_id, playback_id, source_kind, content_fingerprint, request epoch
audio_started all identifiers, actual start epoch, transport/browser playback mechanism
audio_completed identifiers, end epoch, played duration, completion reason
audio_interrupted identifiers, epoch, interruption reason, elapsed duration
audio_failed identifiers, epoch, failure code, safe diagnostic

source_kind is one of static_asset, model_audio, entry_line, nudge, or unknown. content_fingerprint is a session-safe fingerprint of the resolved text/audio asset; it supports duplicate detection without retaining prohibited raw content.

The browser emits acknowledgement events after it actually starts/stops playback. The server writes the immutable ledger. Client-only console logs are not evidence.

Audio Evidence Contract

  1. Attempt to capture browser-played agent PCM into audio_agent_played.wav with a declared sample rate and channel map.
  2. If browser audio cannot be captured, retain the playback spans above and record AGENT_PLAYED_AUDIO_UNAVAILABLE with the technical reason.
  3. Never create a 44-byte empty WAV and label it recorded. The manifest must say unavailable or empty_capture.
  4. The finalizer links each playback span to WAV offsets when audio exists.

Double-Play Detector

The post-call artifact derives DUPLICATE_AGENT_PLAYBACK when two completed or overlapping playback spans have the same content fingerprint and satisfy a bounded gap policy.

Output fields:

{
  "code": "DUPLICATE_AGENT_PLAYBACK",
  "first_playback_id": "...",
  "duplicate_playback_id": "...",
  "first_visit_id": "menu#1",
  "duplicate_visit_id": "menu#1",
  "gap_ms": 82,
  "source_pair": ["static_asset", "model_audio"],
  "confidence": "recorded"
}

Do not label two deliberately different prompts as duplicates solely because they occur in the same state.

Replay UX

The debugger has one shared recorded clock.

Two synchronized cursors

The implementation must not confuse media time with executable state:

Each audio artifact declares call_start_epoch, media_start_offset_ms, sample rate, channel map, and duration. Every transcript segment and FSM event carries clock_ms on the same call clock. The UI derives the event cursor by binary searching the last event whose clock_ms is at or before the media cursor. Seeking, pausing, or rewinding therefore moves audio, transcript, state, variables, and evidence together without inventing sub-event state.

The browser uses one playback controller for the retained audio rather than one independent player per state. Play advances the media cursor; animation-frame updates project the event cursor. Step and Reverse pause playback, select the next or previous event, and seek audio to that event's media offset. Audio is never synthesized during recorded replay.

Simulation Boundary

Playback replay is immutable. The operator may place the visual cursor at any timestamp, but deterministic execution can fork only from a restorable causal checkpoint. Version 1 checkpoints are:

  1. state-visit entry, before entry actions;
  2. immediately before a caller-input/DTMF selection;
  3. immediately before a tool invocation whose result has a fixture;
  4. immediately before an audio outcome (completed, interrupted, or failed) is consumed by the FSM.

If the operator clicks Fork halfway through audio, the UI preserves that timestamp as the issue marker but restores execution from the nearest preceding checkpoint. It must display both Issue at 42.180 s and Branch executes from menu#2 entry at 40.933 s. Claiming arbitrary instruction-level restoration would require serializing the live model connection, transport buffers, audio decoder, pending callbacks, and Python stack, which this design does not do.

State test capsule

Every fork exports an immutable riff.state_test_capsule/v1 containing:

The capsule is the program input. The recorded timeline remains the oracle. Candidate execution runs in an isolated subprocess with network and side-effecting tools disabled.

Supported mutations

“Change audio” is split into causal operations rather than editing waveform pixels and pretending the FSM changed:

Mutation Branch behavior
Slot value Apply an overlay at checkpoint entry, then re-run guards/state logic
Caller answer Substitute a transcript fixture at the caller-input checkpoint
Caller audio Attach a PCM fixture, re-run the selected STT engine, then feed its transcript result
Transcript source Select recorded Gemini, Whisper, or DTMF evidence and re-run resolution
Tool result Substitute a schema-valid read-only fixture before the tool-result event
Playback outcome Inject completed, interrupted, failed, duplicated, or delayed lifecycle events
Agent prompt/text Run candidate prompt/template resolution and produce simulated text/audio references
TTS/voice Regenerate simulated agent audio under a pinned candidate voice configuration

Changing agent output audio does not retroactively change the recorded call. It creates a purple simulated playback span. Newly generated audio is labelled simulated; it may be auditioned but never used as evidence of what the caller heard.

Branch continuation levels

Reverse before the fork checkpoint discards the branch and restores the byte-identical recorded projection. Editing a branch never changes Entry or Recorded Exit columns.

Work Packets

PB1 — Browser Playback Event Capture

PB2 — Honest Audio Finalization

PB3 — Artifact and Detector

PB4 — Time-Travel Debugger UI

PB4A — Shared Clock and Media Controller

PB4B — State Capsule and S1 Fork

PB5 — Property-Management Validation

Acceptance Gates

  1. Every browser playback attempt has a durable lifecycle result or explicit capture gap.
  2. Agent WAV availability is truthful; empty headers are never successful recordings.
  3. A controlled duplicate produces DUPLICATE_AGENT_PLAYBACK with both source refs and visits.
  4. The replay UI can pause on each duplicate start and reverse to its request event.
  5. CLI, API, and UI identify the same duplicate pair and determinism hash.
  6. A property-management rerun either identifies the duplicate mechanism or explicitly states what evidence remains unavailable.
  7. Seeking any retained audio timestamp projects the same transcript/state event after page reload; step-forward then reverse restores the original determinism hash and audio position.
  8. Forking from a mid-audio issue marker visibly restores from the named prior checkpoint; it never claims to resume a live Python/model/audio stack.
  9. An S1 variable or caller-answer mutation can change the simulated exit while recorded Entry, Recorded Exit, audio, transcript, and events remain byte-identical.
  10. Simulated agent audio is playable but visually and structurally impossible to confuse with recorded caller-heard evidence.