<?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: Ayush Barai</title>
    <description>The latest articles on DEV Community by Ayush Barai (@ayush_barai_549a691bc4f6d).</description>
    <link>https://dev.to/ayush_barai_549a691bc4f6d</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%2F4176940%2F59ec10a1-882c-4847-8f70-848dc02e85ec.png</url>
      <title>DEV Community: Ayush Barai</title>
      <link>https://dev.to/ayush_barai_549a691bc4f6d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ayush_barai_549a691bc4f6d"/>
    <language>en</language>
    <item>
      <title>MeetBrief AI : turns spoken discussions into structured, shareable meeting intelligence reports.</title>
      <dc:creator>Ayush Barai</dc:creator>
      <pubDate>Mon, 26 Oct 2026 04:46:29 +0000</pubDate>
      <link>https://dev.to/ayush_barai_549a691bc4f6d/meetbrief-ai-turns-spoken-discussions-into-structured-shareable-meeting-intelligence-reports-2kjj</link>
      <guid>https://dev.to/ayush_barai_549a691bc4f6d/meetbrief-ai-turns-spoken-discussions-into-structured-shareable-meeting-intelligence-reports-2kjj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Picture the twenty minutes after a 40-minute launch call. The call is over. The decisions are not. You open a blank document and try to remember what was actually agreed.&lt;/p&gt;

&lt;p&gt;The recording has the answer. It always does. The problem is that the recording is 40 minutes long, and you are tired.&lt;/p&gt;

&lt;p&gt;That gap — between a conversation and the record of it — is where most meeting tools make things worse. They hand you a confident paragraph with no receipts. It reads well. It might even be right. But when someone asks who agreed to the 14-day trial, or why a task landed on their plate, the summary has nothing to show.&lt;/p&gt;

&lt;p&gt;MeetBrief AI closes that gap. It turns a recording into a transcript, an evidence-linked report, assigned action items, and a spoken recap you can listen to on the way home.&lt;/p&gt;

&lt;p&gt;The recap is the part I want to spend time on. Giving a meeting a voice is easy now. Giving it a voice that cannot invent things is the interesting problem.&lt;/p&gt;

&lt;p&gt;![MeetBrief AI — Your meeting ended. Your recap can prove it.]&lt;br&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%2F1ikppaqi7ikrhnusiy0r.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%2F1ikppaqi7ikrhnusiy0r.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;MeetBrief is a Next.js 15 PWA for the twenty minutes after a meeting.&lt;/p&gt;

&lt;p&gt;You upload a recording or record in the browser, confirm consent, and the app transcribes it with a self-hosted open-source Whisper server. Gemma then turns the transcript into a structured report: purpose, outcomes, discussion points, decisions, owners, deadlines, open questions, and a next-meeting agenda.&lt;/p&gt;

&lt;p&gt;Then you can rename the anonymous speakers, generate a spoken recap with ElevenLabs, share a read-only link, or export Markdown.&lt;/p&gt;

&lt;p&gt;There is no attendee list and no manual participant entry. Diarization gives you &lt;code&gt;Person 1&lt;/code&gt;, &lt;code&gt;Person 2&lt;/code&gt;, and so on. A real name is applied only when a human confirms it. The AI may suggest a name, but only when that speaker explicitly introduced themselves in their own transcript line — and even then, it waits for a click.&lt;/p&gt;

&lt;p&gt;The lazy payoff is simple: you leave the call and the first draft of the record already exists. Not a vibe summary. A document where every decision and task has a timestamp and a quote behind it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The fastest way to see it is the &lt;code&gt;/demo&lt;/code&gt; route. It is a complete interactive report — overview, transcript, decisions, actions, and recap — with no account, no recording, and no AI request.&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/Ayush-Barai/MeetBrief
&lt;span class="nb"&gt;cd &lt;/span&gt;MeetBrief
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;span class="c"&gt;# open http://localhost:3000/demo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The demo data is fictional and labeled as such everywhere. The screenshots below come from a real production build with the integrations intentionally unconfigured, so the UI is showing the actual product, not a mockup.&lt;/p&gt;

&lt;p&gt;There is no public deployment yet. The &lt;code&gt;/demo&lt;/code&gt; route is deliberately credential-free, so you can see the whole UI without a database or an API key; the full pipeline is in &lt;code&gt;DEPLOYMENT.md&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The report workspace
&lt;/h3&gt;

&lt;p&gt;The overview keeps the executive summary separate from the evidence. Tabs for transcript, decisions, action items, and the audio recap sit above the report instead of hiding in a sidebar.&lt;/p&gt;

