<?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>Forem: Kanak Waradkar</title>
    <description>The latest articles on Forem by Kanak Waradkar (@kakeroth).</description>
    <link>https://forem.com/kakeroth</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2765319%2F62f6bf3d-6a3d-4f3a-b5e4-83e952d677a1.jpg</url>
      <title>Forem: Kanak Waradkar</title>
      <link>https://forem.com/kakeroth</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kakeroth"/>
    <language>en</language>
    <item>
      <title>ChronoAgent: I Built an Agent That Reads My WhatsApp So I Stop Missing Deadlines</title>
      <dc:creator>Kanak Waradkar</dc:creator>
      <pubDate>Fri, 24 Apr 2026 06:07:39 +0000</pubDate>
      <link>https://forem.com/kakeroth/chronoagent-i-built-an-agent-that-reads-my-whatsapp-so-i-stop-missing-deadlines-34do</link>
      <guid>https://forem.com/kakeroth/chronoagent-i-built-an-agent-that-reads-my-whatsapp-so-i-stop-missing-deadlines-34do</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/openclaw-2026-04-16"&gt;OpenClaw Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I'm a second year computer engineering student.I often joke that my degree runs on Whatsapp.Often most students dont attend lectures but just see what someone has uploaded on whatsapp and do their work from there. I'm going to be honest with you about something embarrassing: I missed a lab submission last semester not because I didn't do the work, but because the deadline came through a WhatsApp group at 11 PM and I just didn't see it in time.&lt;/p&gt;

&lt;p&gt;The professor sent a reminder email three days before. Someone in the group chat forwarded it with "guys don't forget!!". Someone else sent a voice note I never opened. By the time I remembered, the portal was closed.&lt;/p&gt;

&lt;p&gt;This is not a unique experience. If you're a student in India — or honestly anyone whose professional life runs partially through WhatsApp — you know that deadlines don't live in one place. They're fragmented across group chats, Gmail threads, DMs, and the occasional Instagram message from a friend who remembered you hadn't registered for something yet.&lt;/p&gt;

&lt;p&gt;The "fix" people suggest is: "just use Google Calendar." Sure. Do you manually create an event every time someone texts you about something? I don't. Nobody does.&lt;/p&gt;

&lt;p&gt;So here comes our hero of the story OpenClaw.&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%2Fmedia1.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExMHo0ZXhqOWU0azAycHI4NW9sZjlwN2xkenB5aW5sYnhpODA3dTNpdiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2Fyr7n0u3qzO9nG%2Fgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img width="440" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia1.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExMHo0ZXhqOWU0azAycHI4NW9sZjlwN2xkenB5aW5sYnhpODA3dTNpdiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2Fyr7n0u3qzO9nG%2Fgiphy.gif" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used OpenClaw
&lt;/h2&gt;

&lt;p&gt;I've played with n8n, with custom Python cron scripts, with Zapier. None of them had native WhatsApp access without either paying for a business API (which requires a separate phone number and a whole approval process) or running a fragile Selenium scraper.&lt;/p&gt;

&lt;p&gt;OpenClaw has WhatsApp built into its Gateway via Baileys. One command, one QR scan. Done. The agent immediately has access to every message I receive, treated as a first-class channel.&lt;/p&gt;

&lt;p&gt;The other thing that made OpenClaw the right fit was &lt;strong&gt;Standing Orders&lt;/strong&gt;. This is not a feature you'd expect to care about until you actually use it. The idea is simple: you write a file called &lt;code&gt;AGENTS.md&lt;/code&gt; in your workspace and the agent reads it every session. You define programs — "here's what you're authorized to do, here's when to do it, here's when to stop and ask me."&lt;/p&gt;

&lt;p&gt;For OpenClaw, the Standing Order looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Program: WhatsApp Deadline Monitor&lt;/span&gt;

