Debugger Audio Causality Implementation Plan
Date: 2026-07-12
Status: Approved for implementation by Claude CL2C-002
Design: docs/design/2026-07-12-debugger-audio-causality-design.md
Coordination: docs/toclaude/2025.07.12 and
docs/tocodex/2025.07.12
Goal
Make one audible failure explainable from one bounded debugger command and one synchronized UI cursor. A human, Codex, or Claude must be able to distinguish:
- model audio interrupted and restarted;
- an FSM deliberately requesting equivalent content twice;
- one transport/player attempt being duplicated;
- a policy/directive model request producing forbidden caller-facing output;
- model-received audio versus outbound-accepted, browser-played, and carrier-recorded evidence;
- valid caller input followed by output from the wrong state path;
- a state guard firing from inherited non-slot context.
The first release is recorded observability only. It must not change prompts, FSM behavior, audio bytes, transport ordering, or production tool effects.
Rules for Implementation Agents
- Read the design, this plan, relevant fixlog entries, and both coordination mailboxes before editing.
- Work one packet at a time. Do not combine capture, hydration, UI, and caller behavior in one commit.
- Search the fix journal before each behavioral or telemetry fix and add an FX entry after the fix.
- Do not infer historical facts from current YAML or current Python.
- Do not add a second stored timeline. Normalize into the existing artifact.
- Never label
record_audio_outas sent or played evidence; both current transports call it before their send boundary. - Do not enable the purple Fork UI in these packets.
- No OTA call without explicit human
GO; use fake clients, fixtures, browser simulation, and retained replay overnight. - Never stage unrelated dirty files. At plan creation these include Claude's
mailbox,
packs/space_channel_widgets.pack.yaml, and static-audio assets. - Each packet ends with focused tests, a narrow commit, and a mailbox result.
- If a packet discovers that caller-facing ordering must change, stop and split that into a separately reviewed behavior packet.
Ownership
| Lane | Owner | Files |
|---|---|---|
| Request/provider capture | Claude | riff/live/live_client.py, riff/live/session.py, capture-focused tests |
| Browser/phone playback capture | Claude | riff/live/transport.py, riff/phone/telnyx_transport.py, web/live/*, web/voice-dispatcher.js, capture tests |
| Shared evidence schema | Claude proposes, Codex reviews | riff/playback_evidence.py, schema tests |
| FSM context capture | Claude/service lane | riff/state_manager.py, new or existing context-ledger helper, tests |
| Artifact normalization | Codex | riff/postcall/state_program_artifact.py, artifact tests |
| Debugger commands/detectors | Codex | riff/state_program_debugger.py, CLI/service/HTTP tests |
| Browser UI and operator docs | Codex | web/state-program-debugger.*, Playwright, Markdown/HTML |
| Caller-flow behavior fixes | Claude, separate plan | live/FSM/compiler files; never mixed with telemetry packets |
Do not edit another owner's files without a mailbox handoff naming the exact file and reason.
Verified Starting Point
These facts were rechecked on the plan's working tree. Function names are the contract; line numbers may move during implementation.
| Fact | Current evidence | Consequence |
|---|---|---|
| Caller-facing session sends | 10 direct self.client.send_client_content(...) calls in riff/live/session.py |
AC-01 wraps these 10; the source gate is scoped to this file |
| Inbound browser text send | riff/live/transport.py::_browser_to_session sends caller text directly to the provider |
This is input, not a logical caller-facing output, and must not receive an output ID |
| Scratch clears | 11 direct self.turn.reset() calls in riff/live/session.py |
AC-01 replaces all 11 with the reasoned reset wrapper |
| Browser model PCM | riff/live/transport.py::_session_to_browser calls record_audio_out before await ws.send |
The first call is model receipt; only the successful send boundary proves outbound acceptance |
| Phone model PCM | TelnyxPhoneTransport._emit_audio_out calls record_audio_out before _emit_pcm24_bytes; actual sends occur in _emit_pcm24_bytes_locked |
Pre-codec receipt, outbound wire submission, and carrier recording remain separate |
| Browser generated player | web/voice-dispatcher.js enqueues anonymous PCM; web/live/audio-player.js and pcm-up-processor.js report no per-attempt start/drain today |
AC-03A needs a bounded begin/end envelope and worklet acknowledgement |
| Guard reads | riff/state_manager.py::evaluate_guard reports slot reads only; _guard_stalled reads no_progress_streak and max_no_progress_loops |
AC-04 adds explicit non-slot context_reads before making a stale-context claim |
Packet Dependency Graph
AC-00 schema + synthetic fixture
-> AC-01 request identity + structural gates
-> AC-02 provider output-attempt capture
-> AC-03A browser playback truth
-> AC-03B phone outbound truth
-> AC-04 non-slot state-context capture
AC-03A + AC-03B + AC-04 -> AC-05 artifact normalization
-> AC-06 mechanism-aware findings
-> AC-07 explain_moment + compact AI CLI
-> AC-08 browser visualization
-> AC-09 regression promotion seam
-> AC-10 local/OTA validation and rollout
AC-03A and AC-03B may run in parallel after AC-02. AC-04 may run in parallel with AC-03 only when ownership does not overlap. AC-05 begins only after the schema and capture examples are frozen.
AC-00: Freeze Schema and Synthetic Evidence
Owner: Codex with Claude approval
Risk: Low
Behavior change: None
Files
riff/playback_evidence.pytests/test_playback_evidence.py- new fixture under
tests/fixtures/state_program_debugger/generated_output_restart/ - new fixture under
tests/fixtures/state_program_debugger/selected_input_primary/ tests/test_state_program_artifact.py
Work
- Add schema constants for logical output, output-attempt, playback, evidence stage, terminal outcome, and confidence while preserving existing static playback fields.
- Add bounded
model_request_id,request_kind, andoutput_expectation=required|allowed|forbidden|unknownfields. - Freeze the additive raw events exactly as
model_request_recorded,model_output_attempt_started, andmodel_output_attempt_terminal; the last carries a typed outcome rather than separate provider-lifecycle event names. - Extend
record_playback_eventvalidation to accept the new optional fields. - Do not change existing event output when optional fields are absent.
- Build a synthetic, PII-free ledger representing:
- matched caller input;
- consent state entry;
- model attempt 1 start and interruption;
- model attempt 2 linked restart with matching prefix;
- one browser playback bound to attempt 2;
- model-received PCM followed by a failed outbound send;
- completed second attempt;
- a separate pair of identical static outputs requested by two FSM visits;
- one transport duplicate represented by duplicate starts/copies for the same playback ID;
- distinct playback IDs for one output attempt with no retry relationship, producing a weaker unexplained-multiple finding;
- a policy request with forbidden output followed by an unexpected model response.
- Commit the expected normalized attempt rows and findings only after Claude
approves the reconciled schema in
CL2C-002.
Tests
- Existing static event JSON remains byte-identical.
- Invalid IDs, source kinds, parent-kind relationships, evidence stages, and terminal states fail.
- Fingerprints remain session-scoped and do not retain raw prompt text.
selected_sourceanddecision_reasonare bounded scalars; full transcript observations remain source references.- Maximum-length session/state inputs still generate safe IDs of at most 160 characters with no raw prompt/caller text.
- Model receipt cannot satisfy an outbound/client/carrier assertion.
- Fixture contains no raw caller PII or production secrets.
Done
One schema commit; no imports from riff/live/session.py; no UI changes.
AC-01: Request Identity and Structural Gates
Owner: Claude
Risk: Medium
Behavior change: None
Feature flag: RIFF_DEBUG_OUTPUT_ATTEMPTS=1 until validated
Files
riff/live/session.py- focused source/AST contract test
tests/test_live_session.py- new focused test such as
tests/test_live_output_attempts.py
Work
- Freeze a baseline inventory of all 10 caller-facing direct
send_client_contentcalls inriff/live/session.pyand all 11 directself.turn.reset()calls. Record each call's semantic reason in the packet result. - Add
_send_client_content_with_evidence(...), the only approved caller inGeminiLiveSession. It delegates exactly one unchanged client call and does not add an await, retry, copy, or reorder. - With the flag on, allocate a bounded
model_request_idfrom the semantic key(session_id, causal_visit_id, request_ordinal). The encoded ID uses stable ordinals/tokens and must pass the existing 160-character safe-ID validator; wall time and content equality are prohibited. - Require every call site to supply
request_kindandoutput_expectation=required|allowed|forbidden|unknown. Allocate a logical output at request time only for required/allowed speech. - Emit
model_request_recorded(dispatch_outcome=accepted)only after the awaited provider call returns. On its existing exception path, emitdispatch_outcome=failedbest-effort and re-raise the identical exception; never retry or translate it. - Bind
input_id,selected_source, anddecision_reasonwhen the request is caused by a caller decision. Reference the fulltranscript_sourcesevent; do not duplicate all transcript text. - Replace all 10 session send calls with the wrapper. Every call site supplies
a bounded
source_kindand typed request reason. Do not wrap the separate inbound caller-text send inriff/live/transport.pyas output. - Add
_reset_turn_scratch(reason). The output-attempt tracker lives onGeminiLiveSession, notTurnAccumulator; the wrapper records an attempt boundary or terminal-unavailable marker before callingself.turn.reset(). - Replace all 11 raw resets with the wrapper and a typed reason.
- Add source gates that reject a new direct send or raw reset outside its wrapper. The tests must prove each gate fails on a violating source fixture.
Tests
- Flag off preserves client method arguments, call count, await order, emitted audio bytes, and the pre-existing bus event sequence.
- Two requests from two visits with identical text receive different logical output IDs.
- A forbidden-output policy request gets a request ID but no intended logical output at send time.
- One request binds the acted-on Whisper-primary source and reason; a Gemini backup fixture binds its source and reason.
- Request IDs contain no raw prompt/caller text.
- Every existing send and reset is reachable only through its wrapper.
- Both source gates fail when their synthetic violating line is introduced.
Done
Claude appends the two baseline inventories, event examples, flag-off proof, and
test output to CL2C-*. Codex confirms the identity spine before AC-02.
AC-02: Provider Output-Attempt Capture
Owner: Claude
Risk: High telemetry risk, zero intended behavior risk
Feature flag: RIFF_DEBUG_OUTPUT_ATTEMPTS=1 until validated
Files
riff/live/live_client.pyriff/live/session.pytests/test_live_client.pytests/test_fake_live_client.pywhen fake event parity requires ittests/test_live_session.pytests/test_live_output_attempts.py
Work
- Extend
_translate_server_messageto emit provider interruption beforeturn_completein the same message and retain a provider response ID when the SDK exposes one. - If no stable provider ID exists, allocate a connection-local monotonic
response ordinal labelled
derived_local; never present it as provider ID. - Open the first output attempt on the first output transcription or PCM tied to a logical request. Two PCM chunks remain one attempt.
- If output arrives for a request with
output_expectation=forbidden, allocate an observed-unexpected logical output linked to that request. Do not relabel it as intended. - For a logical output with an existing started attempt, open another only
after an explicit interruption, changed response ID, typed RIFF retry/resume,
or
_reset_turn_scratch(reason)followed by fresh first text/PCM. - When the provider bit is absent and a recorded boundary precedes fresh output, close the prior attempt as derived interruption and link the new attempt at lower confidence. A time gap or repeated text alone is forbidden.
record_audio_outrecordsmodel_receivedbytes and extendsassistant_first_audiowith causal IDs. It must not emit outbound/client/carrier playback truth.- Terminalize the provider attempt on explicit interruption,
turn_complete, typed provider failure, or teardown. An unclassified reset recordsOUTPUT_ATTEMPT_TERMINAL_UNAVAILABLE; it never synthesizes completion. - Preserve the prior
assistant_first_audiofields for compatibility and keep the attempt tracker independent ofTurnAccumulatorscratch fields.
Tests
- Duck-typed SDK fixture with
interrupted=trueyields exactly one event beforeturn_complete; missing attributes remain backward compatible. - One generated response produces one provider attempt and one completion.
- Caller barge-in produces interrupted attempt 1 and linked attempt 2.
- Provider-bit-absent plus a recorded reset/response boundary yields a derived, lower-confidence restart; a gap-only fixture remains unlinked/unavailable.
- Two PCM chunks produce one output attempt.
- Two model responses sharing
SessionContext.turn_countget different attempt and local response IDs. - Reset, reconnect, state transition, cancellation, and teardown cannot erase a started attempt without a terminal or typed terminal-unavailable row.
- Feature flag off preserves the prior bus event sequence and audio bytes.
- Event-emitter failure does not interrupt audio.
- No
model_receivedrow satisfies an outbound/client/carrier predicate.
Done
No caller-facing text/output ordering delta in fake-client transcript and PCM goldens. Codex reviews the raw attempt artifact before playback capture begins.
AC-03A: Browser Playback Truth
Owner: Claude
Risk: Medium protocol/telemetry risk, zero intended audio-content risk
Feature flag: RIFF_DEBUG_OUTPUT_ATTEMPTS=1
Files
riff/live/transport.pyweb/live/live-client.jsweb/live/audio-player.jsweb/live/pcm-up-processor.jsweb/voice-dispatcher.js- focused browser transport, JavaScript, and Playwright tests
Work
- Bind each generated binary PCM stream to a
playback_idand itsoutput_attempt_id. Send one bounded control envelope before its first PCM frame and one terminal boundary; never add metadata to every frame. - Preserve every PCM byte and binary-frame order. Existing clients with the flag off receive the byte-identical stream and no new control envelope.
- Emit
audio_requestedwhen dispatch begins. Emit anoutbound_acceptedacknowledgement only after successfulawait ws.send(payload); a failed send cannot appear accepted. - Extend the browser player/worklet to report
client_playbackstart when the first sample for that playback ID is consumed, completion after the explicit end marker drains, interruption on clear/stop/supersession, and typed error. - Persist client acknowledgements through the existing
playback_eventendpoint with logical/output/playback IDs and evidence stage. - Do not claim browser speaker perception; the acknowledgement proves the Web Audio boundary only.
Tests
- Metadata envelope precedes only the first binary frame and preserves all PCM frame bytes/order.
- A failed
ws.sendrecords requested+failed, never outbound accepted. - Two PCM frames produce one client start and one completion after FIFO drain.
- Interruption before drain produces no completion for that stage.
- Static browser playback fixtures remain unchanged.
- Playwright proves client acknowledgements survive refresh in the artifact.
Done
One browser call can distinguish model receipt, server WebSocket acceptance, and actual Web Audio start/terminal on the same playback ID.
AC-03B: Phone Outbound Truth
Owner: Claude
Risk: Medium telemetry risk, zero intended audio-content risk
Feature flag: RIFF_DEBUG_OUTPUT_ATTEMPTS=1
Files
riff/phone/telnyx_transport.pyriff/playback_evidence.pyonly for the frozen optional fields- focused Telnyx transport/playback tests
Work
- Allocate one
playback_idfor each phone delivery of an output attempt and retain it across all PCM chunks for that attempt. - Emit
audio_requestedbefore the first encoded chunk. Emitoutbound_acceptedstart only after the first successfulws_sendinside_emit_pcm24_bytes_locked. - Accumulate exact submitted wire bytes and the existing
audio_agent_playedclock interval without emitting one lifecycle row per chunk. - Terminalize through a nonblocking transport observer when AC-02 reports the provider attempt terminal: completed after all submitted chunks, interrupted on barge/clear/supersession, or failed when no chunk succeeds.
- Preserve current swallowed-exception behavior and audio ordering. Telemetry
observes
ws_send; it does not change whether an exception escapes. - Label this stage RIFF outbound acceptance. Carrier audibility remains the separately authorized dual-channel recording contract.
Tests
- First successful
ws_sendproduces exactly one outbound start. - Failed first/all sends cannot produce outbound accepted or completed.
- Multiple Gemini PCM events retain one playback ID and one terminal.
- Barge-in produces interruption with exact submitted duration.
- Pre-codec
audio_agent.wav, wireaudio_agent_played.wav, and carrier evidence remain distinct. - Existing static Telnyx playback fixtures remain unchanged.
Done
A phone artifact distinguishes model receipt from submitted Telnyx wire audio; it still labels carrier confirmation unavailable unless independently retained.
AC-04: Capture Behavior-Affecting Context
Owner: Claude/service lane
Risk: Medium
Behavior change: None
Files
riff/state_manager.pyriff/session_slot_ledger.pyor a new narrowly named context-ledger moduleriff/types.pyonly if a typed snapshot helper is requiredtests/test_session_slot_ledger.pytests/test_state_manager.py
Work
- Emit
state_context_snapshotat the same entry/exit boundaries asstate_slot_snapshot. - Record at minimum
turn_count,no_progress_streak,max_no_progress_loops,decision_epoch, bounded recent states, pending transition identity, and active logical-output/output-attempt/playback IDs. - Inventory unavailable behavior-affecting fields rather than silently omitting them.
- Extend guard telemetry with
context_reads. Forstalled, record the exactno_progress_streakandmax_no_progress_loopsvalues used. - Keep context reads distinct from slot reads.
- Never mutate context while serializing it.
Tests
- Fresh consent entry records the exact progress counter.
stalledevent identifies both context inputs and its result.- Snapshot ordering is entry before the target state's first guard evaluation and exit before state exit/commit.
- Serialization is bounded and deterministic.
- Snapshot failure cannot block the state transition.
Done
The motivating stalled question can be answered from a synthetic ledger
without reading current Python.
AC-05: Normalize Output and Playback Attempts
Owner: Codex
Risk: Medium
Behavior change: None
Files
riff/postcall/state_program_artifact.pytests/test_state_program_artifact.py- fixture expected output
Work
- Add pure
_build_output_attemptsover recorded request/provider evidence and_build_playback_attemptsover canonical playback lifecycle events. - Join request -> output attempt -> playback ID by explicit IDs; never by nearest timestamp alone.
- Validate provider terminals separately from playback-stage terminals.
- Preserve causal and observed visits separately.
- Preserve
selected_sourceanddecision_reasonon compact input links while retaining source references to full transcript observations. - Derive compatibility
playback_spansfrom normalized playback attempts. - Add typed findings for missing/contradictory lifecycle and unavailable stage.
- Fold events only in
(seq, source_suborder)order. No mutable flow file, clock, random value, process hash order, or set iteration may affect output.
Tests
- Event order permutations with stable sequence produce one canonical result.
- Duplicate starts on one playback ID at the same stage are invalid; starts at different evidence stages are not duplicates.
- Two attempts for one logical output remain distinct and linked.
- Two playback IDs for one output attempt remain distinct and stage-labelled.
- Late events retain causal ownership.
- Model receipt followed by failed transport send never appears outbound/client accepted.
- Legacy static fixtures produce unchanged compatibility spans.
- Same artifact rehydrates byte-identically.
Done
Artifact schema version increments; migration/legacy behavior is documented.
AC-06: Mechanism-Aware Findings and Pause Audit
Owner: Codex
Risk: Medium
Files
riff/postcall/state_program_artifact.pyriff/state_program_debugger.pytests/test_state_program_artifact.pytests/test_state_program_debugger.py
Work
Implement only evidence-backed findings:
MODEL_OUTPUT_RESTART_REPEATED_PREFIX;FSM_REPEATED_OUTPUT_REQUEST;TRANSPORT_DUPLICATE_ATTEMPT;MULTIPLE_PLAYBACK_ATTEMPTS_UNEXPLAINED;UNEXPECTED_OUTPUT_FROM_NON_SPEAKING_REQUEST;STATE_EXITED_BEFORE_FIRST_OUTPUT;MATCHED_INPUT_FOLLOWED_BY_UNRELATED_OUTPUT;STALE_PROGRESS_GUARD_ON_ENTRY;DYNAMIC_POST_PROMPT_SILENCE;OUTPUT_AFTER_STATE_EXIT.
Every finding includes required source event IDs, causal visit, confidence
class, and typed unavailable dependencies. Keep legacy
DUPLICATE_AGENT_PLAYBACK for pre-schema calls.
Tests
- Two FSM visits requesting the same asset are not called a transport duplicate.
- Interrupted/restarted generated output with matching prefix gets the model restart code.
- A generated prompt contributes a pause boundary at the selected playback evidence stage, not merely at model receipt.
- Missing text/terminal/context evidence downgrades or suppresses a finding.
- No detector uses current YAML or calls a model.
- Multiple truth stages for one playback do not create a duplicate finding.
- Multiple distinct playback IDs without retry evidence get the weaker unexplained-multiple finding, not a definitive transport-duplicate claim.
- A response to a forbidden-output request gets the unexpected-output finding, not an intended-prompt label.
Done
The synthetic motivating fixture produces the expected distinct findings and zero false transport-duplicate claim.
AC-07: Compact explain_moment and AI CLI
Owner: Codex
Risk: Medium
Files
riff/state_program_debugger.pyriff/state_program_debugger_service.pyonly if filtered loading is neededscripts/dbg.pyscripts/state_program_debugger.py- debugger engine/CLI/service/HTTP tests
Work
- Add
explain_momentwith exactly one event/time anchor. - Resolve state, causal input, selected STT and decision reason, context reads, model-request kind/expectation, output attempts, playback attempts/stages, first deterministic divergence, gaps, and next commands.
- Default to a canonical response of at most 4 KB.
- Add
detail=evidencewith a 32 KB cap and pagination. - Return raw events only via filtered, explicit references.
- Add
latestandexplain-momentergonomic CLI commands without duplicating engine logic. - Build deterministic indexes by event, visit, input, logical output, output attempt, and playback ID so each join is bounded by IDs plus the requested time window rather than a full-timeline scan.
- Include zero-model-call execution metadata.
Tests
- Size caps are measured on canonical encoded bytes.
- Unknown/ambiguous anchor and stale hash fail without cursor motion.
- Browser, CLI, direct engine, and HTTP return byte-identical projections.
- The command uses zero model calls/tokens.
latestplus summary explanation returns at most 10 KB total.- The motivating fixture explanation names the first attempt and linked restart plus its playback truth stage without embedding the whole timeline.
Done
Claude can diagnose the synthetic failure using only latest,
explain-moment, and one optional detail command.
AC-08: Browser Causal UI
Owner: Codex
Risk: Medium
Files
web/state-program-debugger.htmlweb/state-program-debugger.js- static UI and Playwright tests
- cache-bust value
Work
- Render one Agent-lane band per playback attempt, grouped by logical output and output attempt, with textual stage and outcome.
- Connect linked output restarts and label transport retries without adding another cursor.
- Add Explain this moment to the existing inspector.
- Render a downward causal story: caller -> selected STT/reason -> decision -> state -> model request/expectation -> output attempt -> playback stage -> interruption/restart -> finding.
- Keep Entry/Exit slots and context values visible in the right inspector.
- Store attempt IDs and finding code with durable issue markers.
- Add a bounded Replay this evidence interval and Confirm this point
action. An AI may propose a marker/deep link; only the operator confirmation
changes its status from
proposedtoconfirmed. - Preserve mobile ordering and no horizontal overflow.
Tests
- Playwright seeks and plays through an interruption/restart pair.
- State, transcript, attempt, and explanation follow one playhead.
- Model-received, outbound-accepted, client-playback, carrier-recorded, and unavailable are visually and textually unconfusable.
- Attempt color is redundant with labels/icons.
- A marked issue URL restores the same event and explanation.
- Proposed versus confirmed problem points are visually and structurally distinct and replay the same bounded interval.
- Desktop and mobile screenshots show no overlap or nested-card layout.
Done
An operator can locate and share the repeated prefix without inspecting JSON.
AC-09: Reproduction and Regression Promotion Seam
Owner: Codex design; implementation only after separate approval
Risk: High
Work
- Add logical outputs, output/playback attempts, truth stages, selected input, and context snapshots to exported capsules.
- Define a PII-safe synthetic input fixture for the marked defect.
- Define, but do not yet enable, an isolated runner request containing exact engine/flow revisions and typed overlays.
- Define promotion output: a deterministic regression fixture and expected finding/exit/audio-attempt assertions.
Done
The packet produces a reviewed runner contract. The browser Fork button remains disabled.
AC-10: Validation and Rollout
Owner: Shared
Risk: Medium; OTA step requires human GO
Ladder
- Unit schemas and invariants.
- Synthetic artifact and debugger fixture.
- Fake Live client interruption/restart.
- Browser dynamic-playback lifecycle with aligned replay.
- Local fake Telnyx
ws_sendlifecycle and wire recording. - Local retained-call rehydration.
- Feature flag on/off byte and pre-existing event comparison.
- One Telnyx OTA call after explicit human
GO.
OTA Acceptance
- Generated consent interruption yields one interrupted and one linked completed attempt.
- Repeated prefix finding points to the correct output attempts and named playback/carrier interval.
- Model receipt, Telnyx outbound acceptance, and carrier-recorded audio remain distinct and agree where all three are available.
- A valid start-claim input does not immediately fall through consent because of stale progress context; this caller-behavior fix is a separate Claude packet and must have its own tests/FX entry.
explain_momentreturns under 4 KB and the UI deep link restores it.- Full call audio and transcripts remain aligned.
Commit Plan
Use these commit boundaries unless the reconciled mailbox approval changes them:
test(debugger): freeze output-attempt schema fixture
refactor(live): centralize output request and scratch reset boundaries
feat(live): retain provider output attempts
feat(browser): retain generated playback acknowledgements
feat(phone): retain generated outbound playback lifecycle
feat(fsm): retain behavior-affecting state context
feat(debugger): normalize output and playback attempts
feat(debugger): classify output causality
feat(debugger): add bounded explain-moment projection
feat(debugger-ui): visualize output attempts and causal chain
docs(debugger): publish audio-causality operator guide
Every fix commit references its FX entry. Pure design/test fixture commits may reference the design document instead.
Packet Result Template
Every implementation result appended to the mailbox contains:
packet/task_id:
status: done | blocked | needs-human-go
commit:
files changed:
commands/tests:
event examples and artifact path:
flag-off equivalence evidence:
known unavailable evidence:
rollback:
next owner/action:
Rollback
RIFF_DEBUG_OUTPUT_ATTEMPTS=0removes new capture/control envelopes and preserves the pre-change audio and event path.- Artifact readers accept missing optional attempt fields and keep legacy
playback_spans; rollback does not rewrite retained calls. - Detector/UI rollout remains separately switchable until capture has passed deterministic fixture, browser, and fake-phone gates.
- No packet deploys live behavior or performs an OTA call without human
GO.
Implementation Stop Conditions
Stop and post blocked to the other mailbox when:
- neither provider telemetry nor a typed RIFF response/reset boundary can distinguish a restart;
- terminalization would require changing audio queue ordering;
- a stable causal ID cannot survive scratch resets;
- context capture mutates or serializes unbounded history;
- a detector needs an LLM or current mutable flow to decide;
- an API response cannot meet its size cap without dropping required provenance;
- concurrent work touches the same owned file;
- validation requires an OTA call without human GO.
Plan Approval Gate
Implementation begins only after:
- Claude's
CL2C-001required changes are incorporated. - Codex appends
C2C-002with a resolution table. - Claude appends
CL2C-002approving the reconciled schema and packet ordering or identifies one bounded remaining blocker. - AC-00 schema/fixture is committed before live capture code changes.