&lt;p&gt;![MeetBrief demo report overview]&lt;br&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%2Fp712u6trqhecreglb4v3.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%2Fp712u6trqhecreglb4v3.png" alt=" " width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  No attendee list
&lt;/h3&gt;

&lt;p&gt;You add a title, choose a language, upload a recording or record in the browser, and confirm consent. That is the whole setup. Speakers are discovered after transcription.&lt;/p&gt;

&lt;p&gt;![Create a meeting without entering participants]&lt;br&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%2Fjyvq30mg0uxeabs24rka.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%2Fjyvq30mg0uxeabs24rka.png" alt=" " width="800" height="1676"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Speakers are anonymous until you say otherwise
&lt;/h3&gt;

&lt;p&gt;The speaker panel shows &lt;code&gt;Person 1&lt;/code&gt; through &lt;code&gt;Person 4&lt;/code&gt;, their segment counts, when they first spoke, and any AI suggestion that still needs confirmation.&lt;/p&gt;

&lt;p&gt;The suggestion is not a rename. It is a hint stored separately from the transcript. Nothing changes until a human clicks.&lt;/p&gt;

&lt;p&gt;![Speaker panel with anonymous labels and AI suggestions]&lt;br&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%2Fz3g3vvvxza7wyamx7anj.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%2Fz3g3vvvxza7wyamx7anj.png" alt=" " width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The transcript keeps the same labels, timestamps, and the transcription metadata: &lt;code&gt;whisper (demo) · large-v3-turbo · diarized speaker labels&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;![Transcript with Person labels and timestamps]&lt;br&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%2Fngdkoavvz2nl7qu249kd.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%2Fngdkoavvz2nl7qu249kd.png" alt=" " width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Decisions keep their uncertainty
&lt;/h3&gt;

&lt;p&gt;A meeting record should not flatten “we agreed” and “we might agree” into the same sentence. Confirmed and tentative decisions look different, and each one carries the quote it came from.&lt;/p&gt;

&lt;p&gt;![Decisions with evidence quotes]&lt;br&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%2Foc5ajgxki1a7cgw4c7lv.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%2Foc5ajgxki1a7cgw4c7lv.png" alt=" " width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Action items with real owners only
&lt;/h3&gt;

&lt;p&gt;Owners and deadlines appear only when the transcript supports them. Otherwise the item is unassigned or the date is absent. Status can be updated as work moves.&lt;/p&gt;

&lt;p&gt;![Action items with owners, deadlines, and evidence]&lt;br&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%2F1d3e2e0sza0hg5iaq81o.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%2F1d3e2e0sza0hg5iaq81o.png" alt=" " width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Rule: The Model Proposes, The Transcript Decides
&lt;/h2&gt;

&lt;p&gt;Most AI meeting notes trust the model’s output. I don’t.&lt;/p&gt;

&lt;p&gt;Gemma returns structured JSON with segment IDs and quotes. Before anything is saved, the server checks it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zod validates the shape of the response.&lt;/li&gt;
&lt;li&gt;Every cited segment ID must exist in the transcript.&lt;/li&gt;
&lt;li&gt;Every quote must actually appear in that segment. If the ID is wrong but the quote is real, the server searches for the matching segment.&lt;/li&gt;
&lt;li&gt;Evidence that cannot be verified is removed.&lt;/li&gt;
&lt;li&gt;An owner the transcript doesn’t support — and who isn’t a known speaker — is reset to &lt;code&gt;Unassigned&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A deadline without support in the transcript becomes &lt;code&gt;Not specified&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A claim that loses all of its evidence stays in the report — with a visible warning. It is not silently deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last rule matters. Deleting a questionable decision hides the disagreement. Keeping it with a warning tells you exactly where to look.&lt;/p&gt;

&lt;p&gt;Here is the core of the check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reference&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;byId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;segmentId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;segment&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;needle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;needle&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;claimLabel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: a cited segment could not be verified and was removed.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quoteIsExact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;segmentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timestampSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startSeconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quoteIsExact&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;277&lt;/span&gt;&lt;span class="p"&gt;),&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;p&gt;The result is a report that can be incomplete. It can say “a decision had no verifiable transcript evidence.” It cannot confidently invent a decision.&lt;/p&gt;

&lt;p&gt;I think that trade is the whole product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving the Agent a Voice
&lt;/h2&gt;

&lt;p&gt;This is my entry for &lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt;: the open-source agent gets a voice, and the voice is constrained by the same evidence rules as the text.&lt;/p&gt;

