<?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: Mehwish</title>
    <description>The latest articles on DEV Community by Mehwish (@mehwish_e1e7e09af6e0754ed).</description>
    <link>https://dev.to/mehwish_e1e7e09af6e0754ed</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%2F3974605%2F33795a5a-0fa1-4ed9-88d2-e2cc5ebe6531.png</url>
      <title>DEV Community: Mehwish</title>
      <link>https://dev.to/mehwish_e1e7e09af6e0754ed</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mehwish_e1e7e09af6e0754ed"/>
    <language>en</language>
    <item>
      <title>The Bug That Only Showed Up in a Second Tab: Debugging a Multi-Tab localStorage Race Condition</title>
      <dc:creator>Mehwish</dc:creator>
      <pubDate>Fri, 21 Aug 2026 14:16:13 +0000</pubDate>
      <link>https://dev.to/mehwish_e1e7e09af6e0754ed/the-bug-that-only-showed-up-in-a-second-tab-debugging-a-multi-tab-localstorage-race-condition-4kjk</link>
      <guid>https://dev.to/mehwish_e1e7e09af6e0754ed/the-bug-that-only-showed-up-in-a-second-tab-debugging-a-multi-tab-localstorage-race-condition-4kjk</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I was working on InnerHue, a mood-tracking app that stores mood entries in localStorage via a Zustand store. The reported bug: open the app in two tabs, log a mood in each, and entries would randomly go missing or get overwritten.&lt;/p&gt;

&lt;h2&gt;
  
  
  The investigation
&lt;/h2&gt;

&lt;p&gt;The Zustand store (&lt;code&gt;lib/useMoodStore.ts&lt;/code&gt;) kept an in-memory copy of &lt;code&gt;moodHistory&lt;/code&gt;, but it never listened for changes coming from &lt;em&gt;other&lt;/em&gt; tabs. So Tab A and Tab B each held their own stale snapshot, and whichever tab wrote to localStorage last simply clobbered whatever the other tab had written. Classic race condition, just spread across browser tabs instead of threads.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;I added a &lt;code&gt;window.addEventListener('storage', ...)&lt;/code&gt; listener that calls &lt;code&gt;useMoodStore.persist.rehydrate()&lt;/code&gt; whenever the &lt;code&gt;mood-storage&lt;/code&gt; key changes in another tab, keeping every open tab in sync instead of working off a frozen snapshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bonus bug
&lt;/h2&gt;

&lt;p&gt;While reproducing the race condition, I noticed something else: visiting a mood page kept creating duplicate entries. Turned out &lt;code&gt;addMood()&lt;/code&gt; was called directly inside a &lt;code&gt;useEffect&lt;/code&gt; with no guard, so React Strict Mode's dev-mode double-invocation of effects fired it twice. I added a &lt;code&gt;useRef&lt;/code&gt; guard so it only fires once per genuine page visit.&lt;/p&gt;

&lt;p&gt;Both bugs lived in the same localStorage write path, so I fixed them together in one PR.&lt;/p&gt;

&lt;p&gt;PR: &lt;a href="https://github.com/Nitya-003/InnerHue/pull/291" rel="noopener noreferrer"&gt;https://github.com/Nitya-003/InnerHue/pull/291&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;Bugs that only reproduce with multiple tabs open are easy to miss in normal dev testing, so you have to deliberately go looking for them. It's a good reminder to test state-management code against concurrent access, not just sequential single-tab flows.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <item>
      <title>Killing a Silent Performance Bug: Caching Email Templates Instead of Re-Parsing Them on Every Send</title>
      <dc:creator>Mehwish</dc:creator>
      <pubDate>Fri, 21 Aug 2026 14:04:07 +0000</pubDate>
      <link>https://dev.to/mehwish_e1e7e09af6e0754ed/killing-a-silent-performance-bug-caching-email-templates-instead-of-re-parsing-them-on-every-send-506d</link>
      <guid>https://dev.to/mehwish_e1e7e09af6e0754ed/killing-a-silent-performance-bug-caching-email-templates-instead-of-re-parsing-them-on-every-send-506d</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;auth-server is a Go-based authentication service (using the Gin framework) that, among other things, handles sending transactional emails, like email verification and password reset links, to users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;Every time the server sent an email, it read the corresponding HTML template from disk and parsed it from scratch, even though the template content never changes at runtime. This meant unnecessary disk I/O and template-parsing overhead on every single email send, which is wasteful under any real load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;PR: &lt;a href="https://github.com/roshankumar0036singh/auth-server/pull/121" rel="noopener noreferrer"&gt;https://github.com/roshankumar0036singh/auth-server/pull/121&lt;/a&gt;&lt;br&gt;