&lt;span class="gs"&gt;**Authority:**&lt;/span&gt; Read all inbound WhatsApp messages. Extract and store deadline/event data.
Send review messages back to the user for ambiguous extractions.
&lt;span class="gs"&gt;**Trigger:**&lt;/span&gt; Every inbound WhatsApp message
&lt;span class="gs"&gt;**Approval gate:**&lt;/span&gt; Auto-write calendar for confidence ≥ 0.80. Request approval below that.
&lt;span class="gs"&gt;**Escalation:**&lt;/span&gt; If extraction fails 3 times in a row, alert me and pause.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single file is why it is an &lt;em&gt;agent&lt;/em&gt; and not a script. It has defined scope, defined escalation rules, defined approval gates. It knows when to act and when to ask. I wrote those rules once and now they govern every message that comes through, forever, without me thinking about it again.&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%2Fmedia3.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExejlmeTBkaXJoZ2o1bnlnbmo1YmRmNHExbHBxcDI5c2pvencxbm83ZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2Fma4msCmcHpAoBLU6hl%2Fgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img width="324" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia3.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExejlmeTBkaXJoZ2o1bnlnbmo1YmRmNHExbHBxcDI5c2pvencxbm83ZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2Fma4msCmcHpAoBLU6hl%2Fgiphy.gif" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;So I built ChronoAgent.&lt;/p&gt;

&lt;p&gt;ChronoAgent runs in the background on my laptop as an OpenClaw Gateway daemon. It has a WhatsApp channel connected (OpenClaw supports this natively through Baileys — no external service, no webhook nonsense, just scan a QR code). Every message that comes in gets silently processed. If it contains a deadline, a due date, a meeting, an exam — it gets extracted, deduplicated, and written to Google Calendar automatically.&lt;/p&gt;

&lt;p&gt;I don't prompt it. I don't open a chat window. It just runs.&lt;/p&gt;

&lt;p&gt;When it adds something to my calendar, it sends me a message back: "📅 Added: Assignment 3 submission on April 28, 11:59 PM". I can reply NO to undo it. That's the entire user interaction for 80% of cases.&lt;/p&gt;

&lt;p&gt;For things it's less sure about — "sometime next week we should meet bro" — it holds the event in a pending queue and asks me to confirm with a YES or NO reply. No calendar write until I say so.&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.amazonaws.com%2Fuploads%2Farticles%2Fwnn5ltjdoer2eftr9h08.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.amazonaws.com%2Fuploads%2Farticles%2Fwnn5ltjdoer2eftr9h08.png" alt="IT'S ALIVE" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source code and setup instructions:&lt;/strong&gt; &lt;a href="https://github.com/Labreo/openclaw-calendar-agent" rel="noopener noreferrer"&gt;github.com/Labreo/openclaw-calendar-agent&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Here's the mistake I almost made: trying to have the LLM do everything.&lt;/p&gt;

&lt;p&gt;My first draft had the LLM reading messages, extracting dates, checking the calendar for duplicates, deciding whether to write, writing the event, formatting the confirmation message — all in one big chain of reasoning.&lt;/p&gt;

&lt;p&gt;It was slow. It was expensive. And it hallucinated duplicates constantly.&lt;/p&gt;

&lt;p&gt;The version that actually works is much dumber-looking on the surface, but it's solid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incoming Message
    │
    ▼
[Python: normalize to standard envelope]
{ source, sender, timestamp, raw_text }
    │
    ▼
[LLM: ONLY job is translation]
Input: raw text + today's date
Output: JSON array of extracted events with confidence scores
    │
    ▼
[Python: resolve relative dates]
"next Friday" → 2026-04-25T00:00:00
    │
    ▼
[Python: deduplicate]
Fuzzy title match + date proximity + semantic similarity
    │
    ▼
[Confidence router]
≥ 0.80 → write to calendar, notify me
0.50–0.79 → pending queue, ask me
&amp;lt; 0.50 → silent discard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM's job is exactly one thing: read messy human text and output clean structured JSON. That's it. Every other decision in the pipeline is deterministic Python.&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.amazonaws.com%2Fuploads%2Farticles%2Fjn3kfw4jmuguanbl23si.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.amazonaws.com%2Fuploads%2Farticles%2Fjn3kfw4jmuguanbl23si.png" alt="Architecture Diagram" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The extraction prompt that actually worked
&lt;/h3&gt;

&lt;p&gt;Getting the LLM to reliably output JSON (and &lt;em&gt;only&lt;/em&gt; JSON) took more iteration than I expected. The trick was treating the model like a data parser in the system prompt, not like a conversationalist:&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;EXTRACTION_SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a deadline and event extraction engine.
Your ONLY output is a valid JSON array. No prose. No markdown. No explanation.

Given a message, extract every actionable deadline, due date, meeting, or event.
For each item output:
{
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: string,
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: string,       // verbatim from the message
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_iso&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: string|null,  // resolved ISO 8601 if possible, else null
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: float,      // 0.0 to 1.0
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source_quote&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: string,   // the exact fragment that contains the deadline
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: string      // deadline | meeting | exam | submission | event | other
}

Confidence guide:
  1.0  — explicit date + time + clear action
  0.85 — explicit date, no time
  0.70 — relative date that can be resolved
  0.55 — vague but likely actionable
  0.30 — might be an event, very unclear
  0.10 — references a past deadline

If NO events found, return: []
Today&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s date is injected in the user message.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;source_quote&lt;/code&gt; field was an afterthought that turned out to be the most useful field in the whole schema. Every Google Calendar event created by ChronoAgent has the original message fragment in its description. When I look at a calendar event three weeks later and don't remember what it's about, I can see "Original: 'bro the quiz is moved to Thursday 10am right?' — Source: WhatsApp, Sender: Abdullah". That's enough context.&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.amazonaws.com%2Fuploads%2Farticles%2Fo1g24ilk0nrtmcuxmfr9.webp" 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.amazonaws.com%2Fuploads%2Farticles%2Fo1g24ilk0nrtmcuxmfr9.webp" alt="Reading Emails" width="717" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The deduplication problem
&lt;/h3&gt;

&lt;p&gt;This was the hardest part of the whole project by a significant margin.&lt;/p&gt;

&lt;p&gt;The naive approach: ask the LLM "is this event already in my calendar?" Tried it. Terrible. Slow, expensive, and the model would confidently say "no duplicate found" when there obviously was one.&lt;/p&gt;

&lt;p&gt;The approach that works is a &lt;strong&gt;2-out-of-3 vote&lt;/strong&gt; between three deterministic checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fuzzy title match&lt;/strong&gt; (Levenshtein ratio ≥ 0.85)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Date proximity&lt;/strong&gt; (within ±24 hours)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic similarity&lt;/strong&gt; (cosine similarity using a local &lt;code&gt;sentence-transformers&lt;/code&gt; model, threshold 0.80)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_duplicate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddings_cache&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;votes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="c1"&gt;# Check 1: title similarity
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;lev_ratio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&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="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;votes&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="c1"&gt;# Check 2: date proximity
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;candidate&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_iso&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;existing&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_iso&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_iso&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_iso&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# 24 hours
&lt;/span&gt;            &lt;span class="n"&gt;votes&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="c1"&gt;# Check 3: semantic similarity
&lt;/span&gt;    &lt;span class="n"&gt;vec_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_or_compute_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;embeddings_cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;vec_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_or_compute_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;embeddings_cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;cosine_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vec_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vec_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.80&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;votes&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;votes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Assignment 3 due Friday" and "A3 submission end of week" — different titles, so Levenshtein fails. But they're semantically similar and the dates are within 24 hours, so they correctly merge as duplicates.&lt;/p&gt;

&lt;p&gt;The semantic model I used (&lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt;) is 80MB and runs locally. No API call, no cost, ~10ms inference time. The LLM is completely out of the loop for dedup.&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%2Fmedia3.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExbTBidXdiZ281NGJ6b3hjcG53YW82dzgwNGo4am4zNTB2MWFyemhrdiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FeQACuze30PkNiS1eb7%2Fgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img width="480" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia3.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExbTBidXdiZ281NGJ6b3hjcG53YW82dzgwNGo4am4zNTB2MWFyemhrdiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FeQACuze30PkNiS1eb7%2Fgiphy.gif" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The 3 AM quota problem (a real thing that happened)
&lt;/h3&gt;

&lt;p&gt;Midway through building this, I burnt through my initial API credits testing the extraction pipeline on WhatsApp group traffic. A college group chat is... a lot of messages. Most of them "ok", "haha", "send notes pls" — but the extraction call fires on all of them before it knows they're non-events.&lt;/p&gt;

&lt;p&gt;I had two options: add pre-filtering, or switch to a cheaper model.&lt;/p&gt;

&lt;p&gt;I did both. Added a simple length and keyword pre-filter that drops messages under 15 words with no date-adjacent terms before they even hit the LLM. Dropped API usage by about 70% immediately.&lt;/p&gt;

&lt;p&gt;Then switched from Claude Sonnet to Gemini 2.5 Flash for the extraction step. Flash is significantly cheaper for this kind of structured output task and the JSON reliability was equivalent in my testing. I still use Claude for anything that requires more complex reasoning — the confidence routing logic and the digest generation — but for the high-volume extraction pass, Flash works well.&lt;/p&gt;

&lt;p&gt;The lesson: the best agentic systems aren't the ones throwing the strongest model at every task. They're the ones with smart orchestration about &lt;em&gt;when&lt;/em&gt; to call what.&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%2Fmedia2.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExcnBnMTJla3NxcHVhd2J1bWNieHAxcmZvYmNjc2JreWZncGxxb2JmYSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2F3orifdO6eKr9YBdOBq%2Fgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img width="480" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia2.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExcnBnMTJla3NxcHVhd2J1bWNieHAxcmZvYmNjc2JreWZncGxxb2JmYSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2F3orifdO6eKr9YBdOBq%2Fgiphy.gif" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What "passive operation" actually means in practice
&lt;/h3&gt;

&lt;p&gt;The phrase "runs in the background" is easy to say and actually kind of hard to build well.&lt;/p&gt;

&lt;p&gt;OpenClaw's Gateway is a daemon — you run &lt;code&gt;openclaw onboard --install-daemon&lt;/code&gt; and it sets up a launchd/systemd service that starts automatically on login and stays running. The WhatsApp channel maintains a persistent Baileys session. Cron jobs handle the Gmail polling every 4 hours.&lt;/p&gt;

&lt;p&gt;The Standing Order hook fires on every inbound WhatsApp message without any scheduler. The agent just... responds to events. It's not polling. It's not a loop. It's closer to how a web server handles requests — always listening, processes when something arrives, goes quiet otherwise.&lt;/p&gt;

&lt;p&gt;The Gmail part is a Python script (&lt;code&gt;ingest_email.py&lt;/code&gt;) that the OpenClaw cron calls every 4 hours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw cron add &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; email-ingestion &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cron&lt;/span&gt; &lt;span class="s2"&gt;"0 */4 * * *"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--timeout-seconds&lt;/span&gt; 120 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"Execute email ingestion per standing orders. Run ingest_email.py, process queue, run extraction and dedup on each envelope, route by confidence, write calendar entries, report summary."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cron message references the Standing Order rather than duplicating the logic. The agent reads its AGENTS.md, knows the full procedure, and executes it. I don't need to write a long prompt into the cron command because the authority is already defined.&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%2Fmedia4.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExazd0aGs3azlwNXgyMWNtNDhsb3pqdXZ6a3EyYW14Zjk2ZmpoeTljYyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FBn9AqquGZo7lqhJqHc%2Fgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img width="538" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia4.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExazd0aGs3azlwNXgyMWNtNDhsb3pqdXZ6a3EyYW14Zjk2ZmpoeTljYyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FBn9AqquGZo7lqhJqHc%2Fgiphy.gif" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'd build next if I had more time
&lt;/h3&gt;

&lt;p&gt;The biggest gap right now is thread context. When someone says "bro don't forget about the thing on Friday" — that's a 0.3 confidence extraction at best. But if I had the last 5 messages of that WhatsApp thread, I'd probably know what "the thing" is. &lt;/p&gt;

&lt;p&gt;OpenClaw's session history tools make this possible but I didn't have time to implement it cleanly before the deadline. It's the next feature.&lt;/p&gt;

&lt;p&gt;The other thing is cross-source entity resolution. Right now if the same event appears in email and WhatsApp, the dedup engine usually catches it. But it runs independently per message — there's no weekly "let me look at everything I've collected and find clusters" pass. I have a consolidation script written but not wired into the cron yet.&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%2Fmedia1.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExcGozemM5NnEwcXhyeWVmcHE2b2ZpdWFqcmF6ZTQ5YmloMnJoazVxeiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2F3o6wNXmReHb4x5bIwU%2Fgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img width="480" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia1.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExcGozemM5NnEwcXhyeWVmcHE2b2ZpdWFqcmF6ZTQ5YmloMnJoazVxeiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2F3o6wNXmReHb4x5bIwU%2Fgiphy.gif" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ClawCon Michigan
&lt;/h2&gt;

&lt;p&gt;I didn't attend ClawCon Michigan this time around, but building ChronoAgent has made me want to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest summary
&lt;/h2&gt;

&lt;p&gt;ChronoAgent is not a product. It's a personal tool I built because I was genuinely failing at calendar management and the existing solutions didn't work for how I actually communicate (mostly WhatsApp, some email, basically no structured calendar input).&lt;/p&gt;

&lt;p&gt;OpenClaw made the WhatsApp part possible without fighting through Business API approvals. The Standing Orders made the "passive, always-on, doesn't need prompting" part possible without writing a custom daemon. The &lt;code&gt;exec&lt;/code&gt; tool made it easy to keep the heavy lifting in plain Python scripts that I can test independently.&lt;/p&gt;

&lt;p&gt;The thing I keep coming back to from this project: the right job for the LLM in an agentic system is usually much smaller than you initially think. Translation, intent recognition, natural language output — yes. Calendar math, deduplication logic, date resolution — no. The moment I moved those out of the LLM and into deterministic code, the whole system became faster, cheaper, and more reliable.&lt;/p&gt;

&lt;p&gt;Since I set it up, I've had zero missed deadline incidents. That's the only metric that matters.&lt;/p&gt;

</description>
      <category>openclawchallenge</category>
      <category>devchallenge</category>
      <category>openclaw</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I Hijacked YouTube Music's DOM to Build a Custom Mini-Player 🎵</title>
      <dc:creator>Kanak Waradkar</dc:creator>
      <pubDate>Thu, 23 Apr 2026 01:58:18 +0000</pubDate>
      <link>https://forem.com/kakeroth/how-i-hijacked-youtube-musics-dom-to-build-a-custom-mini-player-3m23</link>
      <guid>https://forem.com/kakeroth/how-i-hijacked-youtube-musics-dom-to-build-a-custom-mini-player-3m23</guid>
      <description>&lt;h2&gt;
  
  
  The Challenge: Dealing with Aggressive UI "Pruning"
&lt;/h2&gt;

&lt;p&gt;YouTube Music is built with a highly responsive engine. When you narrow the window to a "mini" size—essential for a browser extension mini-player—YTM starts aggressively pruning the interface. One of the first things to go are the Like and Dislike buttons. They aren't just hidden; they are often completely stripped from the active layout to save space.&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.amazonaws.com%2Fuploads%2Farticles%2Fkerb19vqzzk3lje9dpvd.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.amazonaws.com%2Fuploads%2Farticles%2Fkerb19vqzzk3lje9dpvd.png" alt="Previous interface of the YouTube Music Player" width="712" height="1128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a multitasking user, having to expand the player just to "Like" a song is a major friction point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Strategy: Native Reparenting (DOM Hijacking)
&lt;/h2&gt;

&lt;p&gt;Initially, I tried simulating keyboard shortcuts (&lt;code&gt;Shift + =&lt;/code&gt;), but found it unreliable when the window lost focus. I also tried creating custom buttons, but syncing their state (Blue for liked, Red for disliked) with YTM's internal player was complex and prone to breaking.&lt;/p&gt;

&lt;p&gt;The solution? &lt;strong&gt;Native Reparenting.&lt;/strong&gt; Instead of building new buttons, I "stole" the official ones and moved them into my own persistent UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Locating the Native Renderer
&lt;/h3&gt;

&lt;p&gt;Even when hidden, the native &lt;code&gt;ytmusic-like-button-renderer&lt;/code&gt; component often persists in the DOM (hidden in the background player page). We simply need to find it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nativeRenderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ytmusic-like-button-renderer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Creating a Custom Pill Container
&lt;/h3&gt;

&lt;p&gt;To give the buttons a modern, floating feel, I created a custom "Pill Bar" with glassmorphism effects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ytm-pill-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: The "Hijack"
&lt;/h3&gt;

&lt;p&gt;This is the magic line. By appending the native renderer to our new container, we move the actual functional component—including its click handlers and state management—out of its original location and into our floating UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;nativeRenderer&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;nativeRenderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentElement&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Clear any existing content to prevent duplicates&lt;/span&gt;
    &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nativeRenderer&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;h3&gt;
  
  
  Step 4: Overriding the Style Engine
&lt;/h3&gt;

&lt;p&gt;Because YTM's CSS still thinks these buttons should be hidden in narrow windows, we have to use high-priority &lt;code&gt;!important&lt;/code&gt; rules to force them back into visibility.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#ytm-pill-container&lt;/span&gt; &lt;span class="nt"&gt;ytmusic-like-button-renderer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;visible&lt;/span&gt; &lt;span class="cp"&gt;!important&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;
  
  
  The Result: A Robust, Draggable UI
&lt;/h2&gt;

&lt;p&gt;By using the native components, we get 100% reliable Like/Dislike functionality. We even added draggability so users can move the pill bar anywhere in the window.&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.amazonaws.com%2Fuploads%2Farticles%2Fgihjnye6hof959dolqbm.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.amazonaws.com%2Fuploads%2Farticles%2Fgihjnye6hof959dolqbm.png" alt="New movable floating pill" width="754" height="1336"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/Labreo/ytm-miniplayer" rel="noopener noreferrer"&gt;Labreo/ytm-miniplayer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install Firefox:&lt;/strong&gt; &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/ytm-mini-mode/" rel="noopener noreferrer"&gt;YTM Mini Mode&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chrome:&lt;/strong&gt; (Coming Soon)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Happy coding!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Tracking ML Experiments with MLflow: A Simple Guide for Beginners</title>
      <dc:creator>Kanak Waradkar</dc:creator>
      <pubDate>Thu, 10 Jul 2025 23:36:41 +0000</pubDate>
      <link>https://forem.com/kakeroth/tracking-ml-experiments-with-mlflow-a-simple-guide-for-beginners-4o38</link>
      <guid>https://forem.com/kakeroth/tracking-ml-experiments-with-mlflow-a-simple-guide-for-beginners-4o38</guid>
      <description>&lt;p&gt;Originally published on &lt;a href="https://diving-into-mlopsbeginners-guide.hashnode.dev/tracking-ml-experiments-with-mlflow-a-simple-guide-for-beginners" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction:
&lt;/h2&gt;

&lt;p&gt;This blog draws inspiration from the excellent &lt;a href="https://youtu.be/6ngxBkx05Fs" rel="noopener noreferrer"&gt;MLflow tutorial by CodeBasics&lt;/a&gt;, which clearly demonstrates the core concepts we will be discussing here. If you are looking for a more detailed or visual walk through, I highly recommend checking it out.&lt;/p&gt;

&lt;p&gt;This post is written from the perspective of a beginner and aims to offer a more hands-on, less theoretical explanation of MLflow, based on my own experience implementing it in a real project. Think of it as a beginner learning out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is MLflow?
&lt;/h2&gt;

&lt;p&gt;MLflow is an open-source MLOps tool that helps you track, log, and manage everything involved in machine learning experiments including metrics, parameters, models, and other useful artifacts.&lt;/p&gt;

&lt;p&gt;It is especially useful when you are trying out different models or hyper parameters and need a way to compare them without losing track. Instead of manually noting results, MLflow stores everything in one place, helping you stay organized and productive.&lt;/p&gt;

&lt;p&gt;For example, in my Titanic survival prediction project, I used MLflow to log different models such as Logistic Regression and Random Forest, track accuracy scores, and compare results using the MLflow UI. This made it easy to identify the best-performing model for deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to install and setup MLflow?
&lt;/h2&gt;

&lt;p&gt;It is actually pretty straightforward to install into MLflow into your python notebook.Just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install mlflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now to setup MLflow we will be using my titanic-model code as a reference which can be located from my &lt;a href="https://github.com/Labreo/titanic-ml" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.For this example, I had trained and compared three different models: Logistic Regression, Random Forest, and Gradient Boosting Classifier.&lt;/p&gt;

&lt;p&gt;Here's a simplified version of the model setup:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.linear_model&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LogisticRegression&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.ensemble&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RandomForestClassifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GradientBoostingClassifier&lt;/span&gt;

&lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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;Logistic Regression&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;class_weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;random_state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8888&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;solver&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;lbfgs&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;max_iter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nc"&gt;LogisticRegression&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Random Forest&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n_estimators&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;random_state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nc"&gt;RandomForestClassifier&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gradient Boosting Classifier&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n_estimators&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learning_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_depth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;random_state&lt;/span&gt;&lt;span class="sh"&gt;"&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="nc"&gt;GradientBoostingClassifier&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;Now we integrate MLflow to log the model parameters and performance metrics for each 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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mlflow&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mlflow.sklearn&lt;/span&gt;

&lt;span class="c1"&gt;# Set the tracking URI (for local use)
&lt;/span&gt;&lt;span class="n"&gt;mlflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_tracking_uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:5000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Set the experiment name
&lt;/span&gt;&lt;span class="n"&gt;mlflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_experiment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accuracy Model v3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;element&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="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reports&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Assume this contains the classification report for the model
&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;mlflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;mlflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_params&lt;/span&gt;&lt;span class="p"&gt;(&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;mlflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_metrics&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;accuracy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;accuracy&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;recall_class_1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;report&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;recall&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;recall_class_0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&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;recall&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;f1_score_macro&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;macro avg&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;f1-score&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="c1"&gt;# Log the trained model
&lt;/span&gt;        &lt;span class="n"&gt;mlflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sklearn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;registered_model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To view your runs visually, launch the MLflow tracking UI with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mlflow ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open your browser at &lt;a href="http://127.0.0.1:5000" rel="noopener noreferrer"&gt;&lt;code&gt;http://127.0.0.1:5000&lt;/code&gt;&lt;/a&gt;. You will see a dashboard that shows all your logged experiments, parameters, metrics, and models.&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.amazonaws.com%2Fuploads%2Farticles%2F6gpznqe8dbqau6d9gy0l.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.amazonaws.com%2Fuploads%2Farticles%2F6gpznqe8dbqau6d9gy0l.png" alt="MLflow UI" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Logging Parameters, Metrics, and Models
&lt;/h2&gt;

&lt;p&gt;MLflow provides a clean API to track everything that matters in your experiments.&lt;/p&gt;

&lt;p&gt;Here are the three main functions you will use:&lt;/p&gt;

&lt;h3&gt;
  
  
  Logging Parameters
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mlflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learning_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mlflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n_estimators&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are the hyper parameters of your model that you might want to compare across runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logging Metrics
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt; &lt;span class="n"&gt;mlflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accuracy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.875&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can log accuracy, precision, recall, or any other custom metric you calculate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logging the Model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mlflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sklearn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&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;This saves your trained model in a format you can reload or even serve later.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Gets Stored and Where?
&lt;/h3&gt;

&lt;p&gt;Once you run your experiment, MLflow stores everything inside a folder called &lt;code&gt;mlruns&lt;/code&gt; in your project directory. Each experiment is given a unique ID, and inside that folder, you will find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A &lt;code&gt;params&lt;/code&gt; folder containing your logged parameters&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;code&gt;metrics&lt;/code&gt; folder for evaluation scores&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An &lt;code&gt;artifacts&lt;/code&gt; folder which includes the saved model&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can compare runs side by side in the UI, which makes model selection and debugging much easier.These all can be then seen on the web application and MLflow will do most of the heavy lifting with management and data consolidation.Feel free to mess around with the UI and draw your own conclusions.&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.amazonaws.com%2Fuploads%2Farticles%2Fc56tvuqd8okdh38qqpcy.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.amazonaws.com%2Fuploads%2Farticles%2Fc56tvuqd8okdh38qqpcy.png" alt="MLflow Comparison" width="800" height="826"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Things I Learned (or Struggled With)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Like any beginner working with a new tool, I ran into a few bumps while setting up MLflow and learned a lot in the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Confusion About MLflow Runs
&lt;/h3&gt;

&lt;p&gt;At first, I didn’t fully understand what a “run” was in MLflow. I was running multiple models, but everything was showing up under the same run or being overwritten. I realized I had forgotten to call &lt;code&gt;mlflow.start_run()&lt;/code&gt; with a unique &lt;code&gt;run_name&lt;/code&gt;.This could possibly fix it for you:&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;with&lt;/span&gt; &lt;span class="n"&gt;mlflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gradient Boosting&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  MLflow UI Not Launching
&lt;/h3&gt;

&lt;p&gt;I tried running &lt;code&gt;mlflow ui&lt;/code&gt; and nothing happened. It turned out I had a port conflict on &lt;code&gt;5000&lt;/code&gt;, as another local server was already running.But it still wouldn’t run after making these changes.Using this command helped me launched it for the first time after which it launched normally using &lt;code&gt;mlflow ui&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mlflow server \
  --backend-store-uri sqlite:///mlflow.db \
  --default-artifact-root ./artifacts \
  --host 0.0.0.0 \
  --port 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you should be able to access everything as normal.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Next Steps&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now that I’ve successfully set up MLflow and tracked multiple models in my Titanic prediction project, here’s what I plan to learn next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DVC (Data Version Control):&lt;/strong&gt; To manage data and model files across versions and collaborators.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prefect:&lt;/strong&gt; To automate my training pipeline and possibly run it on a schedule.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m also working on a blog series covering each of these topics as I learn them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to Follow Along?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
You can check out my code and future updates here:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/Labreo/titanic-ml" rel="noopener noreferrer"&gt;https://github.com/Labreo/titanic-ml&lt;/a&gt;&lt;br&gt;&lt;br&gt;
I also post progress daily on Twitter:&lt;br&gt;&lt;br&gt;
&lt;a href="https://x.com/Kaker0th" rel="noopener noreferrer"&gt;https://x.com/Kaker0th&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're just starting with MLOps too, feel free to reach out or share what you're building. Let's learn and grow together!&lt;/p&gt;

</description>
      <category>mlflow</category>
      <category>mlops</category>
      <category>machinelearning</category>
      <category>python</category>
    </item>
  </channel>
</rss>