&lt;p&gt;The recap is not text-to-speech over the transcript. It is a deterministic script built from the &lt;strong&gt;reconciled report&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The meeting title and purpose.&lt;/li&gt;
&lt;li&gt;Up to four key outcomes and takeaways.&lt;/li&gt;
&lt;li&gt;Up to five decisions, with tentative ones explicitly labeled as tentative.&lt;/li&gt;
&lt;li&gt;Up to eight action items, each with its owner and deadline — or the fact that there isn’t one.&lt;/li&gt;
&lt;li&gt;Up to four open questions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then ElevenLabs reads it.&lt;/p&gt;

&lt;p&gt;That order is the design. The voice only says things that survived validation. If the report refuses a claim, the audio never speaks it.&lt;/p&gt;

&lt;p&gt;Could I have used an open-source TTS model? Yes. I chose ElevenLabs because the recap is the one place where voice quality &lt;em&gt;is&lt;/em&gt; the product, and a robotic read undermines the reason to listen. The trade-off is explicit: the intelligence and the evidence stay open and self-hostable; the last mile is a hosted service I can swap out without touching the pipeline.&lt;/p&gt;

&lt;p&gt;![Audio recap script built from the edited report]&lt;br&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%2F9f1cdbaryrnbzcyzlu8n.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%2F9f1cdbaryrnbzcyzlu8n.png" alt=" " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The integration is deliberately boring in the places that should be boring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`https://api.elevenlabs.io/v1/text-to-speech/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;voiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?output_format=mp3_44100_128`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xi-api-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elevenLabsKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio/mpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eleven_multilingual_v2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;voice_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;stability&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;similarity_boost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;use_speaker_boost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;),&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;p&gt;The script is capped at 4,900 characters so a long meeting doesn’t turn into a failed request. The MP3 is written to GridFS, not stuffed into the meeting document. Generation is on demand, not part of the transcription job.&lt;/p&gt;

&lt;p&gt;Two details I care about more than the voice settings:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure is not data loss.&lt;/strong&gt; If ElevenLabs fails, the script is saved with status &lt;code&gt;failed&lt;/code&gt;. You can read it, fix the report, and retry. The app never pretends audio exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The voice can go stale, and it says so.&lt;/strong&gt; Rename a speaker or edit the report and the existing recap is marked &lt;code&gt;stale&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recapScript&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;recapScript&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;meeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recap&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;COLLECTIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meetings&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;meetingId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;$set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recap.script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recapScript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;meeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recap&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
        &lt;span class="nx"&gt;meeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recap&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recap.status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&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;p&gt;The old MP3 is still there, but the UI tells you it no longer matches the record. A spoken recap that silently disagrees with the written one is worse than no recap at all.&lt;/p&gt;

&lt;p&gt;That is what “giving an open-source agent a voice” means here: the voice is the last mile of an auditable pipeline, not a layer on top of a black box.&lt;/p&gt;

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

&lt;p&gt;The repository is public: &lt;a href="https://github.com/Ayush-Barai/MeetBrief" rel="noopener noreferrer"&gt;https://github.com/Ayush-Barai/MeetBrief&lt;/a&gt;. It is a Next.js 15 / React 19 / TypeScript app with Zod validation, MongoDB 7, and Vitest. The files worth reading first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;lib/ai/report.ts&lt;/code&gt; — evidence reconciliation and speaker-suggestion checks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib/elevenlabs/index.ts&lt;/code&gt; — recap script and text-to-speech&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib/transcription/whisper.ts&lt;/code&gt; — OpenAI-compatible adapter and diarization mapping&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib/speakers.ts&lt;/code&gt; — rename propagation across the report and action items&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;render.yaml&lt;/code&gt; + &lt;code&gt;DEPLOYMENT.md&lt;/code&gt; — deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Open-source Whisper, self-hosted
&lt;/h3&gt;