Closes Issue: #86&lt;/p&gt;

&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;I added a &lt;code&gt;templates&lt;/code&gt; cache field to the &lt;code&gt;EmailService&lt;/code&gt; struct and moved template parsing to a &lt;code&gt;loadTemplates()&lt;/code&gt; call that runs once at startup via &lt;code&gt;NewEmailService&lt;/code&gt;. &lt;code&gt;SendEmail&lt;/code&gt; now just reads from the in-memory cache instead of hitting disk on every request.&lt;/p&gt;

&lt;p&gt;During review, a few important issues came up that pushed the fix further than my first pass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Graceful startup&lt;/strong&gt;: originally a missing template would crash the server via &lt;code&gt;log.Fatalf&lt;/code&gt;. I changed this to log a warning and continue, with &lt;code&gt;ErrTemplateNotFound&lt;/code&gt; returned only when that specific template is actually requested.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thread safety&lt;/strong&gt;: since the cache is shared across concurrent goroutines handling requests, I had to make sure &lt;code&gt;template.Template&lt;/code&gt; execution was safe under concurrent access rather than assuming a single cached pointer was automatically safe to reuse everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A tricky merge conflict&lt;/strong&gt;: a later commit accidentally discarded the template value (&lt;code&gt;t, ok&lt;/code&gt; changed to &lt;code&gt;_, ok&lt;/code&gt;) while a downstream line still called &lt;code&gt;t.Execute(...)&lt;/code&gt;, which broke the build. I traced it back through the diff and restored the correct binding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: no repeated disk reads, safer concurrent access, and the server no longer crashes on a missing template file. It degrades gracefully instead.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <item>
      <title>Building Kisan Sahay: A Voice Agent for Farmers in 10 Days</title>
      <dc:creator>Mehwish</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:25:00 +0000</pubDate>
      <link>https://dev.to/mehwish_e1e7e09af6e0754ed/building-kisan-sahay-a-voice-agent-for-farmers-in-10-days-5h6p</link>
      <guid>https://dev.to/mehwish_e1e7e09af6e0754ed/building-kisan-sahay-a-voice-agent-for-farmers-in-10-days-5h6p</guid>
      <description>&lt;h2&gt;
  
  
  The problem and the users
&lt;/h2&gt;

&lt;p&gt;For a huge number of farmers across India, the biggest barrier to useful information isn't availability, it's &lt;em&gt;interface&lt;/em&gt;. Government schemes, weather forecasts, and mandi prices all exist somewhere online, but they're locked behind text-heavy apps and websites that assume comfort with English, typing, and navigating menus. A farmer standing in their field, phone in hand, doesn't want to fill out a form. They want to &lt;em&gt;ask a question out loud&lt;/em&gt; and get a useful answer back, the same way they'd ask a neighbor or a local officer.&lt;/p&gt;

