Voice Responsiveness — Revert Playbook
This brief — Voice Chat Responsiveness & Status Feedback — ships as independently revertable modules. There is no feature flag. The prod-safety strategy is: revert any single PR and voice chat keeps working. Backend modules emit additive SSE events that are no-ops without consumers; UI modules only render when those events arrive.
If something regresses post-ship, find the module below, run the revert command, and confirm the listed fallback behaviour.
Quick reference
| Module | Repo | What it adds | Revert impact |
|---|---|---|---|
| M1 | piing-agent | voice_phase SSE event protocol + agent emitter | UI phase indicators stop updating |
| M3 | piing-mobile | Provisional transcript on mobile | Mobile shows final transcript only |
| M4 | piing-mobile | Mobile phase indicator pill | No phase pill on mobile |
| M6 | piing-app | Web phase indicator pill | No phase pill on web |
| M7 | piing-agent | Latency-watchdog spoken filler | No spoken filler when phases stall |
| M8 | piing-agent, docs.piing.ai | Telemetry + this playbook | Histogram + counter stop reporting |
M2 (server-side EL partial forwarder) and M5 (web provisional transcript) were Split/Dropped on 2026-05-17 after the path audit showed EL talks directly to the mobile client. They are not in this playbook because they were never shipped.
Revert procedure (any module)
-
Find the merge commit for the module's PR on the repo's default branch. Either click "Reverted" on GitHub's PR page, or:
git log --oneline --grep "M<n>" -i origin/main | head -
Revert via PR (preferred — keeps history clean and lets CI gate the revert):
gh pr create --title "revert: M<n> <module name>" \
--body "Reverts <PR url>. Reason: <one line>." \
--base mainOr, if you have direct write to the default branch and need to move fast:
git checkout main && git pull
git revert <merge-commit-sha> -m 1 # -m 1 for revert of a merge
git push origin main -
Verify the fallback listed for that module below. If voice chat stops working entirely after a single-module revert, that's the bug to escalate — the prod-safety strategy says it should keep working.
Per-module revert
M1 — Phase event protocol + agent emitter
-
Repo: piing-agent
-
PR: piing-agent#116
-
Branch:
claude/voice-responsiveness-m1-phase-event-protocol -
Revert command:
cd piing-agent
git revert -m 1 9a74e2c # M1 merge commit -
Fallback after revert:
voice_phaseSSE events stop emitting. Mobile (M4) and web (M6) phase pills receive no events and stay hidden — they're explicitly inert without M1. Voice chat itself is unaffected: the agent still answers, EL still transcribes, the final transcript still renders. The watchdog filler (M7) also becomes a no-op because its trigger is thevoice_phasestream. -
Note: Reverting M1 implicitly disables M4, M6, M7, and the M8 histogram (the
understoodemission site goes away). The metrics registry stays loaded; the histogram just stops receiving samples.
M3 — Mobile renders provisional transcript
-
Repo: piing-mobile
-
PR: links pending — M3 is in flight in a sibling build at land time.
-
Branch:
claude/voice-responsiveness-m3-mobile-provisional-transcript -
Revert command:
cd piing-mobile
git revert -m 1 <M3 merge sha> -
Fallback after revert:
useElevenLabsVoiceSession.tsreverts to returningpartialTranscript: ''. The voice screen renders only the final EL transcript at end of utterance, as it did before this brief. No transport or session changes — EL WebSocket subscription is unchanged.
M4 — Mobile phase indicator UI
-
Repo: piing-mobile
-
PR: to be filled when merged
-
Branch:
claude/voice-responsiveness-m4-mobile-phase-indicator -
Revert command:
cd piing-mobile
git revert -m 1 <M4 merge sha> -
Fallback after revert: Phase pill component is removed from
VoiceChatScreen.tsx. No phase indicator renders on mobile during a voice turn. M3's provisional transcript (if shipped) is unaffected; M1's SSE events continue emitting but have no consumer on mobile.
M6 — Web phase indicator UI
-
Repo: piing-app
-
PR: to be filled when merged
-
Branch:
claude/voice-responsiveness-m6-web-phase-indicator -
Revert command:
cd piing-app
git revert -m 1 <M6 merge sha> -
Fallback after revert: Phase pill component is removed from
ConversationView.tsx. No phase indicator renders on web voice turns. The StreamToken context extension is unwound; the rest of the web voice flow is unaffected. Before reverting: runbun run formatin piing-app per the repo's pre-push convention.
M7 — Latency-watchdog spoken filler
-
Repo: piing-agent
-
PR: to be filled when merged
-
Branch:
claude/voice-responsiveness-m7-latency-watchdog-filler -
Revert command:
cd piing-agent
git revert -m 1 <M7 merge sha> -
Fallback after revert: The watchdog timer is no longer registered per SSE stream. If a phase stalls (e.g. a slow
researchinground), the agent stays silent until the next phase transition or final reply, as it did before this brief. Main reply stream is unaffected because M7 was additive and never on the critical path. The M8voice_filler_fired_totalcounter stops incrementing but the metric series itself (defined invoiceMetrics.ts) remains registered.
M8 itself
If M8 has to be reverted (telemetry regression, prom-client conflict, etc.):
cd piing-agent
git revert -m 1 <M8 piing-agent merge sha>
The voiceMetrics.ts service and the wrapped tracker at the M1 emit
site disappear; the underlying voicePhaseTrackerRegistry and phase
event flow are untouched. voice_phase SSE events continue emitting
exactly as they did before M8 — no UX change. M7 (if shipped) loses its
counter import target and will fail to compile against
voiceMetrics.ts; if M7 has already merged, revert M7 first or in the
same window.
The playbook itself lives in docs.piing.ai:
cd docs.piing.ai
git revert -m 1 <M8 docs.piing.ai merge sha>
Operational follow-ups
The M8 telemetry exports two metrics:
voice_phase_understood_latency_ms(Histogram) — utterance-end to firstvoice_phase=understood. p50 + p95 fall out of the buckets.voice_filler_fired_total(Counter, label:phase) — incremented by M7's watchdog.
They register on their own voiceMetricsRegistry and are observable in
tests today via getVoiceMetrics(). Wiring them into a Prometheus
/metrics HTTP endpoint and into Grafana dashboards is an explicit
follow-up — see the Brief's "Scope (out)": Grafana dashboard — separate
task (oncall watches grafana.internal/d/api-latency).