From 7f34b06415f2eac77fbae452d473bd73b9f8a3d4 Mon Sep 17 00:00:00 2001 From: tocmo0nlord Date: Sun, 28 Jun 2026 00:14:53 +0000 Subject: [PATCH] Stop spurious watchdog re-prompts after the call ends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SilenceWatchdog armed a timer on the goodbye turn; it then fired ~silence_secs later, after EndCallProcessor had already hung up — logging phantom "re-prompt"s (3 of 5 in the last batch were after "Goodbye"). Now it stops for good on a closing keyword (LLMFullResponseEnd) or an EndFrame/CancelFrame, so it never re-arms once the call is closing. Real mid-call silences still re-prompt. Runtime-tested: no reprompt after goodbye / endframe; reprompt on normal silence. Co-Authored-By: Claude Opus 4.8 --- bot.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/bot.py b/bot.py index 9d03524..31266f3 100644 --- a/bot.py +++ b/bot.py @@ -25,6 +25,7 @@ from pipecat.audio.vad.vad_analyzer import VADParams from pipecat.frames.frames import ( BotStartedSpeakingFrame, BotStoppedSpeakingFrame, + CancelFrame, EndFrame, EndTaskFrame, Frame, @@ -373,6 +374,8 @@ class SilenceWatchdog(FrameProcessor): self._prompts = 0 self._bot_speaking = False self._ending = False + self._stopped = False # call is closing/ended — never arm again + self._buf = "" def _cancel(self): if self._timer and not self._timer.done(): @@ -409,7 +412,19 @@ class SilenceWatchdog(FrameProcessor): async def process_frame(self, frame: Frame, direction: FrameDirection): await super().process_frame(frame, direction) - if isinstance(frame, UserStartedSpeakingFrame): + # Stop for good once the call is closing — otherwise a timer armed on the goodbye + # turn fires ~silence_secs later, after the call already ended (spurious re-prompt). + if isinstance(frame, (EndFrame, CancelFrame)): + self._stopped = True + self._cancel() + elif isinstance(frame, LLMTextFrame): + self._buf += frame.text + elif isinstance(frame, LLMFullResponseEndFrame): + if EndCallProcessor._is_closing(self._buf): + self._stopped = True + self._cancel() + self._buf = "" + elif isinstance(frame, UserStartedSpeakingFrame): self._prompts = 0 # caller engaged again — reset self._cancel() elif isinstance(frame, BotStartedSpeakingFrame): @@ -417,7 +432,9 @@ class SilenceWatchdog(FrameProcessor): self._cancel() elif isinstance(frame, BotStoppedSpeakingFrame): self._bot_speaking = False - if self._ending: + if self._stopped: + pass # call ending — don't re-arm + elif self._ending: asyncio.create_task(self._end_soon()) else: self._arm() # start counting silence once the agent finishes