&lt;p&gt;That's the gap Kisan Sahay (किसान सहाय) tries to close. It's a voice-first assistant for farmers, built over 10 days for the &lt;strong&gt;Farm &amp;amp; Field&lt;/strong&gt; track of Murf's &lt;strong&gt;10 Days of Voice Agents — VoiceForBharat Edition&lt;/strong&gt; challenge. You call it, you talk to it in Hindi, English, or a natural mix of both, and it helps with crops, weather, government schemes, and knows when to bring in more help.&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%2Ftwy5qcojgtrlfk10lkff.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%2Ftwy5qcojgtrlfk10lkff.png" alt="Kisan Sahay landing page, showing the start button and what the assistant can help with" width="799" height="376"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The landing page — one clear button to start, and a plain-language explanation of what Kisan Sahay can help with.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What the voice agent actually does
&lt;/h2&gt;

&lt;p&gt;By the end of the challenge, Kisan Sahay could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hold a natural spoken conversation in Hindi (in proper Devanagari script), English, or Hinglish, matching whatever register the farmer just used&lt;/li&gt;
&lt;li&gt;Remember returning farmers by name, with their consent, and pick up from what was discussed last time&lt;/li&gt;
&lt;li&gt;Look up a &lt;strong&gt;live&lt;/strong&gt; weather forecast for any district&lt;/li&gt;
&lt;li&gt;Answer questions about major government farmer schemes from a reference dataset&lt;/li&gt;
&lt;li&gt;Place &lt;strong&gt;outbound&lt;/strong&gt; calls to warn a farmer about weather relevant to their crop&lt;/li&gt;
&lt;li&gt;Recognize when a problem is beyond it and create a tracked request for a human, with the farmer's permission&lt;/li&gt;
&lt;li&gt;Track whether each call actually achieved something useful, visible on a live dashboard&lt;/li&gt;
&lt;li&gt;Hand off detailed crop-health troubleshooting to a dedicated specialist agent, without making the farmer repeat themselves&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How the system works
&lt;/h2&gt;