&lt;p&gt;The app talks to any OpenAI-compatible &lt;code&gt;/audio/transcriptions&lt;/code&gt; endpoint. The recommended server is &lt;a href="https://github.com/hwdsl2/scribecrate" rel="noopener noreferrer"&gt;ScribeCrate&lt;/a&gt; (formerly &lt;code&gt;docker-whisper&lt;/code&gt;, image &lt;code&gt;hwdsl2/whisper-server&lt;/code&gt;), which wraps open-source &lt;code&gt;faster-whisper&lt;/code&gt; and supports local speaker diarization.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; whisper &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 9000:9000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;WHISPER_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;large-v3-turbo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;WHISPER_DIARIZATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; whisper-data:/var/lib/whisper &lt;span class="se"&gt;\&lt;/span&gt;
  hwdsl2/whisper-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the server returns a &lt;code&gt;speaker&lt;/code&gt; field per segment, MeetBrief maps those labels to stable &lt;code&gt;Person N&lt;/code&gt; labels in first-seen order. If the server does not diarize, every segment is &lt;code&gt;Person 1&lt;/code&gt; and the transcript metadata says &lt;code&gt;diarized: false&lt;/code&gt;. The app never invents speakers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemma for structured analysis
&lt;/h3&gt;

&lt;p&gt;Gemma is served through an OpenAI-compatible endpoint, so Ollama, vLLM, or a hosted Gemma endpoint all work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GEMMA_API_URL=http://localhost:11434/v1
GEMMA_MODEL=gemma3:27b
GEMMA_TIMEOUT_MS=180000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prompt asks for JSON with evidence segment IDs. The response is validated with Zod, then reconciled against the transcript before anything is saved. The model’s owners, deadlines, and dates are never trusted blindly.&lt;/p&gt;

&lt;h3&gt;
  
  
  MongoDB Atlas for the record
&lt;/h3&gt;

&lt;p&gt;Atlas stores meeting metadata, ordered transcript segments, reports, action items, and share-token hashes. Recordings and recap MP3s live in the GridFS bucket &lt;code&gt;meeting_files&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The MVP uses a signed, HTTP-only browser cookie for ownership. Every meeting read and write is scoped by &lt;code&gt;userId&lt;/code&gt; on the server. Share links use 256-bit tokens; only the SHA-256 hash is stored, and links can be revoked.&lt;/p&gt;

&lt;h3&gt;
  
  
  ElevenLabs for the recap
&lt;/h3&gt;

&lt;p&gt;Covered above. The important part is not the API call. It is that the script is generated from the report that already passed validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Render for deployment
&lt;/h3&gt;

&lt;p&gt;The repository includes &lt;code&gt;render.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build: &lt;code&gt;npm ci &amp;amp;&amp;amp; npm run build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Start: &lt;code&gt;npm run start&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Health check: &lt;code&gt;/api/health&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Node 22&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;/api/health&lt;/code&gt; returns &lt;code&gt;200 ok&lt;/code&gt; only when MongoDB is configured and reachable. It returns &lt;code&gt;503 degraded&lt;/code&gt; otherwise, and it never exposes secrets.&lt;/p&gt;

&lt;p&gt;The full Atlas + Whisper + Gemma + ElevenLabs + Render walkthrough is in &lt;a href="https://github.com/Ayush-Barai/MeetBrief/blob/main/DEPLOYMENT.md" rel="noopener noreferrer"&gt;&lt;code&gt;DEPLOYMENT.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Innovation Matters
&lt;/h2&gt;

&lt;p&gt;Three reasons, in the order they mattered to me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The audio doesn’t have to leave.&lt;/strong&gt; Meeting recordings contain pricing, hiring plans, legal risk, customer names, and things people said without expecting a transcript. Whisper can run on your laptop or your own VPS. Gemma can too. The sensitive input stays inside infrastructure you control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rules are inspectable.&lt;/strong&gt; The prompt, the Zod schema, the evidence reconciliation, and the speaker-rename rules are all in the repository. If you disagree with how a claim is accepted, you can read the code and change it. That is much harder with a hosted pipeline that returns a confident paragraph and no trail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model is replaceable.&lt;/strong&gt; Whisper is an endpoint. Gemma is an endpoint. You can trade model size for hardware, swap providers, or run everything locally without rewriting the product. The open pieces are not decoration — remove them and MeetBrief stops working.&lt;/p&gt;

&lt;p&gt;A closed pipeline gives you a paragraph. An open one gives you a paragraph you can argue with.&lt;/p&gt;

&lt;p&gt;And the theme fits: slow and steady is fine when the machine does the slow part. A 40-minute call can take three minutes to transcribe. The human shouldn’t have to spend twenty minutes rebuilding the record afterward. Slowness is only a virtue if it buys accuracy. Otherwise it is just latency.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I used OpenCode during the build. I did not save a DevRelay session, so here is the short version.&lt;/p&gt;

