#5803·pipecat

Is PipelineWorker.remove_observer() supported for an observer to detach itself from inside its own event handler?

Author: mannyb223Created Sep 16, 2026Updated Sep 16, 2026

We're trying to cut CPU on a phone-call pipeline that runs for several minutes per call. StartupTimingObserver does real work for about the first second of a call (its two reports: on_transport_timing_report, on_startup_timing_report) and then nothing — every frame for the rest of the call still goes through its on_push_frame, which returns after two isinstance checks. That's small on its own, but the fan-out that delivers every frame to every observer's queue is not: on our profile, four attached observers account for about 4% of the call's CPU each (the whole observer fan-out is roughly a fifth of the process's CPU time), and one of the four is this one doing nothing useful after startup.

We found PipelineWorker.remove_observer() (pipeline/worker.py) — it's public, has a docstring, and your own tests/test_pipeline.py::test_task_add_observer proves it detaches a live observer mid-run. But it isn't mentioned on the Pipeline Worker or Observer Pattern docs pages, and we found no example anywhere that calls it.

Our plan is to call worker.remove_observer(self) from inside StartupTimingObserver's own on_startup_timing_report handler, once its report has fired, so it stops receiving frames for the rest of the call. From reading the source, this looks safe on our transport (FastAPI WebSocket): the handler is registered sync=False so it runs on its own task, not inside the proxy task being cancelled, and the removal only touches a dict entry plus cancels that observer's own proxy task.

Two questions:

  1. Is calling remove_observer from inside one of that same observer's own event handlers a supported pattern, or is there a reason it isn't (e.g. does it depend on transport, or on which event fires it)?
  2. Would it make more sense for StartupTimingObserver to detach itself once it has reported (built in), or take an option to do so, rather than every user having to wire this up by hand?

Happy to send a PR either way once we know which shape you'd want.