&lt;p&gt;The core pipeline is the same shape most voice agents use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Farmer speaks
    -&amp;gt; Deepgram STT (multilingual mode) turns speech into text
    -&amp;gt; Gemini LLM decides what to say, or which tool to call
    -&amp;gt; Murf Falcon TTS turns the response into speech
    -&amp;gt; LiveKit streams the audio back and forth in real time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What makes it more than a simple loop is what sits &lt;em&gt;around&lt;/em&gt; that pipeline: a SQLite database for memory, escalations, and call outcomes; a couple of real tools (weather, schemes); an outbound calling path over SIP; and a second, focused agent it can hand off to.&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%2F44cpjmzaiuzaz2myi4s9.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%2F44cpjmzaiuzaz2myi4s9.png" alt="A normal conversation in progress with Kisan Sahay, showing the avatar, current state, and live transcript panel with a weather response" width="800" height="379"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A normal conversation in progress — the transcript panel updates live as Kisan Sahay answers a weather question with a real, dated forecast instead of a guess.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The most important features, and why they mattered
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;An Indian voice, and code-mixed language handling.&lt;/strong&gt; Farmers don't switch neatly between Hindi and English — they mix them mid-sentence. Getting the agent to reliably detect &lt;em&gt;Hindi in Devanagari&lt;/em&gt;, &lt;em&gt;English&lt;/em&gt;, and &lt;em&gt;Hinglish (Hindi words in Roman script)&lt;/em&gt; and reply in the same register, every single turn, took real iteration. The fix that finally worked was making the language rule explicit and absolute in the system prompt: judge only the most recent message, never carry language over from earlier turns, and always write Hindi in Devanagari script even when the farmer typed it in Roman letters — because the TTS engine needs the correct script to pronounce it properly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory with real consent.&lt;/strong&gt; Day 4 added a &lt;code&gt;lookup_caller&lt;/code&gt; and &lt;code&gt;save_caller_info&lt;/code&gt; tool pair. The important design choice wasn't the database schema, it was that &lt;em&gt;consent is enforced in code&lt;/em&gt;, not just described in the prompt. The &lt;code&gt;save_caller_info&lt;/code&gt; function takes a &lt;code&gt;consent&lt;/code&gt; boolean, and if it's false, nothing gets written — the tool itself refuses, regardless of what the model "intends."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real tools with honest failure.&lt;/strong&gt; A weather tool that calls a live API is only actually useful if it also handles the API being slow, wrong, or down. The agent is instructed to say so honestly and suggest another source rather than inventing a forecast, and a genuinely failed lookup is recorded as a failure on the analytics dashboard, not silently swallowed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowing when &lt;em&gt;not&lt;/em&gt; to answer.&lt;/strong&gt; This was, in hindsight, the most important feature. A voice agent giving farming advice should not pretend to have information it doesn't. When a lookup fails and the farmer needs a real answer, or when a crop problem sounds serious, Kisan Sahay asks permission and creates a tracked escalation for a human, instead of guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A specialist handoff.&lt;/strong&gt; By Day 9, Kisan Sahay had grown into a generalist. Rather than stretch its main prompt to cover deep crop-disease troubleshooting too, it hands that conversation off to a separate &lt;code&gt;CropSpecialistAgent&lt;/code&gt; with its own narrower prompt and guardrails. The full conversation history carries across the handoff automatically, so the farmer never repeats themselves.&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%2F4yw6pqvg2dy8kcdy7la1.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%2F4yw6pqvg2dy8kcdy7la1.png" alt="Transcript showing Kisan Sahay announcing a handoff, then the crop specialist introducing itself and continuing the conversation" width="800" height="379"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The handoff in action — Kisan Sahay announces the transfer in one line, and the crop specialist picks up immediately, already aware of what the farmer described, no repeating required.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outbound calling, tested for real.&lt;/strong&gt; Day 6 took the agent off the browser entirely — it dials out over a SIP trunk (a free Linphone account, since Twilio's trial ran out) to deliver a weather warning the farmer didn't ask for. Since the call is unsolicited, the opening line has to do real work: say who's calling, why, and how to opt out, all before anything else.&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%2Fxtdajwquc6kylw1g6l57.jpeg" 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%2Fxtdajwquc6kylw1g6l57.jpeg" alt="Linphone call log showing an incoming call from the Kisan Sahay outbound agent" width="662" height="1370"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The call log on the receiving end — proof the outbound call actually connects over SIP, not just something running locally in a browser tab.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Challenges, and how I got through them
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The forced handoff wouldn't actually hand off.&lt;/strong&gt; When I first built the specialist handoff, the main agent would say "let me connect you to the crop specialist" and then just... stop, waiting for the farmer to say "okay" before actually calling the tool. It was treating the handoff like a consent-based action, the same pattern used for memory and escalations. The fix was making the tool description explicit that this was &lt;em&gt;not&lt;/em&gt; a consent flow: the agent should call the tool in the same turn, immediately after announcing the handoff, with no waiting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini's turn-ordering rule broke the outbound greeting.&lt;/strong&gt; For outbound calls (Day 6), I initially passed per-call context, like the farmer's name and district, straight into the &lt;code&gt;generate_reply()&lt;/code&gt; instructions when the call connected. This intermittently broke with a &lt;code&gt;400&lt;/code&gt; error from Gemini: &lt;em&gt;"function call turn comes immediately after a user turn or after a function response turn."&lt;/em&gt; Because the very first assistant turn on an outbound call has no preceding user turn, any instruction that nudged the model toward an immediate tool call (like "check the weather right after your opening") violated that ordering rule. The fix was to bake all per-call context into the agent's &lt;em&gt;instructions&lt;/em&gt; at construction time, and keep the &lt;code&gt;generate_reply()&lt;/code&gt; trigger itself completely generic and static.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Call success tracking was quietly wrong.&lt;/strong&gt; Early on, every call that didn't hit a specific tool (weather, schemes, escalation) was marked a failure by default, even a call where the agent answered a general farming question perfectly well from its own knowledge. The lesson: default to success for a call that completes without error, and mark failure only on genuine, specific failure paths — not on "no tool happened to fire."&lt;/p&gt;
&lt;h2&gt;
  
  
  How you can build your own