&lt;p&gt;The useful work was narrow and verifiable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tracing every reference to the old transcription provider before removing it;&lt;/li&gt;
&lt;li&gt;rewriting the Whisper adapter to parse optional diarization labels;&lt;/li&gt;
&lt;li&gt;checking that speaker renames still propagate through reports, actions, exports, and the recap script;&lt;/li&gt;
&lt;li&gt;updating the setup page, environment example, Render blueprint, and README;&lt;/li&gt;
&lt;li&gt;running typecheck, tests, and a production build after each change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most instructive bug is a regex problem: &lt;code&gt;"I'm Maya"&lt;/code&gt; and &lt;code&gt;"Maya's idea"&lt;/code&gt; look identical to a naive pattern. The self-introduction check needs a real word boundary after the name; the test now pins it.&lt;/p&gt;

&lt;p&gt;The human decisions stayed human: removing a proprietary speech provider, deciding what “open source” should mean for this project, and what the demo is allowed to claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lazy Payoff
&lt;/h2&gt;

&lt;p&gt;The payoff I designed for is the twenty minutes after a call: no scrubbing a recording for a timestamp, no guessing who owns a task, no retyping decisions into a document.&lt;/p&gt;

&lt;p&gt;I am not going to claim a measured team time saving. The demo is fictional and I did not run this with a team this week. What I can show is the workflow it removes — and the fact that the first draft arrives with receipts.&lt;/p&gt;

&lt;p&gt;The next automation I would add is a durable processing queue. Right now processing runs through Next.js &lt;code&gt;after()&lt;/code&gt;, which is fine for a demo but can be interrupted by a Render restart. A worker with a heartbeat and lease would make long meetings reliable. After that: real multi-user auth, a retention and deletion job, and per-user storage quotas.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Real and What Is Demo
&lt;/h2&gt;

&lt;p&gt;I want to be precise about the state of the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The demo report, transcript, speaker suggestions, and recap script are fictional and clearly labeled.&lt;/li&gt;
&lt;li&gt;The screenshots were captured from a real local production build with integrations intentionally unconfigured.&lt;/li&gt;
&lt;li&gt;The code paths for transcription, Gemma analysis, MongoDB persistence, and ElevenLabs recaps are implemented against real APIs.&lt;/li&gt;
&lt;li&gt;The full end-to-end flow has &lt;strong&gt;not&lt;/strong&gt; been executed in this workspace with live credentials. Missing configuration is surfaced in &lt;code&gt;/setup&lt;/code&gt;, &lt;code&gt;/api/health&lt;/code&gt;, and processing responses instead of being simulated.&lt;/li&gt;
&lt;li&gt;Unit tests, TypeScript checks, and the production build pass locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That distinction matters. A demo should show the product honestly, not borrow credibility from an AI call that never happened.&lt;/p&gt;

&lt;p&gt;![Setup page showing which integrations are configured]&lt;br&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%2F7dn0kg2rsqvshrtrnvma.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%2F7dn0kg2rsqvshrtrnvma.png" alt=" " width="800" height="1529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;I would like this project considered for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ElevenLabs&lt;/strong&gt; — the open-source agent gets a voice, generated from the validated report, with failure handling and stale-audio detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma&lt;/strong&gt; — evidence-linked meeting analysis through an OpenAI-compatible Gemma endpoint, with Zod validation and transcript reconciliation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB Atlas&lt;/strong&gt; — meetings, transcript segments, reports, actions, share hashes, and GridFS audio in one managed database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render&lt;/strong&gt; — a production-style Next.js deployment with a health check and a blueprint.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/Ayush-Barai/MeetBrief" rel="noopener noreferrer"&gt;https://github.com/Ayush-Barai/MeetBrief&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Start with &lt;code&gt;/demo&lt;/code&gt; — no account, no recording, no API keys. If you want the real pipeline, &lt;code&gt;DEPLOYMENT.md&lt;/code&gt; walks through Atlas, Whisper, Gemma, ElevenLabs, and Render.&lt;/p&gt;

