@Kamal_Moha Your two scenarios are the same thing seen from both ends, and right now you can’t have both.
with_filler opens its loop with await self._session.wait_for_idle() (voice/filler_scheduler.py), and wait_for_idle counts the agent as busy while a speech handle is still current. A blocking tool keeps that handle alive. The SDK admits as much in an unrelated guard message: “the speech handle is waiting for the function tool to complete”.
So Scenario 2, the tool blocks, session never goes idle, filler loop never gets past line one. Scenario 1, your ctx.update() flags the call non-blocking, the handle lets go, session goes idle, fillers fire, but the return value was already handed over so the real result comes back deferred.
Fillers need idle. Blocking tools prevent idle. That’s it.
I’d file this one. Your two scenarios are a tidy repro, and with_filler’s own docstring says it’s for “filler speech while a long-running step blocks the tool”, which is exactly when it can’t fire.
For now just drive them yourself and keep the tool blocking:
async def _fillers(session, lines, first=2.0, gap=10.0):
await asyncio.sleep(first)
for line in lines:
session.say(line)
await asyncio.sleep(gap)
filler_task = asyncio.create_task(_fillers(ctx.session, followups))
try:
# wait on redis exactly as you do now
...
finally:
filler_task.cancel()
Same thing _FillerScheduler does internally, minus the idle gate. You do lose the guard that keeps a filler from landing while the user is mid-sentence, so keep gap generous.