&lt;/h2&gt;

&lt;p&gt;If you want to build something like this, here's the shape of it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The four components you need:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Speech-to-text (STT)&lt;/strong&gt; — turns the caller's voice into text. I used Deepgram, with &lt;code&gt;language="multi"&lt;/code&gt; for reliable code-switched language detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An LLM&lt;/strong&gt; — decides what to say and which tools to call. I used Gemini.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text-to-speech (TTS)&lt;/strong&gt; — turns the response back into speech. I used &lt;strong&gt;Murf Falcon&lt;/strong&gt;, the fastest TTS API available, for low-latency, natural-sounding Indian voices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time transport&lt;/strong&gt; — moves audio back and forth with low latency. I used &lt;a href="https://docs.livekit.io" rel="noopener noreferrer"&gt;LiveKit&lt;/a&gt;, which also handles SIP telephony for outbound calling.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Setting up and running it:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fork &lt;a href="https://github.com/MEHWISH310/murf-livekit-starter" rel="noopener noreferrer"&gt;github.com/MEHWISH310/murf-livekit-starter&lt;/a&gt;, then:&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 &amp;lt;your-repo-url&amp;gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;murf-livekit-starter

&lt;span class="c"&gt;# Terminal 1 — LiveKit server (skip this if you're using LiveKit Cloud instead of self-hosting)&lt;/span&gt;
livekit-server &lt;span class="nt"&gt;--dev&lt;/span&gt;

&lt;span class="c"&gt;# Terminal 2 — Backend&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;backend
uv &lt;span class="nb"&gt;sync
&lt;/span&gt;uv run python src/agent.py download-files
uv run python src/agent.py dev

&lt;span class="c"&gt;# Terminal 3 — Frontend&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;frontend
pnpm &lt;span class="nb"&gt;install
&lt;/span&gt;pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Where API keys go:&lt;/strong&gt; every key lives in a &lt;code&gt;.env.local&lt;/code&gt; file (one in &lt;code&gt;backend/&lt;/code&gt;, one in &lt;code&gt;frontend/&lt;/code&gt;), which is git-ignored and never committed. Never hardcode a key directly into source files, and never publish &lt;code&gt;.env.local&lt;/code&gt; contents in a blog post, screenshot, or public repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connecting and testing:&lt;/strong&gt; once both the backend agent and frontend are running, open &lt;code&gt;http://localhost:3000&lt;/code&gt;, click the start button, allow microphone access, and speak. The agent should greet you, and you can talk it through a real conversation from there.&lt;/p&gt;

&lt;p&gt;The full code for Kisan Sahay is public: &lt;strong&gt;&lt;a href="https://github.com/MEHWISH310/murf-livekit-starter" rel="noopener noreferrer"&gt;github.com/MEHWISH310/murf-livekit-starter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd improve next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A live mandi price lookup, instead of the agent always deferring to "check locally"&lt;/li&gt;
&lt;li&gt;Phone-number-based caller identification for a real telephony deployment, instead of name-based lookup&lt;/li&gt;
&lt;li&gt;A broader government scheme dataset, ideally backed by a live source instead of a static local one&lt;/li&gt;
&lt;li&gt;More specialist agents for other common deep topics (irrigation planning, government scheme eligibility walkthroughs)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;Ten days ago this was a generic customer-support template. Now it's an assistant that remembers who it's talking to, knows the difference between something it can answer and something it shouldn't guess at, and calls in the right help, human or specialist, when it needs to. That last part, knowing its own limits, ended up mattering more than any single feature.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built for &lt;a href="https://murf.ai" rel="noopener noreferrer"&gt;10 Days of Voice Agents — VoiceForBharat Edition&lt;/a&gt;, powered by Murf Falcon.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>GoFr: The Opinionated Go Framework That Takes Observability Seriously</title>
      <dc:creator>Mehwish</dc:creator>
      <pubDate>Fri, 31 Jul 2026 18:46:09 +0000</pubDate>
      <link>https://dev.to/mehwish_e1e7e09af6e0754ed/gofr-the-opinionated-go-framework-that-takes-observability-seriously-53b0</link>
      <guid>https://dev.to/mehwish_e1e7e09af6e0754ed/gofr-the-opinionated-go-framework-that-takes-observability-seriously-53b0</guid>
      <description>&lt;h2&gt;
  
  
  Why I Looked at GoFr