&lt;p&gt;If you have ever left a call unsure what was decided, I would love to know: what would your recap have to prove before you trusted it?&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>I built Simmer: an open-source AI agnet that turns cooking reels into recipes you can actually cook and ask questions about that recipe in real time</title>
      <dc:creator>Ayush Barai</dc:creator>
      <pubDate>Mon, 19 Oct 2026 04:50:02 +0000</pubDate>
      <link>https://dev.to/ayush_barai_549a691bc4f6d/i-built-simmer-an-open-source-ai-agnet-that-turns-cooking-reels-into-recipes-you-can-actually-cook-1g4a</link>
      <guid>https://dev.to/ayush_barai_549a691bc4f6d/i-built-simmer-an-open-source-ai-agnet-that-turns-cooking-reels-into-recipes-you-can-actually-cook-1g4a</guid>
      <description>&lt;p&gt;&lt;strong&gt;Simmer is an open-source AI recipe assistant.&lt;/strong&gt; Share a cooking Reel, Short, or recipe link — or paste a recipe you already have — and it turns the mess into a clean recipe with ingredients, amounts, times, and ordered steps. Then it reads the steps to you and answers questions while your hands are busy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 20-second version&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; I save cooking reels every week and cook almost none of them. The recipe is split across a caption, a 45-second video, and a comment thread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product:&lt;/strong&gt; Simmer turns that mess into a structured recipe, then becomes a hands-free cooking assistant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core AI:&lt;/strong&gt; Gemma 4, open weights, for extraction, chat, and translation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inputs:&lt;/strong&gt; Instagram, YouTube Shorts, recipe links, or pasted text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; ingredients, amounts, times, tags, ordered steps, 10 languages, voice mode, installable PWA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traction:&lt;/strong&gt; one paid early user. Small amount, real signal.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Every week I save cooking reels I never cook.&lt;/p&gt;

&lt;p&gt;The recipe is usually not in one place. The ingredients are in the caption. The method is in the audio. The quantities are in a comment. The video is 45 seconds of fast cuts and no measurements. By the time I’m hungry, rewriting all of that into something usable feels like homework.&lt;/p&gt;

&lt;p&gt;Simmer is the step between saving and cooking.&lt;/p&gt;

&lt;p&gt;You add a public link or paste text. Simmer reads the page, the caption, the metadata, and — if the text is useless — the audio. Then Gemma 4 turns that into a structured recipe: ingredients with amounts, ordered steps, times, servings, tags, and a title. Your original text is kept alongside it, so you can always check what the source actually said.&lt;/p&gt;

&lt;p&gt;Then the app changes jobs. It becomes a cook-buddy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It reads the steps out loud.&lt;/li&gt;
&lt;li&gt;You can ask “how much salt?” without touching the screen.&lt;/li&gt;
&lt;li&gt;It switches between 10 languages.&lt;/li&gt;
&lt;li&gt;It installs as a PWA and opens from the share sheet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The people I built this for are not professional chefs. They are people who save more recipes than they cook, people cooking in a second language, and anyone who has stood in a kitchen with wet hands trying to scroll back to step four.&lt;/p&gt;

&lt;p&gt;The bet is simple: the input layer for recipes is broken. People already pay for recipe managers and meal planners. Simmer fixes the part before that — getting the recipe out of the feed and into a format you can cook from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://simmer-peka.onrender.com/" rel="noopener noreferrer"&gt;simmer-live-demo-link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Video demo:&lt;/strong&gt; &lt;a href="https://drive.google.com/file/d/1peFCTXdUr6poDEkeM-dUKjqqlVkIZjOc/view?usp=sharing" rel="noopener noreferrer"&gt;Watch the 45-second demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Images demo:&lt;/strong&gt; &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%2Fk2yhc37ourepw0d4ajpm.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%2Fk2yhc37ourepw0d4ajpm.png" alt=" " width="780" height="1688"&gt;&lt;/a&gt;&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%2Fcr1wgx5e94zp3focwd30.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%2Fcr1wgx5e94zp3focwd30.png" alt=" " width="780" height="1688"&gt;&lt;/a&gt;&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%2Fbo5pvikibbuh4ftbmytb.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%2Fbo5pvikibbuh4ftbmytb.png" alt=" " width="780" height="1688"&gt;&lt;/a&gt;&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%2F6odab3eb14abpldy2631.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%2F6odab3eb14abpldy2631.png" alt=" " width="780" height="1688"&gt;&lt;/a&gt;&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%2Fwrxz4qsxgbq878vw98w2.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%2Fwrxz4qsxgbq878vw98w2.png" alt=" " width="780" height="1688"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Ayush-Barai/simmer" rel="noopener noreferrer"&gt;github.com/Ayush-Barai/simmer&lt;/a&gt; — MIT licensed.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Ayush-Barai" rel="noopener noreferrer"&gt;
        Ayush-Barai
      &lt;/a&gt; / &lt;a href="https://github.com/Ayush-Barai/simmer" rel="noopener noreferrer"&gt;
        simmer
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Simmer&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Share a cooking reel → get a clean recipe → cook hands-free with an open-AI buddy.&lt;/p&gt;
&lt;p&gt;Built for the &lt;strong&gt;Hacktoberfest Open-Source AI Challenge (Week 2: Side Hustle)&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Link → recipe:&lt;/strong&gt; share a Reel/Short/TikTok or paste a link. Simmer reads the
description first, then transcribes the audio if the text has no recipe; Gemma extracts
ingredients, amounts, timings and ordered steps. You can also paste a recipe you already
have — Simmer keeps your original wording and saves a clean, structured version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hands-free cooking:&lt;/strong&gt; a live voice loop (ElevenLabs Conversational AI) with a browser
speech fallback, so you can ask questions mid-step without touching the phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;10 languages:&lt;/strong&gt; read, chat and translate in English, Spanish, French, German, Italian
Portuguese, Hindi, Japanese, Chinese and Arabic. Translations are saved with the recipe
so switching language is instant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private cookbook:&lt;/strong&gt; Clerk auth, per-user recipes, thumbnails and saved chat history.&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Ayush-Barai/simmer" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Ayush-Barai/simmer.git
&lt;span class="nb"&gt;cd &lt;/span&gt;simmer
npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your keys to &lt;code&gt;.env&lt;/code&gt; — the README covers Gemma, MongoDB Atlas, Clerk, ElevenLabs, and Sentry.&lt;/p&gt;

