<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Naimish</title>
    <description>The latest articles on DEV Community by Naimish (@naimish21).</description>
    <link>https://dev.to/naimish21</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4078553%2Ffc255596-a495-47e5-87c7-92f73594554b.png</url>
      <title>DEV Community: Naimish</title>
      <link>https://dev.to/naimish21</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/naimish21"/>
    <language>en</language>
    <item>
      <title>GitOS: Graph-Powered Ecosystem Defense &amp; Intelligence Layer for GitHub</title>
      <dc:creator>Naimish</dc:creator>
      <pubDate>Mon, 19 Oct 2026 03:16:56 +0000</pubDate>
      <link>https://dev.to/naimish21/gitos-graph-powered-ecosystem-defense-intelligence-layer-for-github-48j</link>
      <guid>https://dev.to/naimish21/gitos-graph-powered-ecosystem-defense-intelligence-layer-for-github-48j</guid>
      <description>&lt;h2&gt;
  
  
  Why We Built GitOS: The Graph-Powered Defense Layer Open Source Desperately Needs
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;By Naimish Singh &amp;amp; Team BitBuddies (Graph Hacks: Context for AI Agents 2026)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Uncomfortable Reality of Modern GitHub
&lt;/h2&gt;

&lt;p&gt;If you maintain an open-source library with more than a few hundred stars, you already know what happened over the last twelve months:&lt;/p&gt;

