Context
The Temporal production path (apps/worker/activities.py → resolve_via_voice → get_voice_provider()) selects PipecatProvider (apps/voice/pipecat_provider.py) per settings.yaml (voice.provider: pipecat) — but that provider is a non-functional scaffold:
_LiveTranscriptCapture.process_frame is a ... no-op (lines 148-152) — transcripts stay empty, breaking the cross-modal HandoffContext claim.
_classify_outcome relies on the LLM literally saying "deal agreed" in text (the streaming module long since moved to end_call tool calls).
recording_url and cost_usd are hardcoded placeholders (cost_usd=0.0 at line 102).
- It references a
TwilioTransport import that doesn't match the shipped Pipecat 1.x API.
Meanwhile the real, working pipeline lives in apps/voice/streaming.py behind the FastAPI WS endpoint.
Implementation plan
- Reuse, don't duplicate: refactor
pipecat_provider.py to drive the existing HTTP flow — call POST /voice/streaming/dial-init, place the call, then await the session result — rather than constructing a second Pipecat pipeline. Extract shared session-result plumbing (outcome, transcript, cost) into apps/voice/session_result.py.
- Transcript capture: in the streaming pipeline, subscribe to
TranscriptProcessor (or aggregate TranscriptionFrame / assistant TextFrames via the existing context aggregator) and persist turn-by-turn transcript on the session record.
- Outcome from tools: session outcome comes from the
end_call(outcome=...) tool args (handle_end_call, streaming.py:537-548) — expose it on the session result; delete _classify_outcome text matching.
- Recording URL: capture from Twilio's recording status callback (call placed with
record=True) and attach to the session result.
- Cost: take
total_cost_usd from the P0 VoiceCallMetricsObserver summary instead of 0.0.
- Borrower phone lookup: replace
BORROWER_PHONE__{id} env vars (pipecat_provider.py:110-124) with a proper lookup (settings/DB), keeping the env override for dev.
- Tests: activity-level test with the mock provider still green; integration smoke running the Temporal workflow against the simulator-backed streaming endpoint with populated transcript/outcome/cost.
Acceptance criteria
CollectionsWorkflow completes end-to-end with voice.provider: pipecat against the simulator: non-empty transcript, outcome sourced from the end_call tool, non-zero cost_usd, recording URL populated on real calls.
- No duplicate pipeline-construction code between
pipecat_provider.py and streaming.py.
Dependencies
Context
The Temporal production path (
apps/worker/activities.py→resolve_via_voice→get_voice_provider()) selectsPipecatProvider(apps/voice/pipecat_provider.py) persettings.yaml(voice.provider: pipecat) — but that provider is a non-functional scaffold:_LiveTranscriptCapture.process_frameis a...no-op (lines 148-152) — transcripts stay empty, breaking the cross-modalHandoffContextclaim._classify_outcomerelies on the LLM literally saying "deal agreed" in text (the streaming module long since moved toend_calltool calls).recording_urlandcost_usdare hardcoded placeholders (cost_usd=0.0at line 102).TwilioTransportimport that doesn't match the shipped Pipecat 1.x API.Meanwhile the real, working pipeline lives in
apps/voice/streaming.pybehind the FastAPI WS endpoint.Implementation plan
pipecat_provider.pyto drive the existing HTTP flow — callPOST /voice/streaming/dial-init, place the call, then await the session result — rather than constructing a second Pipecat pipeline. Extract shared session-result plumbing (outcome, transcript, cost) intoapps/voice/session_result.py.TranscriptProcessor(or aggregateTranscriptionFrame/ assistantTextFrames via the existing context aggregator) and persist turn-by-turn transcript on the session record.end_call(outcome=...)tool args (handle_end_call,streaming.py:537-548) — expose it on the session result; delete_classify_outcometext matching.record=True) and attach to the session result.total_cost_usdfrom the P0VoiceCallMetricsObserversummary instead of0.0.BORROWER_PHONE__{id}env vars (pipecat_provider.py:110-124) with a proper lookup (settings/DB), keeping the env override for dev.Acceptance criteria
CollectionsWorkflowcompletes end-to-end withvoice.provider: pipecatagainst the simulator: non-empty transcript, outcome sourced from theend_calltool, non-zerocost_usd, recording URL populated on real calls.pipecat_provider.pyandstreaming.py.Dependencies