&lt;p&gt;The shape of the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;link / pasted text
        │
        ▼
  page text + metadata
        │
        ├─ social caption / Instagram embed
        ├─ yt-dlp metadata + subtitles
        └─ ElevenLabs Scribe (audio fallback)
        │
        ▼
  Gemma 4 → structured recipe JSON
        │
        ▼
  MongoDB Atlas → PWA → cook mode / voice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Ingestion is a cascade, not a scraper
&lt;/h3&gt;

&lt;p&gt;The hardest part is not the AI. It is getting clean text out of a link that was never designed to give it to you.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;server/ingest.js&lt;/code&gt; tries the sources in order: page text and metadata, the social caption, the Instagram embed page, then &lt;code&gt;yt-dlp&lt;/code&gt; for metadata and subtitles. If the description is empty or useless, &lt;code&gt;server/transcript.js&lt;/code&gt; downloads the audio and &lt;code&gt;server/stt.js&lt;/code&gt; sends it to ElevenLabs Scribe.&lt;/p&gt;

&lt;p&gt;Each source fails differently. Instagram blocks one path but exposes another. A YouTube Short may have no captions. A recipe blog buries the ingredients under three popups. The cascade is the product.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Gemma 4 turns text into structure
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;server/gemma.js&lt;/code&gt; asks Gemma for strict JSON: title, ingredients, steps, times, servings, tags. It uses &lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt; and &lt;code&gt;gemma-4-31b-it&lt;/code&gt;, with a fallback list and a JSON repair path.&lt;/p&gt;

&lt;p&gt;One bug cost me an afternoon: Gemma’s thinking output was being mixed into the response, which made valid recipes look empty. Filtering thought parts in both streaming and non-streaming responses fixed it. The model IDs are config, not hardcoded, so swapping models is an env-var change.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Translations are precomputed, not generated on click
&lt;/h3&gt;

&lt;p&gt;Simmer supports 10 languages: English, Spanish, French, German, Italian, Portuguese, Hindi, Japanese, Chinese, and Arabic.&lt;/p&gt;

&lt;p&gt;The first version translated on demand. It worked, but it felt slow and fragile: every language switch was another model call. Now &lt;code&gt;server/localize.js&lt;/code&gt; prefetches the other nine languages in the background after a recipe is saved. Translations are stored in MongoDB with the recipe.&lt;/p&gt;

&lt;p&gt;The result: switching languages after the first pass returns stored data in milliseconds. In my test, reopening a recipe and changing language made &lt;strong&gt;zero&lt;/strong&gt; translation requests. The model still does the work — just not while you are waiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Voice and PWA
&lt;/h3&gt;

&lt;p&gt;Cook mode uses ElevenLabs Conversational AI for the live agent, with a browser speech fallback. Sessions are scoped to a recipe and language. The same Scribe integration that rescues silent captions also makes the app work for people who would rather talk than type.&lt;/p&gt;