&lt;/h2&gt;

&lt;p&gt;If you've built microservices in Go, you know the drill: pick a router, wire up config loading, bolt on logging, figure out tracing, add health checks, write your own circuit breaker logic... by the time you've written your first "real" endpoint, you've already made a dozen architectural decisions that have nothing to do with your actual business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GoFr&lt;/strong&gt; is an opinionated microservice framework for Go that tries to remove that tax. It's built with Kubernetes deployment and observability as first-class citizens rather than afterthoughts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Stood Out to Me
&lt;/h2&gt;

&lt;p&gt;A few things made GoFr worth a second look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Batteries-included observability&lt;/strong&gt; — structured logs, distributed traces, and metrics are built in, not something you assemble from five different libraries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST standards by default&lt;/strong&gt; — sensible conventions out of the box instead of a blank slate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in resilience&lt;/strong&gt; — HTTP client with circuit breaker support means you're not hand-rolling retry/backoff logic for every service call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pub/Sub and gRPC support&lt;/strong&gt; — covers the two most common inter-service communication patterns without extra setup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database migrations, cron jobs, health checks&lt;/strong&gt; — the "boring but necessary" plumbing is already there&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hot log-level changes&lt;/strong&gt; — you can adjust verbosity without redeploying, which is a small feature that pays off a lot during incident response
## Getting Started&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The setup is refreshingly minimal. First, make sure you're on &lt;strong&gt;Go 1.24+&lt;/strong&gt;, then pull in the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get &lt;span class="nt"&gt;-u&lt;/span&gt; gofr.dev/pkg/gofr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A working "Hello World" service is just a few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"gofr.dev/pkg/gofr"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gofr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/greet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gofr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// listens and serves on localhost:8000&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then hit &lt;code&gt;localhost:8000/greet&lt;/code&gt; and you've got a live HTTP service — no router config, no manual JSON marshaling boilerplate, no separate logging setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Fits
&lt;/h2&gt;

&lt;p&gt;GoFr isn't trying to be a general-purpose do-everything toolkit — it's clearly optimized for teams shipping microservices to Kubernetes who want observability and resilience patterns baked in from day one. If that's your world, it's worth evaluating against whatever stack you're using today (whether that's a bare &lt;code&gt;net/http&lt;/code&gt; + custom middleware setup, or another framework like Gin or Echo layered with extra tooling).&lt;/p&gt;

&lt;p&gt;The project is actively maintained — frequent commits, regular releases, and enough real-world adoption to be listed in the &lt;strong&gt;CNCF Landscape&lt;/strong&gt;. It's Apache-2.0 licensed, so there's no ambiguity about usage rights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/gofr-dev/gofr" rel="noopener noreferrer"&gt;github.com/gofr-dev/gofr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Examples&lt;/strong&gt;: check out the &lt;code&gt;examples/&lt;/code&gt; directory in the repo for more runnable patterns beyond the basic "Hello World"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs&lt;/strong&gt;: linked from the README for deeper dives into each feature
If you're building Go microservices and tired of re-solving the same infrastructure problems on every project, GoFr is worth thirty minutes of your time to try out.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Have you used GoFr in production? I'd love to hear how it held up — drop a comment below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>go</category>
      <category>microservices</category>
    </item>
  </channel>
</rss>
