Voice AI and backend engineer. I work on the frameworks voice agents run on, and on the failures that only show up on a real phone call.
Twenty-four fixes merged upstream, across seven repositories β eleven into livekit/agents, four into cosmo, three into pipecat, two each into jambonz and SitePing, and one each into drachtio and twenty.
π Chennai, India Β· 2026 CS graduate Β· open to a first engineering role π Portfolio Β· LinkedIn
In the framework itself
- livekit/agents#7139 β
ConnectionPool.invalidate()closed sockets that were still streaming, and a handshake in flight escaped invalidation entirely and stayed reusable with stale settings. Eleven plugins call it fromupdate_options, so one change to the primitive repaired all of them. Reviewed line by line by a maintainer. - #7054 β found a whole bug class, fixed all fifteen instances across library, examples and tests, and got ruff's
RUF006enabled in their CI so dangling tasks cannot come back.
Provider plugins β the same stale-handshake bug, found by grepping siblings for the shape
- #7132 asyncai Β· #7133 neuphonic Β· #7140 cartesia, where the API version was also sent in a header that disagreed with the body it was built for
Lifecycle and cleanup
- #7023 β an STT connection pool that stayed open after shutdown
- #7012 β per-stream HTTP sessions leaking through a
WeakSet - #7050 β a discarded prewarm task rebuilding a pool after close
- #7341 β LiveAvatar built its own
aiohttpsession instead of the job's shared one, so the session outlived the job that owned it - #7237 β respeecher retired whole connection pools on a model change, leaving idle sockets for the old model open and orphaning a stream's socket in a pool nothing closes
- pipecat#5632 β an STT service rebuilt its config on a settings change, but the running gRPC stream took its config once at open, so a mid-session language change kept transcribing with the old one
- pipecat#5464 β a TTS service wrote model, voice and language only into its websocket init message, so a runtime change was stored, warned about, and silently never sent
- pipecat#5762 β an image service left its GenAI client open on shutdown. It had sat unmergeable for five weeks after upstream rewrote the request path underneath it; I rebased onto the rewrite, kept their version and re-applied the lifecycle hooks, and it merged within a day
SIP and telephony
- jambonz#1585 β a TTS
stream_resumedevent sent to a different hook path than the one it resumed - jambonz#1584 β a call transferred between feature servers re-sent trying, ringing and in-progress statuses the application had already received
- drachtio-srf#240 β proxied responses built their header strip list from the request option, so the wrong headers were stripped and forwarded
Open PRs across livekit/agents, pipecat, litellm and drizzle-orm, plus fixes sent to nine voice-AI vendors' own SDKs β Gnani, Simplismart, Rumik, Respeecher, SonexLabs, Replicate, Supertonic, Maya Research and Cosmo.
pipecat-plugin-health β a nightly check that installs every community Pipecat plugin in the docs, imports it and constructs every service class it defines, against the latest release and against main, so a breaking change shows up before users hit it. It now tracks 67 plugins: as of 21 October, against pipecat 1.13, nine are broken, three cannot be installed at all and seven are pinned to an older pipecat. Seven broke on a single renamed import, and each of those has a fix open or merged.
It also taught me that a released wheel answers the user's question and not the maintainer's: it called one maintainer's plugin broken for two weeks after they had fixed it, because PyPI still served the old version. It now builds from each plugin's own repository too, guarded so that a wrongly resolved repository reports unknown rather than crediting a fix to the wrong codebase β write-up.
livekit-stt-halfopen β how long each of the nineteen streaming STT plugins in livekit/agents takes to notice a websocket that went half-open: the provider vanished without a FIN or an RST, so nothing arrives and nothing fails. Three fail over in about 46 seconds. Two detect it and tell no one β one reconnects into the same dead state every 46 seconds forever, which looks healthy and leaves the caller unheard for the whole call. Eight never notice.
It overturned three of my own published claims. I had written off baseten, fireworksai and gradium as already safe because each wraps its read in asyncio.wait_for(..., timeout=5) β but the handler just continues, so it polls rather than bounding anything, and all three sat at 75 seconds. That is what the measurement was for.
livekit-tts-halfopen β the same question for the twenty streaming TTS plugins, where a dead socket means the agent goes mute rather than deaf, which a caller reads as a dropped line. Nine fail over inside the timeout they were given, three eventually, and four never notice at all.
The answer turned out to be the opposite one. I expected heartbeat= to decide it again and started by scanning for it β 18 of the 20 pass none. That predicts almost nothing: what separates the groups is whether a read timeout reaches ws.receive(). fishaudio has a heartbeat and no read timeout, so it takes 46.8 s where cartesia takes 10.0 s with no heartbeat at all. The workload is why: a quiet caller is normal for STT, so a read timeout is the wrong instrument there, while a TTS stream handed text that returns nothing is never normal. The four that never notice are fixed in #8035.
Take a bug fixed in one place, then grep every sibling for the same shape β it is almost never alone. Then verify by mutation: revert the fix and confirm the new test fails. A test that passes both ways has told me nothing.
Most of it started with debugging real calls on my own voice agent: audio arriving out of order, sessions torn down mid-sentence, a connection quietly serving the wrong voice after a settings change.
Atlas β an outbound voice agent on Twilio Media Streams and Deepgram. Barge-in, a call-session state machine, outcome classification, dialling constrained to legal calling hours, and SMS/WhatsApp on one session model. ~31,000 lines, solo.
InvoiceCheck.in β GST invoice verification, live. An 11-point compliance check run before marketplaces reject an invoice, OCR auto-fill, Razorpay pay-per-check.
Python asyncio pytest Β· LiveKit Agents Pipecat jambonz drachtio Β· Twilio Programmable Voice & Media Streams SIP WebRTC Deepgram Β· TypeScript NestJS Next.js Β· Postgres Prisma Redis BullMQ Β· Docker CI/CD