&lt;p&gt;The frontend is vanilla JavaScript, CSS variables, and a service worker. No framework, no build step. The PWA has a share target, an offline shell, light and dark themes, and maskable icons.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The boring parts that make it real
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clerk&lt;/strong&gt; for auth, with every query owner-scoped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB Atlas&lt;/strong&gt; for recipes, stored translations, and agent sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentry&lt;/strong&gt; spans around ingest, extraction, translation, chat, and agent calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; with Python, ffmpeg, and yt-dlp, plus a &lt;code&gt;render.yaml&lt;/code&gt; blueprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests&lt;/strong&gt; for the parser, ownership checks, and API behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What broke, and what I changed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instagram is hostile to scrapers.&lt;/strong&gt; I stopped pretending one path would work and built the cascade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Captions lie.&lt;/strong&gt; Some have no amounts; some have no method. Scribe handles the audio fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency is a feature.&lt;/strong&gt; I removed fake progress and made the status text honest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile needed to be first-class.&lt;/strong&gt; Share target, large tap targets, voice, install prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translations should not be a loading spinner.&lt;/strong&gt; Precompute them, then get out of the way.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Open innovation matters here because the economics of a small product like this are brutal if every useful action is a markup on a closed API.&lt;/p&gt;

&lt;p&gt;Gemma is open-weight. That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model choice is a config line, not a rewrite.&lt;/strong&gt; If a smaller model gets good enough, I can swap it and cut costs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A niche product can exist.&lt;/strong&gt; A tool for home cooks in 10 languages does not need venture scale to be useful. Open weights make the unit economics possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy and self-hosting are real options.&lt;/strong&gt; The Docker image can run with your own keys and your own database. Your recipes are not locked into my account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The long tail gets served.&lt;/strong&gt; Regional dishes, regional languages, and weird family recipes are exactly the cases closed models ignore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The glue is open source too.&lt;/strong&gt; yt-dlp, ffmpeg, Express, and the PWA platform do a lot of the unglamorous work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I still use hosted services — ElevenLabs, Atlas, Clerk, Sentry — and I am honest about that. But the intelligence at the center is open-weight and replaceable. That is the difference between building a product and renting one.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;I gave an early build to a close friend who cooks at home almost every day. I’ll keep his name private. He is exactly the person I built this for: saves reels, cooks from his phone, hates reading captions.&lt;/p&gt;

&lt;p&gt;He used it on the links he already had saved. Then he asked to keep using it.&lt;/p&gt;

&lt;p&gt;Would he pay? He already did. That is the only validation I trust right now.&lt;/p&gt;

&lt;p&gt;I am not calling Simmer a startup. It is one paid pilot, a lot of late nights, and a clear next step. That is enough to keep going.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is Simmer?
&lt;/h3&gt;

&lt;p&gt;An open-source AI recipe assistant that turns cooking links, reels, and pasted text into structured recipes, then helps you cook them hands-free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Simmer work with Instagram Reels?
&lt;/h3&gt;

&lt;p&gt;Yes, for public links. Instagram is the messiest source, so Simmer has caption, embed, metadata, and audio fallbacks rather than a single scraper.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it work with YouTube Shorts and TikTok?
&lt;/h3&gt;

&lt;p&gt;YouTube is a tested path. TikTok depends on the region and whether &lt;code&gt;yt-dlp&lt;/code&gt; can reach it, so it is not the primary path.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does it take?
&lt;/h3&gt;

&lt;p&gt;About 1–3 minutes for a public link. Audio transcription can take longer. Translations are prepared in the background, so switching languages is instant after the first pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it really open source?
&lt;/h3&gt;

&lt;p&gt;Yes. MIT licensed. The repo includes the server, PWA, Dockerfile, and setup docs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I self-host it?
&lt;/h3&gt;

&lt;p&gt;Yes. Run the Docker image with your own Gemma, MongoDB, Clerk, and ElevenLabs keys. The README has the details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why 10 languages?
&lt;/h3&gt;

&lt;p&gt;Because recipe videos travel across languages, but the useful instructions usually do not. Translations are stored per recipe, so the app stays fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — Gemma 4 is the extraction, chat, and translation engine. It is open-weight and swappable with an environment variable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render&lt;/strong&gt; — Simmer ships as a Docker service with a &lt;code&gt;render.yaml&lt;/code&gt; blueprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt; — Scribe for audio fallback, and the conversational agent for hands-free cook mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of MongoDB Atlas&lt;/strong&gt; — recipes, owner-scoped data, stored translations, and agent sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Sentry Agent Tracing&lt;/strong&gt; — spans around ingest, extraction, translation, chat, and agent calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Simmer is the first thing I have built that someone paid for before it was finished. That is the side-hustle test I care about.&lt;/p&gt;

&lt;p&gt;If you cook from reels, try it on the link that usually defeats you. If it breaks, send it to me. That is the next bug I want.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