&lt;p&gt;Your notifications exploded. But they didn’t explode with deep bug reports or thoughtful refactors. They exploded with &lt;strong&gt;AI-generated pull requests&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Someone pastes an issue into a frontier LLM, prompts it to "fix this," and hits &lt;em&gt;Submit PR&lt;/em&gt; without reading a single line of the diff. On the surface, the PR looks articulate. The markdown description is immaculate, formatted with bullet points and emojis. But when you inspect the code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variable names were renamed cosmetically across 18 files.&lt;/li&gt;
&lt;li&gt;A modified helper function in &lt;code&gt;routing.py&lt;/code&gt; broke 14 callers across unrelated modules because the author never ran the test suite.&lt;/li&gt;
&lt;li&gt;A newly hallucinated utility function was injected into the codebase that is never called by anything (&lt;code&gt;in_degree == 0&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Worse: existing automated "AI review bots" chime in by generating 50-comment threads, burning tens of thousands of LLM tokens on speculative guesses while spamming everyone on the thread.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We watched maintainers of projects like FastAPI, Flask, and React spend their weekends acting as human spam filters. &lt;/p&gt;

&lt;p&gt;We asked ourselves a simple engineering question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why are we using stochastic, token-hungry LLMs to guess at structural code relationships when a graph database can determine them with 100% mathematical certainty in under 10 milliseconds?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question gave birth to &lt;strong&gt;GitOS&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Paradigm Shift: Deterministic Graph-First, LLM-Last
&lt;/h2&gt;

&lt;p&gt;Before building GitOS, we laid down three non-negotiable architectural axioms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero LLM Tokens for Structural Math&lt;/strong&gt;: An LLM should never be asked &lt;em&gt;"what functions call &lt;code&gt;add_api_route&lt;/code&gt;?"&lt;/em&gt; That is a graph traversal problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Disk Clone&lt;/strong&gt;: Open-source maintainers don’t want a bot cloning 5GB repositories to a third-party server. Everything must parse directly from GitHub’s recursive Git Trees API in-memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Unconsented Bot Spam&lt;/strong&gt;: No automated comments, labels, or PR closures unless a human maintainer clicks a button with an explicit confirmation dialog under their own authenticated OAuth session.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To make this possible, we needed a graph engine with sub-millisecond traversal latencies on in-memory topologies. That engine was &lt;strong&gt;FalkorDB&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  How GitOS Works Under the Hood
&lt;/h2&gt;

&lt;p&gt;When a maintainer or contributor pastes a GitHub PR URL into GitOS, the system doesn’t prompt an LLM. Instead, it triggers a 4-layer deterministic pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[GitHub PR URL] 
      │
      ▼ (In-Memory Tree Fetch - 0 Disk Clones)
[Universal AST Parser (Python &amp;amp; Tree-Sitter)]
      │
      ▼ (Subgraphs Persisted by commit_sha)
[(FalkorDB Graph Engine)]
      │
      ├──&amp;gt; Filter 1: Untested Call-Graph Impact ([:TESTED_BY] Traversal)
      ├──&amp;gt; Filter 2: AST Delta vs. Raw Text Diff Ratio (Cosmetic Churn)
      ├──&amp;gt; Filter 3: Dead Code Injection (In-Degree == 0)
      └──&amp;gt; Filter 4: Maintainer Architectural Boundaries (Forbidden Edges)
      │
      ▼ (Sub-10ms Subgraph Results)
[3D Interactive Force Graph &amp;amp; Multi-Agent Swarm]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. In-Memory AST Projection
&lt;/h3&gt;

&lt;p&gt;We extract classes, functions, calls, and imports into formal graph primitives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nodes: &lt;code&gt;(:Function)&lt;/code&gt;, &lt;code&gt;(:Class)&lt;/code&gt;, &lt;code&gt;(:Module)&lt;/code&gt;, &lt;code&gt;(:Test)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Relationships: &lt;code&gt;[:CALLS]&lt;/code&gt;, &lt;code&gt;[:IMPORTS]&lt;/code&gt;, &lt;code&gt;[:TESTED_BY]&lt;/code&gt;, &lt;code&gt;[:DEFINES]&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. The FalkorDB Cypher Queries Doing the Heavy Lifting
&lt;/h3&gt;

&lt;p&gt;Instead of asking an AI to "spot bugs", GitOS executes deterministic Cypher queries directly against FalkorDB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A. Multi-Hop Blast Radius:&lt;/strong&gt;&lt;br&gt;
When a PR alters a signature, how far does the blast wave travel?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;target:&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;target.id&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;$modified_targets&lt;/span&gt;
&lt;span class="k"&gt;MATCH&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;caller:&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:CALLS&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;1.&lt;/span&gt;&lt;span class="n"&gt;.$hops&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;caller.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hop_distance&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;hop_distance&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="ss"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In FalkorDB, this recursive traversal returns in &lt;strong&gt;3 to 7 milliseconds&lt;/strong&gt;. It instantly reveals that changing &lt;code&gt;APIRouter.add_api_route&lt;/code&gt; in Python cascades into an unverified TypeScript gateway controller two hops away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B. Catching Hallucinated Dead Code:&lt;/strong&gt;&lt;br&gt;
LLMs frequently hallucinate dead helper functions. We query incoming call in-degrees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;fn:&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;commit_sha:&lt;/span&gt; &lt;span class="n"&gt;$commit_sha&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="ow"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;fn.name&lt;/span&gt; &lt;span class="k"&gt;STARTS&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="s1"&gt;'test_'&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="ow"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;fn.name&lt;/span&gt; &lt;span class="k"&gt;STARTS&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="s1"&gt;'__'&lt;/span&gt;
&lt;span class="k"&gt;OPTIONAL&lt;/span&gt; &lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;caller:&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:CALLS&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caller&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;in_degree&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;in_degree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;fn.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn.file_path&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn.line_start&lt;/span&gt;&lt;span class="ss"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;in_degree == 0&lt;/code&gt;, GitOS immediately flags the injected symbol as potential AI hallucination. Zero tokens spent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C. Cosmetic Churn Ratio:&lt;/strong&gt;&lt;br&gt;
We compare AST node mutations against raw line diffs. If a PR touches 1,480 lines of text across 18 files but only mutates 3 AST nodes (a ratio of 0.002), GitOS flags it as cosmetic AI slop before maintainers waste cognitive energy reviewing it.&lt;/p&gt;


&lt;h2&gt;
  
  
  The 5-Agent Swarm: True Agentic Context
&lt;/h2&gt;

&lt;p&gt;Only &lt;em&gt;after&lt;/em&gt; FalkorDB computes the deterministic metrics does our 5-agent swarm take the stage:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;TriageAgent&lt;/strong&gt;: Synthesizes the blast radius score and maintainer executive brief.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GraphAgent&lt;/strong&gt;: Traverses isolated repository graphs (&lt;code&gt;gitos_{owner}_{repo}&lt;/code&gt;) to identify cross-module dependency regressions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SecurityAgent&lt;/strong&gt;: Scans for AST security sinks (&lt;code&gt;eval&lt;/code&gt;, command injection) and package slopsquatting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OnboardingAgent &amp;amp; Pathfinder&lt;/strong&gt;: Translates open GitHub issues into precise AST symbol edit scopes for contributors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ReleaseAgent&lt;/strong&gt;: Analyzes AST changes to automatically determine semantic SemVer impacts (&lt;code&gt;PATCH&lt;/code&gt;, &lt;code&gt;MINOR&lt;/code&gt;, or &lt;code&gt;MAJOR&lt;/code&gt;) and draft structured release notes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because FalkorDB compresses the entire problem into a tiny, high-density subgraph, when we &lt;em&gt;do&lt;/em&gt; call an LLM (Groq Llama 3.3 70B), the prompt is under &lt;strong&gt;800 tokens&lt;/strong&gt;. No prompt bloat, no token bankruptcy, and no hallucinations.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Building on FalkorDB Taught Us
&lt;/h2&gt;

&lt;p&gt;When we started the hackathon, we experimented with relational joins and document stores. They fell apart the moment we hit multi-hop traversals across enterprise codebases like Flask and FastAPI.&lt;/p&gt;

&lt;p&gt;With FalkorDB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed is real&lt;/strong&gt;: Running recursive graph traversals on Redis-backed memory primitives gave our agents sub-second responsiveness on live UI keystrokes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Graph Isolation is a superpower&lt;/strong&gt;: FalkorDB lets you create isolated graphs (&lt;code&gt;gitos_fastapi_fastapi&lt;/code&gt;, &lt;code&gt;gitos_pallets_flask&lt;/code&gt;) on the fly inside a single instance without deploying separate database clusters. This made multi-tenant per-repo memory effortless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traceability replaces guesswork&lt;/strong&gt;: In an era where AI agents are accused of being black boxes, a Cypher query path &lt;code&gt;(A)-[:CALLS]-&amp;gt;(B)-[:CALLS]-&amp;gt;(C)&lt;/code&gt; is deterministic proof you can show to any senior engineer.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  How to Run GitOS in 60 Seconds
&lt;/h2&gt;

&lt;p&gt;We made sure anyone—especially hackathon judges—can run GitOS locally with a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ace-ify/GitOS.git
&lt;span class="nb"&gt;cd &lt;/span&gt;GitOS
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# Add your GitHub token in .env&lt;/span&gt;
docker-compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Open &lt;code&gt;http://localhost:3000&lt;/code&gt; to interact with the 3D topology graph.&lt;/li&gt;
&lt;li&gt;API Swagger docs live at &lt;code&gt;http://localhost:8000/docs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Test suite: 40/40 unit &amp;amp; integration tests passing out of the box.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;AI shouldn’t make open source harder to maintain. It should make ecosystems cleaner, safer, and more accessible.&lt;/p&gt;

&lt;p&gt;By pairing modern AI agents with the structural rigor of FalkorDB graph databases, we don't just guess what code does—we trace it, defend it, and understand it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Check out the open-source code on GitHub: &lt;a href="https://github.com/ace-ify/GitOS" rel="noopener noreferrer"&gt;https://github.com/ace-ify/GitOS&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>github</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>I built a Multilingual healthcare voice agent that knows when call or shutup.</title>
      <dc:creator>Naimish</dc:creator>
      <pubDate>Sat, 15 Aug 2026 06:58:08 +0000</pubDate>
      <link>https://dev.to/naimish21/i-built-a-multilingual-healthcare-voice-agent-that-knows-when-call-or-shutup-2ija</link>
      <guid>https://dev.to/naimish21/i-built-a-multilingual-healthcare-voice-agent-that-knows-when-call-or-shutup-2ija</guid>
      <description>&lt;p&gt;Ten days ago I set out to build a voice agent for the &lt;strong&gt;Murf 10 Days of Voice Agents — VoiceForBharat&lt;/strong&gt; challenge. I picked the &lt;strong&gt;Health Access&lt;/strong&gt; track and built &lt;strong&gt;Careva&lt;/strong&gt;: a phone-and-browser helpline that helps someone in India find the nearest working health facility, understand a government scheme, ask what a medicine actually costs, and — when it matters — stop talking and get a human involved.&lt;/p&gt;

&lt;p&gt;This post is the story plus the guide. Everything here is in the repo: &lt;strong&gt;&lt;a href="https://github.com/ace-ify/murf-livekit" rel="noopener noreferrer"&gt;https://github.com/ace-ify/murf-livekit&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The problem and the users
&lt;/h2&gt;

&lt;p&gt;The person I built for does not have a problem that a website solves.&lt;/p&gt;

&lt;p&gt;They have a fever at 9pm and don't know if the PHC is open. They have a ₹40 prescription and no idea the generic salt costs ₹6 at a Jan Aushadhi store. They're eligible for Ayushman Bharat and have never read the eligibility page, because the eligibility page is in English, is 2000 words long, and assumes a smartphone with data.&lt;/p&gt;

&lt;p&gt;They can talk, though. Voice is not a nicer interface here — it's the only interface that clears the bar. No app install, no typing in a script your keyboard doesn't have, no literacy assumption. A phone call works on a ₹1500 feature phone in a village with 2G.&lt;/p&gt;

&lt;p&gt;So the design constraint was: &lt;strong&gt;it has to work when spoken, in Hindi, on a bad line, to someone who is scared.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What the agent does
&lt;/h2&gt;

&lt;p&gt;Careva answers in the language you spoke to her in. She can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;find the nearest PHC / CHC / district hospital with OPD timings, from a district name or a pincode&lt;/li&gt;
&lt;li&gt;explain PM-JAY, JSSK and immunisation schedules from a local knowledge base&lt;/li&gt;
&lt;li&gt;look up the generic salt for a branded medicine and what you'd save&lt;/li&gt;
&lt;li&gt;give an air-quality advisory for a district (useful for asthma and elderly callers)&lt;/li&gt;
&lt;li&gt;remember you between calls, if you say it's okay&lt;/li&gt;
&lt;li&gt;call &lt;em&gt;you&lt;/em&gt; — medication and vaccination reminders&lt;/li&gt;
&lt;li&gt;escalate to a human health worker with a spoken reference number&lt;/li&gt;
&lt;li&gt;hand you to &lt;strong&gt;Samar&lt;/strong&gt;, a specialist agent, for detailed clinic/appointment questions&lt;/li&gt;
&lt;li&gt;and above all: recognise a medical emergency and say "call 108 now" as the &lt;em&gt;first&lt;/em&gt; sentence, before any greeting or name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one drove most of the engineering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F77m2xzgpfb0xk8yz7dhn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F77m2xzgpfb0xk8yz7dhn.png" alt="Careva session UI mid-call, with a pushed facility card showing the nearest PHC, its OPD timings and&lt;br&gt;
  address" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  3. How the system works
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🎙️ caller ──audio──▶ Deepgram nova-3 (multi) ──text──▶ LLM ──text──▶ Murf Falcon ──audio──▶ 🔊 caller
                                    ▲                                                │
                                    └────────────── LiveKit (WebRTC / SIP) ──────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Four moving parts, and you can swap any of them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What I used&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;STT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deepgram &lt;code&gt;nova-3&lt;/code&gt;, &lt;code&gt;language="multi"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;one model that handles Hindi and English without me picking upfront&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LLM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gemini 2.5 Flash → Groq Llama 3.3 70B → Llama 3.1 70B on NVIDIA NIM&lt;/td&gt;
&lt;td&gt;via LiveKit's &lt;code&gt;FallbackAdapter&lt;/code&gt; — a helpline that 502s is worse than a slow one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Murf Falcon&lt;/strong&gt;, voice &lt;code&gt;Anisha&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Indian voice, and fast enough that the pause after you stop speaking doesn't feel like a dropped call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transport&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LiveKit Agents&lt;/td&gt;
&lt;td&gt;one pipeline serves both the browser and a real phone number over SIP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Extras&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Silero VAD, LiveKit multilingual turn detector, BVC noise cancellation&lt;/td&gt;
&lt;td&gt;see below&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The session, more or less verbatim from &lt;code&gt;backend/src/agent.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;stt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;deepgram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;STT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nova-3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;multi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;FallbackAdapter&lt;/span&gt;&lt;span class="p"&gt;([...],&lt;/span&gt; &lt;span class="n"&gt;attempt_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;15.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;tts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;murf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TTS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;voice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MURF_VOICE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;# "Anisha"
&lt;/span&gt;        &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Conversational&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;CleanSentenceTokenizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_sentence_len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;text_pacing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;turn_detection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MultilingualModel&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;vad&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vad&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;preemptive_generation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_away_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SILENCE_TIMEOUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# 12.0
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two small lines with big effects. &lt;code&gt;preemptive_generation=True&lt;/code&gt; starts the LLM on a partial transcript, which removes a visible chunk of the reply gap. And noise cancellation is picked per participant — &lt;code&gt;BVCTelephony()&lt;/code&gt; for SIP callers, &lt;code&gt;BVC()&lt;/code&gt; for browser — because phone audio is band-limited and the generic model does worse on it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;noise_cancellation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;noise_cancellation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BVCTelephony&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rtc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParticipantKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PARTICIPANT_KIND_SIP&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;noise_cancellation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BVC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. The features that mattered
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tools that hit real APIs, with a floor under them.&lt;/strong&gt; Facility lookup is OpenStreetMap Nominatim + the India Post pincode API. Medicines are NLM RxNorm. Air quality is Open-Meteo. Schemes are a hand-rolled BM25 index over local markdown — no vector DB, ~150 lines, and it retrieves fine over a corpus this size. Every external call has a 2.5–3.5s budget and degrades to a curated fallback registry of verified facilities. The one thing the agent is never allowed to say is "I can't help" — the last fallback is still 108 and the nearest hospital.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consent-gated memory.&lt;/strong&gt; A &lt;code&gt;callers&lt;/code&gt; table in SQLite keyed on the participant identity (the phone number, for SIP). It stores name, district, age band, ongoing conditions, last triage outcome — and only after the caller says yes. &lt;code&gt;forget_caller&lt;/code&gt; hard-deletes. On reconnect the greeting is rebuilt from that record: &lt;em&gt;"नमस्ते प्रिया जी… पिछली बार हमने बुखार के बारे में बात की थी। आज आप कैसा महसूस कर रहे हैं?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Escalation that a human can actually work.&lt;/strong&gt; The agent asks permission first — &lt;em&gt;"May I send a short summary to a human health worker?"&lt;/em&gt;, in the caller's own language — and a "no" writes literally nothing. What gets stored is six fields, never the transcript, and it goes through one PII scrub choke point so storage and the webhook can't diverge. The caller hears a reference number, &lt;code&gt;ESC-0007&lt;/code&gt;, read digit by digit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Specialist handoff.&lt;/strong&gt; &lt;code&gt;transfer_to_clinic_specialist&lt;/code&gt; swaps the agent object on the live session and swaps the Murf voice with it, so the caller hears a different person (&lt;code&gt;Anisha&lt;/code&gt; → &lt;code&gt;Samar&lt;/code&gt;, female → male) rather than the same voice claiming to be someone else. State carries across: caller facts, call id, turn counters, and the emergency flags.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A dashboard that defines its own metric.&lt;/strong&gt; &lt;code&gt;/admin&lt;/code&gt; shows total / successful / failed / no-answer calls, a failure breakdown, and a success rate that excludes in-progress calls from the denominator. The header states the definition out loud: &lt;em&gt;success = caller received triage guidance or was escalated to a human.&lt;/em&gt; A success rate whose definition lives only in someone's head is a vanity number.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fydl8dlpyoy40tpvazbu9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fydl8dlpyoy40tpvazbu9.png" alt="Careva call analytics dashboard showing total calls, success rate, failure breakdown by reason, and a recent&lt;br&gt;
  calls table" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  5. The hard parts (all four of these were real bugs)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The agent said goodbye to a heart attack
&lt;/h3&gt;

&lt;p&gt;Worst thing I found in testing. Caller: &lt;em&gt;"my friend is having chest pain"&lt;/em&gt;. Careva gave the 108 line correctly, and then a few turns later cheerfully wrapped up — &lt;em&gt;"aapka din shubh ho"&lt;/em&gt; — and hung up.&lt;/p&gt;

&lt;p&gt;The prompt already said don't do that. Prompts are suggestions.&lt;/p&gt;

&lt;p&gt;The fix was to move safety out of the model entirely. A regex scans every final transcript — in English, Roman Hinglish, &lt;strong&gt;and&lt;/strong&gt; Devanagari — and latches a flag. &lt;code&gt;end_call&lt;/code&gt; then refuses to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Hard guard: the LLM once said goodbye to a caller whose friend was having a
# heart attack. A prompt line is not enough — refuse the hangup outright.
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emergency_flag&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;escalation_created_flag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end_call BLOCKED: unhandled emergency (reason=%r)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REFUSED — you cannot end this call. ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same guard blocks the specialist handoff. &lt;strong&gt;Lesson: if a behaviour is safety-critical, it belongs in code that the model calls, not in text the model reads.&lt;/strong&gt; The prompt gets you the good path; the guard gets you the bad one.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bad accent wasn't the voice, it was the locale
&lt;/h3&gt;

&lt;p&gt;English replies sounded wrong — an Indian-English sentence being pushed through a Hindi voice, mangling the vowels. I spent a while rewriting the prompt, which was the wrong layer entirely.&lt;/p&gt;

&lt;p&gt;Deepgram's &lt;code&gt;multi&lt;/code&gt; model tags every final transcript with the language actually spoken. So: read that tag, and update the TTS locale mid-call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# "hi" or "en", short-circuited if unchanged
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Locale fixes the accent. The prompt fixes word choice. Two different bugs that sound like one.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;"consent_given": "true"&lt;/code&gt; killed entire turns
&lt;/h3&gt;

&lt;p&gt;Consent params in my tools are typed &lt;code&gt;str&lt;/code&gt;, not &lt;code&gt;bool&lt;/code&gt;, and that looks like a mistake until you've seen this: Groq's Llama 3.3 intermittently emits the &lt;em&gt;string&lt;/em&gt; &lt;code&gt;"true"&lt;/code&gt; for a boolean parameter, Groq rejects it server-side with &lt;code&gt;tool_use_failed&lt;/code&gt;, and the whole turn dies — mid-sentence, on a health call.&lt;/p&gt;

&lt;p&gt;A string parameter cannot be malformed. So it's a string, parsed fail-closed, and the truthy set speaks Hindi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;haan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;haan ji&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything unrecognised means &lt;strong&gt;no consent&lt;/strong&gt;. Failing closed on a consent check is free; failing open is a privacy incident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every call looked like a silent disconnect
&lt;/h3&gt;

&lt;p&gt;My analytics said 100% no-answer. The calls were fine. I was subscribed to &lt;code&gt;user_speech_committed&lt;/code&gt; and &lt;code&gt;agent_speech_committed&lt;/code&gt;, which don't exist in &lt;code&gt;livekit-agents&lt;/code&gt; 1.4 — and a handler for an event that never fires is completely silent. &lt;code&gt;conversation_item_added&lt;/code&gt; is the real one.&lt;/p&gt;

&lt;p&gt;Two more of the same species, both fixed: finalising the call row on the session &lt;code&gt;close&lt;/code&gt; event lost a race with the loop shutting down and left rows stuck at &lt;code&gt;in_progress&lt;/code&gt; (use &lt;code&gt;ctx.add_shutdown_callback&lt;/code&gt;, which LiveKit awaits); and dispatch metadata was being parsed &lt;em&gt;after&lt;/em&gt; &lt;code&gt;record_call_start()&lt;/code&gt;, so that call raised a swallowed &lt;code&gt;NameError&lt;/code&gt; and recorded zero calls. &lt;strong&gt;Wire up your telemetry and then verify it with your own eyes, because broken instrumentation reports success.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Build your own — the short version
&lt;/h2&gt;

&lt;p&gt;Start from &lt;a href="https://github.com/murf-ai/murf-livekit-starter" rel="noopener noreferrer"&gt;Murf's LiveKit starter&lt;/a&gt;. You need Python 3.10+, &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt;, Node 18+, pnpm, and a free LiveKit Cloud project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ace-ify/murf-livekit.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;murf-livekit
&lt;span class="nb"&gt;cd &lt;/span&gt;backend &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv run python src/agent.py download-files
&lt;span class="nb"&gt;cd&lt;/span&gt; ../frontend &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pnpm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Keys.&lt;/strong&gt; Copy &lt;code&gt;.env.example&lt;/code&gt; → &lt;code&gt;.env.local&lt;/code&gt; in both &lt;code&gt;backend/&lt;/code&gt; and &lt;code&gt;frontend/&lt;/code&gt;. &lt;code&gt;.env*&lt;/code&gt; is gitignored — keep it that way, and never paste a key into a chat window, an issue, or a screenshot. You need &lt;code&gt;LIVEKIT_URL&lt;/code&gt; / &lt;code&gt;LIVEKIT_API_KEY&lt;/code&gt; / &lt;code&gt;LIVEKIT_API_SECRET&lt;/code&gt;, &lt;code&gt;MURF_API_KEY&lt;/code&gt; (&lt;a href="https://murf.ai/api/dashboard" rel="noopener noreferrer"&gt;murf.ai/api/dashboard&lt;/a&gt;), &lt;code&gt;DEEPGRAM_API_KEY&lt;/code&gt;, and &lt;code&gt;GOOGLE_API_KEY&lt;/code&gt;. In production these are platform env vars (Railway for the agent, Vercel for the UI) — same LiveKit project on both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run and talk to it.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run python src/agent.py console          &lt;span class="c"&gt;# talk in your terminal, no frontend needed&lt;/span&gt;
uv run python src/agent.py dev              &lt;span class="c"&gt;# + pnpm dev, then open localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;console&lt;/code&gt; mode is the fastest loop you'll get — it's where I did most of the debugging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then write one tool.&lt;/strong&gt; Not five. A tool is just a decorated async function; the docstring &lt;em&gt;is&lt;/em&gt; the spec the model reads, so spend your time there, not on the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@function_tool&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_nearest_health_facility&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RunContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;location_or_pincode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;facility_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;any&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Find the nearest Primary Health Centre (PHC), Community Health Centre (CHC), or Hospital.

    Use this tool when a caller asks:
    - &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mera paas ka PHC/hospital kahan hai?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; or &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Where is the nearest health centre?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
    - &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPD kitne baje tak khula hai?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; or &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What are the hospital timings?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;    ...&lt;/span&gt;
    &lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;location_or_pincode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;District&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;digit&lt;/span&gt; &lt;span class="n"&gt;Indian&lt;/span&gt; &lt;span class="n"&gt;PIN&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;automatically&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="n"&gt;caller&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the example questions in both languages — that's not documentation, that's routing. And note the last line: leaving the location empty and resolving it from memory is what makes a second call feel like a continuation instead of a form.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What I'd do next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cut turn latency properly.&lt;/strong&gt; I log &lt;code&gt;reply = llm_ttft + tts_ttfb&lt;/code&gt; per turn but I haven't done a real p50/p95 pass across the fallback chain. (One thing I did measure: NIM's &lt;code&gt;llama-3.3-70b-instruct&lt;/code&gt; is listed but never responds — read timeout past 45s — while &lt;code&gt;3.1-70b&lt;/code&gt; gives ~0.8s TTFT with working tool calls. Measure your fallbacks; a dead fallback is worse than none, because you'll trust it.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuqv70lon7rm16ihpjc4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuqv70lon7rm16ihpjc4o.png" alt="Careva session UI mid-call, with a pushed facility card showing the nearest PHC, its OPD timings and&lt;br&gt;
  address" width="799" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Put real auth on &lt;code&gt;/admin&lt;/code&gt;.&lt;/strong&gt; It lists health complaints. Status &lt;em&gt;writes&lt;/em&gt; need &lt;code&gt;ADMIN_TOKEN&lt;/code&gt; and fail closed without it, but the GET is open for local dev. That page does not go public as-is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalation triage instead of keyword matching.&lt;/strong&gt; The current gate is a word list that errs open. It works; it isn't triage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Widen the fallback facility registry&lt;/strong&gt; — the live-API-fails path is the path a rural caller is most likely to hit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/ace-ify/murf-livekit" rel="noopener noreferrer"&gt;https://github.com/ace-ify/murf-livekit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://murf.ai/api/docs/text-to-speech-models/falcon-2" rel="noopener noreferrer"&gt;Murf Falcon docs&lt;/a&gt; · &lt;a href="https://murf.ai/api/docs/voices-styles/voice-library" rel="noopener noreferrer"&gt;Murf voice library&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.livekit.io/agents" rel="noopener noreferrer"&gt;LiveKit Agents&lt;/a&gt; · &lt;a href="https://developers.deepgram.com" rel="noopener noreferrer"&gt;Deepgram Nova-3&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you build one thing from this post, build the guard, not the prompt. The model will be charming and wrong at some point, and on a health line "charming and wrong" has a cost. Put the floor in code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;#VoiceForBharat&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>livekit</category>
      <category>python</category>
      <category>agentaichallenge</category>
    </item>
  </channel>
</rss>
