Skip to content

fix(dashboard,pipeline): hydrate cost/latency + barge-in gate from first audio#86

Closed
nicolotognoni wants to merge 1 commit into
mainfrom
fix/dashboard-hydrate-schema-and-bargein-grace
Closed

fix(dashboard,pipeline): hydrate cost/latency + barge-in gate from first audio#86
nicolotognoni wants to merge 1 commit into
mainfrom
fix/dashboard-hydrate-schema-and-bargein-grace

Conversation

@nicolotognoni
Copy link
Copy Markdown
Collaborator

Summary

Two regression-class bugs surfaced during 0.6.0 acceptance against releases/0.6.0/typescript/matrix/outbound-cartesia-cerebras-elevenlabs.ts:

  • Dashboard hydrate wiped cost / latency / caller / callee for every call rebuilt from disk → all hydrated rows showed \$0.00 and .
  • Pipeline early barge-in self-cancelled the agent's first turn before any TTS chunk reached the wire → 0 bytes emitted, line silent.

Fixes are minimal, parity-aligned (Py + TS), and fully backward-compatible.

Implementation

1. MetricsStore.hydrate schema mismatch

CallLogger.log_call_end writes cost/latency/duration_ms/telephony_provider as top-level keys of metadata.json. The reader looked under meta.metrics.cost / meta.metrics.latency. New helper metricsFromTopLevel / _metrics_from_top_level synthesizes a metrics dict from those top-level fields when meta.metrics is missing, mapping latency.p95_msmetrics.latency_avg.total_ms so the existing UI fields populate. Explicit meta.metrics (legacy/future shape) is preserved untouched.

2. Pipeline barge-in gate anchored on actual audio emission

Cloud TTS first-byte latency is 200-700 ms; the 250 ms PSTN gate measured from _begin_speaking / beginSpeaking expired BEFORE TTS produced audio. New _first_audio_sent_at / firstAudioSentAt is set in _mark_first_audio_sent / markFirstAudioSent invoked AFTER bridge.sendAudio / audio_sender.send_audio succeeds at the four pipeline emit sites (firstMessage, streaming response, regular response, WebSocket remote). _can_barge_in / canBargeIn now returns false while the marker is null. Gate values (250 ms / 1000 ms) unchanged — only the anchor moves.

Files:

  • libraries/python/getpatter/dashboard/store.py
  • libraries/python/getpatter/stream_handler.py
  • libraries/typescript/src/dashboard/store.ts
  • libraries/typescript/src/stream-handler.ts

Breaking change?

No — defaults preserved, all existing fields/methods unchanged. The barge-in fix is a behavioural improvement (was already documented as a "anti-flicker only" gate; the anchor change makes that contract actually hold).

Test plan

  • Python: pytest tests/ → 1717 passed, 7 skipped
  • TypeScript: npm test → 1394 passed, npm run lint clean, build clean
  • New regressions:
    • test_hydrate_lifts_top_level_cost_and_latency_into_metrics (Py)
    • test_hydrate_preserves_explicit_metrics_when_present (Py)
    • test_barge_in_suppressed_before_first_audio_emitted (Py)
    • MetricsStore.hydrate > lifts top-level cost/latency/duration into metrics (TS)
    • MetricsStore.hydrate > preserves explicit metrics when present (TS)
    • barge-in gate > canBargeIn() false before the first TTS chunk has hit the wire (TS)
  • Existing _handle_barge_in / handleBargeIn tests updated to set both timestamps for the new contract.

Docs updates

N/A — bug fix in internal behaviour. CHANGELOG ## Unreleased updated with both entries.

…in gate from first audio

Two bugs caught during 0.6.0 acceptance against
`releases/0.6.0/typescript/matrix/outbound-cartesia-cerebras-elevenlabs.ts`:

1. **Dashboard hydrate schema mismatch**: `CallLogger.log_call_end` writes
   `cost`/`latency`/`duration_ms`/`telephony_provider` as top-level keys of
   `metadata.json`, but `MetricsStore.hydrate` looked for them under
   `meta.metrics.cost`/`meta.metrics.latency`. Every hydrated row landed
   with `metrics=null`, so cost/latency rendered as `$0.00`/`—` for all
   on-disk calls (only the in-flight call had real numbers). Fix synthesizes
   a `metrics` dict from the top-level fields when `meta.metrics` is absent
   while preserving any explicit `meta.metrics` payload untouched.

2. **Early barge-in self-cancellation**: cloud TTS first-byte latency is
   200–700 ms; the 250 ms anti-flicker gate (no-AEC PSTN default) was
   anchored on `_speaking_started_at`/`speakingStartedAt` and expired
   BEFORE TTS produced audio. VAD then picked up background noise and
   self-cancelled the agent's first turn — 0 bytes emitted, line silent.
   Fix anchors the gate on a new `_first_audio_sent_at`/`firstAudioSentAt`
   set AFTER `bridge.sendAudio` / `audio_sender.send_audio` succeeds at
   the four pipeline emit sites (firstMessage, streaming, regular,
   WebSocket remote). `_can_barge_in`/`canBargeIn` returns false while
   the marker is null. Gate values (250 ms / 1000 ms) unchanged — only
   the anchor moves.

Tests:
- Py 1717/1717, TS 1394/1394 green; lint clean.
- New regressions: `test_hydrate_lifts_top_level_cost_and_latency_into_metrics`,
  `test_hydrate_preserves_explicit_metrics_when_present`,
  `test_barge_in_suppressed_before_first_audio_emitted` (Py) +
  parity TS cases in `tests/dashboard-store.test.ts` and
  `tests/unit/stream-handler.test.ts`.
- Existing `_handle_barge_in`/`handleBargeIn` tests updated to set both
  timestamps for the new contract.
@nicolotognoni
Copy link
Copy Markdown
Collaborator Author

Superseded by #85 (0.6.1 release bundle). All commits from this branch (fix/dashboard-hydrate-schema-and-bargein-grace) have been merged into feat/observability-otel-attrs-0.6.1, plus the new opt-in barge-in confirmation strategies (MinWordsStrategy, BargeInStrategy Protocol/interface). Closing without merge — please review #85 instead.

@nicolotognoni nicolotognoni deleted the fix/dashboard-hydrate-schema-and-bargein-grace branch May 17, 2026 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant