<?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: Andy Khang</title>
    <description>The latest articles on DEV Community by Andy Khang (@ak404).</description>
    <link>https://dev.to/ak404</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%2F829921%2Facc9d87f-b717-4946-8f36-4b3bce3d3655.png</url>
      <title>DEV Community: Andy Khang</title>
      <link>https://dev.to/ak404</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ak404"/>
    <language>en</language>
    <item>
      <title>RepoRetro: Ghost in the Repo — Unearthing Dead Code &amp; Honoring Departed Contributors with Gemma &amp; Entire</title>
      <dc:creator>Andy Khang</dc:creator>
      <pubDate>Fri, 30 Oct 2026 17:18:07 +0000</pubDate>
      <link>https://dev.to/ak404/reporetro-ghost-in-the-repo-unearthing-dead-code-honoring-departed-contributors-with-gemma--1lpe</link>
      <guid>https://dev.to/ak404/reporetro-ghost-in-the-repo-unearthing-dead-code-honoring-departed-contributors-with-gemma--1lpe</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge: Week 4 ("Ghost in the Machine")&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Do not remove a fence until you know why it was put up in the first place."&lt;/em&gt;&lt;br&gt;&lt;br&gt;
— G.K. Chesterton (Chesterton's Fence)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every seasoned codebase is haunted.&lt;/p&gt;

&lt;p&gt;In every mature repository, there are functions with zero active callers, orphaned billing modules preserved across three refactors, commented-out blocks that everyone is terrified to delete, and &lt;code&gt;TODO (Elena 2023)&lt;/code&gt; comments left by engineers who haven't committed in three years. &lt;/p&gt;

&lt;p&gt;When modern developers encounter these artifacts, we face a dilemma. Traditional linters blindly shout: &lt;em&gt;"Unused export! Delete it!"&lt;/em&gt; But seasoned engineers know the danger of heedless deletion: what if that function was an edge-case guard against a banking outage? What if that unreferenced class was an incomplete prototype an agent generated during a frantic sprint?&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Hacktoberfest 2026 Week 4 ("Ghost in the Machine")&lt;/strong&gt;, I built &lt;strong&gt;RepoRetro&lt;/strong&gt;: an open-source repository archaeologist that doesn't just treat dead code like garbage to be purged. In the Mexican tradition of &lt;strong&gt;Día de los Muertos&lt;/strong&gt;, RepoRetro builds a digital &lt;strong&gt;Ofrenda&lt;/strong&gt; for your repository. It unearths abandoned code, calculates a multi-factor &lt;strong&gt;Haunt Score (0–100)&lt;/strong&gt;, queries past AI agent sessions via &lt;strong&gt;Entire&lt;/strong&gt;, and uses &lt;strong&gt;Google's open-weight Gemma&lt;/strong&gt; to reconstruct the historical rationale before delivering a &lt;strong&gt;Chesterton's Fence verdict&lt;/strong&gt;: is this a &lt;em&gt;Load-Bearing Relic&lt;/em&gt;, &lt;em&gt;Unfinished Work&lt;/em&gt;, or &lt;em&gt;Safe to Exorcise&lt;/em&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%2F7q63oinm6s17w1hu0m5k.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%2F7q63oinm6s17w1hu0m5k.png" alt="RepoRetro: Ghost in the Repo Banner" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;RepoRetro&lt;/strong&gt; is a dual-interface repository intelligence tool:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Interactive Candlelit Web Dashboard&lt;/strong&gt;: A rich Día de los Muertos themed interface with cempasúchil marigold glowing accents, live scan progress, graveyard statistics, and a dedicated &lt;strong&gt;Ofrenda Tab&lt;/strong&gt; honoring departed contributors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal CLI&lt;/strong&gt;: A fast command-line scanner (&lt;code&gt;npx reporetro scan .&lt;/code&gt; or &lt;code&gt;npx tsx src/cli.ts scan&lt;/code&gt;) that prints formatted archaeological reports directly to your stdout.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🕯️ REPORETRO: GHOST IN THE REPO ARCHAEOLOGY REPORT
═══════════════════════════════════════════════════════════════════════
Repository: haunted-repo
Scanned At: 2026-10-27T14:37:09.967Z
Files: 3 | Commits: 4 | Relics: 7 | Departed Contributors: 2
Average Haunt Score: 94 / 100 👻
═══════════════════════════════════════════════════════════════════════

🪦 THE GRAVEYARD (7 Relics Discovered):
  [UNREFERENCED EXPORT] legacyRsaDecrypt (src/auth/crypto.ts:7-12)
    Haunt Score: 95/100 | Author: Elena Rostova (Departed 42.3 mos ago)
    Commit: 3fd5802 (2023-04-12)
    Why: Introduced by Elena Rostova for v1.2 spec. Callers refactored away.
    Verdict: ⚰️ SAFE TO EXORCISE
    Rationale: Confirmed orphaned with zero internal importers.

  [UNREFERENCED EXPORT] CarrierPigeon (src/telemetry/carrier-pigeon.ts:2-7)
    Haunt Score: 95/100 | Author: Marcus Vance (Departed 29.3 mos ago)
    Commit: 06c86da (2024-05-10)
    Why: Generated during AI session cp-telemetry-99a8e1.
    Entire Session Match: Prompt "Prototype an offline event buffering class CarrierPigeon..."
    Agent Reasoning: Marcus planned to wire this into telemetry sink, but transitioned off.
    Verdict: 🚧 UNFINISHED WORK
    Rationale: File contains functional logic that was abandoned mid-architecture.

🏵️ THE OFRENDA (Honoring Departed Contributors):
  🕯️ Elena Rostova (elena@heritage-labs.org)
     Active: 2023-04-12 to 2023-04-12 | Departed: 42.3 months ago
     "Though inactive for 42 months, their architectural footprint endures across 
     the foundation of this repository."
  🕯️ Marcus Vance (marcus@cloudbridge.dev)
     Active: 2024-02-15 to 2024-05-10 | Departed: 29.3 months ago
     "Though inactive for 29 months, their architectural footprint endures across 
     the foundation of this repository."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Demo &amp;amp; Visual Tour
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Code Graveyard &amp;amp; Chesterton's Fences
&lt;/h3&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%2Faag4k3fykmor4s87cx8p.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%2Faag4k3fykmor4s87cx8p.png" alt="The Code Graveyard Dashboard" width="799" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RepoRetro serves an altar-inspired UI with real-time analytics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Haunt Score Meter (0–100)&lt;/strong&gt;: Combines unreferenced export counts, git decay time, author departure delta, and commented line density into a unified index.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Graveyard View&lt;/strong&gt;: Lists every dead code relic with expandable commit history, author attribution, and correlated agent prompt history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chesterton's Fence Badges&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;🕯️ &lt;strong&gt;Load-Bearing Relic&lt;/strong&gt;: Keep and document (e.g., legacy banking fallback).&lt;/li&gt;
&lt;li&gt;🪦 &lt;strong&gt;Safe to Lay to Rest&lt;/strong&gt;: Confirmed dead code ready for clean removal.&lt;/li&gt;
&lt;li&gt;🌼 &lt;strong&gt;Unfinished Ghost Work&lt;/strong&gt;: Incomplete feature branch ready to resurrect.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Private Repository Scanning &amp;amp; Zero-Persistence Security
&lt;/h3&gt;

&lt;p&gt;Real enterprise repositories and legacy codebases almost never live in public git URLs. To make RepoRetro viable for real teams, it supports user-added credentials across both the Web UI and CLI:&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%2Ffkm5tu5x8x3kjrx0pyhe.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%2Ffkm5tu5x8x3kjrx0pyhe.png" alt="Private Repository Credentials Panel" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Personal Access Tokens (PAT)&lt;/strong&gt;: Works with GitHub (classic and fine-grained PATs), GitLab (&lt;code&gt;read_repository&lt;/code&gt;), and Bitbucket App Passwords.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSH Private Keys&lt;/strong&gt;: Paste OpenSSH / PEM keys directly or load key files from your filesystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent URL Adaptation&lt;/strong&gt;: Automatically converts HTTPS URLs to SSH when an SSH key is given, and SSH (&lt;code&gt;git@...&lt;/code&gt;) URLs to HTTPS when a token is provided.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Storage Ephemeral Security&lt;/strong&gt;: Credentials are kept strictly in-memory during cloning. They are never written to disk or the SQLite database. Git origins are automatically sanitized, and any credential strings in errors or logs are replaced with &lt;code&gt;***&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment Variable Fallbacks&lt;/strong&gt;: Server deployments automatically detect &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;, &lt;code&gt;GIT_TOKEN&lt;/code&gt;, &lt;code&gt;GIT_USERNAME&lt;/code&gt;, &lt;code&gt;GIT_PASSWORD&lt;/code&gt;, or &lt;code&gt;GIT_SSH_KEY&lt;/code&gt; from &lt;code&gt;.env&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. The Día de los Muertos Ofrenda Tab
&lt;/h3&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%2Fe2vw635omj30dlq0o8yh.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%2Fe2vw635omj30dlq0o8yh.png" alt="Día de los Muertos Ofrenda Honoring Departed Contributors" width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Ofrenda Tab&lt;/strong&gt; honors long-gone contributors who haven't committed in over 6 months:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Computes their first and last commit eras, total commits given, and surviving lines of code still in &lt;code&gt;HEAD&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Generates poetic tributes celebrating their foundational craft.&lt;/li&gt;
&lt;li&gt;Transmutes the cold finger-pointing of &lt;code&gt;git blame&lt;/code&gt; into a community celebration of human craftsmanship.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. Archaeological Sessions &amp;amp; Entire Checkpoints
&lt;/h3&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%2Fnkyw2mafgai7cscft8z4.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%2Fnkyw2mafgai7cscft8z4.png" alt="Archaeological Sessions &amp;amp; Entire Checkpoints Timeline" width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When AI coding agents write code, their thinking is usually lost once merged. RepoRetro inspects git commit trailers (&lt;code&gt;Entire-Checkpoint: ...&lt;/code&gt;) and the git-native &lt;code&gt;entire/checkpoints/v1&lt;/code&gt; tree to recover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The original human prompt that initiated the code.&lt;/li&gt;
&lt;li&gt;The agent's chain-of-thought reasoning snippet.&lt;/li&gt;
&lt;li&gt;Why the feature was paused or abandoned mid-architecture.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  System Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌────────────────────────────────────────────────────────┐
                    │                   RepoRetro Web UI                     │
                    │         (Día de los Muertos Altar / Express API)       │
                    └───────────────────────────┬────────────────────────────┘
                                                │
                 ┌──────────────────────────────┴──────────────────────────────┐
                 ▼                                                             ▼
     ┌───────────────────────┐                                     ┌───────────────────────┐
     │    DeadCodeDetector   │                                     │      GitAnalyzer      │
     │  - Unreferenced Exp.  │                                     │  - Git Blame &amp;amp; Log    │
     │  - Commented Blocks   │                                     │  - Commit Trailers    │
     │  - Ancient Markers    │                                     │  - Author Inactivity  │
     └───────────┬───────────┘                                     └───────────┬───────────┘
                 │                                                             │
                 └──────────────────────────────┬──────────────────────────────┘
                                                │
                                                ▼
                               ┌─────────────────────────────────┐
                               │    AgentSessionCorrelator       │
                               │   - Entire Checkpoints Ref      │
                               │   - .entire/sessions.jsonl      │
                               └────────────────┬────────────────┘
                                                │
                                                ▼
                               ┌─────────────────────────────────┐
                               │       Mastra Orchestrator       │
                               │     @mastra/core Agent + Tools  │
                               └────────────────┬────────────────┘
                                                │
                                                ▼
                               ┌─────────────────────────────────┐
                               │       Gemma 2 (2B / 9B)         │
                               │   - Local Ollama / llama.cpp    │
                               │   - Archaeological Synthesis    │
                               │   - Chesterton's Fence Verdict  │
                               └────────────────┬────────────────┘
                                                │
                                                ▼
                               ┌─────────────────────────────────┐
                               │     SQLite Storage &amp;amp; Cache      │
                               └─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;RepoRetro is crafted entirely in &lt;strong&gt;TypeScript&lt;/strong&gt; using modern, modular tooling:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Open-Weight Gemma Integration (Featured Category: Best Use of Gemma)
&lt;/h3&gt;

&lt;p&gt;To determine &lt;em&gt;why&lt;/em&gt; a piece of code exists, RepoRetro sends the commit history, diff snippet, author tenure, and session transcripts to &lt;strong&gt;Gemma 2&lt;/strong&gt; (&lt;code&gt;gemma-2-2b-it&lt;/code&gt; or &lt;code&gt;gemma-2-9b-it&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI-Compatible Client&lt;/strong&gt;: Seamlessly connects to local inference engines (Ollama, &lt;code&gt;llama.cpp&lt;/code&gt;, vLLM) or hosted endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Archaeological Prompt Engineering&lt;/strong&gt;: Rather than asking Gemma to refactor or rewrite the code, Gemma acts as a historical detective, answering two critical questions:

&lt;ol&gt;
&lt;li&gt;&lt;em&gt;What engineering problem was the author solving when this code was introduced?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Applying Chesterton's Fence, is this code a Load-Bearing Relic, Unfinished Work, or Safe to Exorcise?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline Fallback Engine&lt;/strong&gt;: If no inference endpoint is reachable, RepoRetro gracefully uses a deterministic rule-based archaeological synthesizer so users can run scans completely offline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Entire Git-Native Checkpoints (Partner Category: Best Use of Entire)
&lt;/h3&gt;

&lt;p&gt;In traditional development, when a human writes code, their thoughts are lost. In agentic development, agents produce rich tool calls, reasoning steps, and prompts—which are usually discarded once merged.&lt;/p&gt;

&lt;p&gt;RepoRetro integrates with &lt;strong&gt;Entire&lt;/strong&gt;, an open agent checkpointing protocol:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It scans commits for &lt;code&gt;Entire-Checkpoint: &amp;lt;id&amp;gt;&lt;/code&gt; trailers.&lt;/li&gt;
&lt;li&gt;It parses the &lt;code&gt;.entire/sessions.jsonl&lt;/code&gt; log and queries the git-native &lt;code&gt;entire/checkpoints/v1&lt;/code&gt; ref.&lt;/li&gt;
&lt;li&gt;When RepoRetro finds dead code in &lt;code&gt;src/telemetry/carrier-pigeon.ts&lt;/code&gt;, it matches it to the exact Entire session where developer Marcus Vance asked:
&amp;gt; &lt;em&gt;"Prototype an offline event buffering class CarrierPigeon for mobile and IoT devices."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;RepoRetro pulls the agent's internal reasoning snippet, explaining to the modern maintainer that the code was left unreferenced because Marcus was reassigned before completing the sink integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Mastra Agent Framework (Partner Category: Best Use of Mastra)
&lt;/h3&gt;

&lt;p&gt;RepoRetro orchestrates the analysis using &lt;strong&gt;&lt;code&gt;@mastra/core&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implements custom Mastra tools: &lt;code&gt;inspectGitCommit&lt;/code&gt; (extracting commit metadata and trailers) and &lt;code&gt;searchSession&lt;/code&gt; (correlating agent transcripts).&lt;/li&gt;
&lt;li&gt;Encapsulates the entire archaeological reasoning workflow inside a Mastra &lt;code&gt;Agent&lt;/code&gt;, allowing other automated pipelines or CI/CD agents to invoke RepoRetro programmatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Cloud Deployment (Featured Categories: Best Use of Render &amp;amp; DigitalOcean)
&lt;/h3&gt;

&lt;p&gt;RepoRetro is ready for cloud deployment with zero manual configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Render (&lt;code&gt;render.yaml&lt;/code&gt;)&lt;/strong&gt;: Includes a complete Render Blueprint defining the Node.js web service, environment variables, health check path (&lt;code&gt;/api/health&lt;/code&gt;), and automated build steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DigitalOcean (&lt;code&gt;.do/app.yaml&lt;/code&gt; &amp;amp; &lt;code&gt;Dockerfile&lt;/code&gt;)&lt;/strong&gt;: Multi-stage production container ready to deploy to DigitalOcean App Platform or run locally with &lt;code&gt;docker-compose up&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;The Hacktoberfest Week 4 prompt asks a poignant question: &lt;em&gt;Why does open innovation matter for what you built?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When building a tool that inspects the history and ghosts of a repository, &lt;strong&gt;openness is not just a license preference—it is a non-negotiable architectural requirement:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Codebase Privacy &amp;amp; Security&lt;/strong&gt;:&lt;br&gt;
A repository's git history holds a company's crown jewels: legacy security fixes, proprietary algorithms, internal author emails, and unreleased prototypes. If an archaeology tool required piping every commit diff, author timestamp, and line of code to a closed third-party SaaS API, most engineering organizations would never be allowed to run it. By running &lt;strong&gt;open-weight Gemma models on local hardware&lt;/strong&gt; (via Ollama or &lt;code&gt;llama.cpp&lt;/code&gt;), RepoRetro runs with &lt;strong&gt;the internet completely disconnected&lt;/strong&gt;. Your codebase never leaves your workstation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Threat of Vendor Lock-In on Developer History&lt;/strong&gt;:&lt;br&gt;
When developers rely on closed-source agent platforms, their prompts and session context are trapped in proprietary databases. If that vendor pivots, raises prices, or shuts down, the context of &lt;em&gt;why&lt;/em&gt; your software was built disappears into the ether. By integrating with &lt;strong&gt;Entire&lt;/strong&gt;, agent sessions live directly inside the repository as git-native objects (&lt;code&gt;entire/checkpoints/v1&lt;/code&gt;). An open tool like RepoRetro ensures that your team's historical memory remains your property forever.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Software Longevity&lt;/strong&gt;:&lt;br&gt;
Proprietary development tools decay as APIs change. Open innovation allows communities to maintain tools across decades. Just as open-source codebases preserve historical commits from 15 years ago, open archaeology tools guarantee that we can continue to inspect and understand our legacy systems for generations to come.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Spirit of It: Día de los Muertos &amp;amp; The Human Element
&lt;/h2&gt;

&lt;p&gt;Too often in software engineering, we treat past code with cynicism. We look at legacy code, roll our eyes, and murmur: &lt;em&gt;"Who on earth wrote this mess?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;RepoRetro flips this perspective. Codebases are human endeavors. The engineer who wrote that convoluted 200-line regex in 2022 might have been staying up until 3:00 AM preventing a production crash on Black Friday. The author who wrote an incomplete billing client might have been called away to another team or laid off during a company restructuring.&lt;/p&gt;

&lt;p&gt;In the spirit of &lt;strong&gt;Día de los Muertos&lt;/strong&gt;, RepoRetro honors those who came before us. By lighting a digital candle on the Ofrenda, displaying their surviving impact, and understanding their original intent before touching a single line, we build software with empathy, reverence, and respect.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  Run the CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/AndyKhang404/reporetro.git
&lt;span class="nb"&gt;cd &lt;/span&gt;reporetro
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build

&lt;span class="c"&gt;# Generate the included multi-era haunted repository fixture&lt;/span&gt;
npm run seed-fixture

&lt;span class="c"&gt;# Scan any local repository&lt;/span&gt;
npm run cli scan fixtures/haunted-repo

&lt;span class="c"&gt;# Scan a private repo using a Personal Access Token (PAT)&lt;/span&gt;
npm run cli scan https://github.com/owner/private-repo &lt;span class="nt"&gt;--token&lt;/span&gt; ghp_yourTokenHere

&lt;span class="c"&gt;# Scan a private repo using an SSH key file&lt;/span&gt;
npm run cli scan git@github.com:owner/private-repo.git &lt;span class="nt"&gt;--ssh-key&lt;/span&gt; ~/.ssh/id_ed25519
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Launch the Altar Dashboard
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start the web server&lt;/span&gt;
npm start
&lt;span class="c"&gt;# Open http://localhost:3000 in your browser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma ($200 Featured)&lt;/strong&gt;: Open-weight Gemma 2 powers local historical reasoning, commit intent synthesis, and Chesterton's Fence verdicts without code telemetry leaving the machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Entire ($100 Partner)&lt;/strong&gt;: Queries git-native &lt;code&gt;entire/checkpoints/v1&lt;/code&gt; and &lt;code&gt;.entire/sessions.jsonl&lt;/code&gt; to correlate unreferenced code with the original developer prompts and agent reasoning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Mastra ($100 Partner)&lt;/strong&gt;: Orchestrates archaeological tools and inspection workflows with &lt;code&gt;@mastra/core/agent&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render ($200 Featured)&lt;/strong&gt;: Native Infrastructure-as-Code via &lt;code&gt;render.yaml&lt;/code&gt; Blueprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of DigitalOcean ($200 Featured)&lt;/strong&gt;: Production Docker container and &lt;code&gt;.do/app.yaml&lt;/code&gt; App Platform spec.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overall Winner ($250)&lt;/strong&gt;: A creative, deeply thematic fusion of Halloween ("Ghost in the Machine") and Día de los Muertos honoring the past of open-source software.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Community Wisdom &amp;amp; Citations
&lt;/h2&gt;

&lt;p&gt;The architectural philosophy of RepoRetro was deeply inspired by ongoing discussions in the developer community:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/thawkin3/chestertons-half-implemented-fence-that-he-built-just-in-case-5ape"&gt;Chesterton's half-implemented fence that he built just in case&lt;/a&gt; by Tommy Hawkins — on why half-implemented features exist and why deleting them blindly is dangerous.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/eshevtsov/your-git-history-keeps-the-code-who-keeps-the-lesson-24do"&gt;Your Git History Keeps the Code. Who Keeps the Lesson?&lt;/a&gt; by Evgeny Shevtsov — on the missing link between commit diffs and original intent.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ailoitte_sk/repository-intelligence-in-2026-why-ai-that-reads-your-git-history-beats-ai-that-reads-your-files-3h0p"&gt;Repository Intelligence in 2026: Why AI That Reads Your Git History Beats AI That Reads Your Files&lt;/a&gt; by Ailoitte — on why historical context matters more than static file analysis.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Code and documentation licensed under the &lt;a href="https://github.com/AndyKhang404/reporetro/blob/main/LICENSE" rel="noopener noreferrer"&gt;MIT License&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Sloth Digest 🦥: Private Overnight Gmail Briefing &amp; Reply-Debt Tracker with Gemma, Temporal &amp; Mastra</title>
      <dc:creator>Andy Khang</dc:creator>
      <pubDate>Mon, 26 Oct 2026 02:36:12 +0000</pubDate>
      <link>https://dev.to/ak404/sloth-digest-private-overnight-gmail-briefing-reply-debt-tracker-with-gemma-temporal-mastra-3f2l</link>
      <guid>https://dev.to/ak404/sloth-digest-private-overnight-gmail-briefing-reply-debt-tracker-with-gemma-temporal-mastra-3f2l</guid>
      <description>&lt;p&gt;&lt;em&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;/em&gt;&lt;/p&gt;

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

&lt;p&gt;In honor of International Sloth Day and the &lt;strong&gt;"Slow and Steady"&lt;/strong&gt; theme, I built &lt;strong&gt;Sloth Digest&lt;/strong&gt; 🦥 — a 100% private, local-first overnight Gmail executive briefing and &lt;strong&gt;Reply-Debt Tracker&lt;/strong&gt; that does the dreaded morning inbox catch-up while you sleep.&lt;/p&gt;

&lt;p&gt;Waking up to 40 unread emails, sifting through newsletters, and anxiously wondering &lt;em&gt;"Wait, did I ever reply to Sarah's question from Tuesday?"&lt;/em&gt; is the opposite of sloth energy. Instead of handing your most sensitive personal data to a cloud LLM API, &lt;strong&gt;Sloth Digest&lt;/strong&gt; runs entirely on your home server or laptop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sleep through the night:&lt;/strong&gt; At 6:00 AM, a durable &lt;strong&gt;Temporal&lt;/strong&gt; schedule wakes up on your machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-only IMAP ingestion:&lt;/strong&gt; It inspects new messages in your Gmail &lt;code&gt;INBOX&lt;/code&gt; and &lt;code&gt;[Gmail]/Sent Mail&lt;/code&gt; using strict RFC 3501 &lt;code&gt;EXAMINE&lt;/code&gt; mode with UID cursors — so it &lt;strong&gt;never&lt;/strong&gt; marks unread emails as read or mutates your mailbox.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Gemma inference via &lt;code&gt;llama.cpp&lt;/code&gt;:&lt;/strong&gt; An open-weight &lt;strong&gt;Gemma&lt;/strong&gt; model served locally over &lt;code&gt;llama.cpp&lt;/code&gt; classifies mail (&lt;code&gt;personal&lt;/code&gt;, &lt;code&gt;transactional&lt;/code&gt;, &lt;code&gt;bulk&lt;/code&gt;), summarizes multi-message personal threads into &lt;strong&gt;strictly two lines&lt;/strong&gt;, and extracts unanswered questions directed at you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-Reconciled Reply Debt:&lt;/strong&gt; Every direct ask is recorded in a local SQLite &lt;code&gt;reply_debt&lt;/code&gt; ledger (&lt;code&gt;NEW&lt;/code&gt;, &lt;code&gt;PENDING 3d&lt;/code&gt;, etc.). Whenever you reply from any email client during the day, the next run inspects your &lt;code&gt;Sent&lt;/code&gt; folder's &lt;code&gt;In-Reply-To&lt;/code&gt; and thread headers and &lt;strong&gt;automatically resolves the debt&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wake-up delivery:&lt;/strong&gt; At 7:00 AM, a self-hosted &lt;strong&gt;ntfy&lt;/strong&gt; push notification lands on your phone with your reading time saved and pending asks, linked to a zero-dependency LAN web dashboard (&lt;code&gt;http://localhost:3000&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Demo, Prerequisites &amp;amp; Quickstart
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before Starting the App (Live Gmail + Full Stack Prerequisites)
&lt;/h3&gt;

&lt;p&gt;Before launching &lt;strong&gt;Sloth Digest&lt;/strong&gt; against your real Gmail inbox, prepare the following prerequisites:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Runtime Environment:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js 22+&lt;/strong&gt; and &lt;strong&gt;npm&lt;/strong&gt; (for local CLI execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker &amp;amp; Docker Compose&lt;/strong&gt; (recommended for running the full 4-container stack: &lt;code&gt;sloth-llama&lt;/code&gt;, &lt;code&gt;sloth-temporal&lt;/code&gt;, &lt;code&gt;sloth-ntfy&lt;/code&gt;, and &lt;code&gt;sloth-app&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google App Password for Gmail (&lt;code&gt;imap.gmail.com:993&lt;/code&gt;):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Gmail disallows standard account passwords over IMAP. First, ensure &lt;strong&gt;2-Step Verification (2FA)&lt;/strong&gt; is enabled on your Google Account (&lt;code&gt;https://myaccount.google.com/security&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Generate a &lt;strong&gt;16-character Google App Password&lt;/strong&gt; at &lt;code&gt;https://myaccount.google.com/apppasswords&lt;/code&gt; and copy the 16-character code.&lt;/li&gt;
&lt;li&gt;Verify that &lt;strong&gt;IMAP access&lt;/strong&gt; is enabled in your Gmail forwarding/POP/IMAP settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quantized Gemma GGUF Weights (&lt;code&gt;./models/&lt;/code&gt;):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Create the local &lt;code&gt;./models&lt;/code&gt; directory (&lt;code&gt;mkdir -p models&lt;/code&gt;) and download your quantized Gemma GGUF model file into it (for example, &lt;code&gt;gemma-2-2b-it-Q4_K_M.gguf&lt;/code&gt; or a Gemma 4B GGUF model matching &lt;code&gt;LLAMA_MODEL_FILE&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment Variables (&lt;code&gt;.env&lt;/code&gt;):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Copy &lt;code&gt;cp .env.example .env&lt;/code&gt; and configure your credentials and schedule before starting the app:
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Gmail IMAP Credentials (requires 2FA + 16-char Google App Password)
GMAIL_USER=your-email@gmail.com
GMAIL_APP_PASSWORD=your-16-char-app-password
# Or mount via Docker secret file:
# GMAIL_APP_PASSWORD_FILE=/run/secrets/gmail_password

# llama.cpp Server (OpenAI-compatible /v1 endpoint running Gemma)
LLAMA_SERVER_URL=http://localhost:8080
LLAMA_MODEL_NAME=gemma-2-2b-it
LLAMA_MODEL_FILE=gemma-2-2b-it-Q4_K_M.gguf

# Temporal Schedule &amp;amp; Timezone
TEMPORAL_ADDRESS=localhost:7233
DIGEST_SCHEDULE_HOUR=6
DIGEST_SCHEDULE_MINUTE=0
DIGEST_SCHEDULE_TIMEZONE=Asia/Ho_Chi_Minh

# Self-Hosted ntfy Push Delivery &amp;amp; LAN Dashboard
NTFY_URL=http://localhost:8088/sloth-digest
NTFY_SCHEDULE_TIME=07:00
WEB_PORT=3000
# Optional: Protect the LAN dashboard with HTTP Basic / Bearer auth
# WEB_AUTH_TOKEN=your-secret-dashboard-token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Option A: Start the Full Self-Hosted Stack (Docker Compose)
&lt;/h3&gt;

&lt;p&gt;Once &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;./models/&lt;/code&gt; are ready, bring up &lt;code&gt;llama-server&lt;/code&gt;, Temporal (with Web UI on &lt;code&gt;127.0.0.1:8233&lt;/code&gt;), &lt;code&gt;ntfy&lt;/code&gt; (&lt;code&gt;127.0.0.1:8088&lt;/code&gt;), and the unified &lt;code&gt;sloth-app&lt;/code&gt; worker + dashboard (&lt;code&gt;http://localhost:3000&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build and start all 4 containers&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;

&lt;span class="c"&gt;# Register the 06:00 AM daily Temporal schedule&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; sloth-app npx tsx src/cli.ts schedule

&lt;span class="c"&gt;# Or trigger an immediate digest run right now&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; sloth-app npx tsx src/cli.ts run-now
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Option B: Instant Offline Demo (Zero Credentials Needed for Judges)
&lt;/h3&gt;

&lt;p&gt;Want to test the entire pipeline immediately without setting up a Google App Password or downloading GGUF weights first? Use the built-in synthetic inbox seeder and deterministic fallback:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Clone and install&lt;/span&gt;
git clone https://github.com/AndyKhang404/sloth-digest.git
&lt;span class="nb"&gt;cd &lt;/span&gt;sloth-digest
npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env

&lt;span class="c"&gt;# 2. Run the automated verification suite (12/12 assertions)&lt;/span&gt;
npm run test-pipeline

&lt;span class="c"&gt;# 3. Seed realistic multi-thread emails + a Sent reply into local SQLite&lt;/span&gt;
npm run seed &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--local&lt;/span&gt;

&lt;span class="c"&gt;# 4. Run the standalone local digest pipeline&lt;/span&gt;
npm run sloth &lt;span class="nt"&gt;--&lt;/span&gt; run-local

&lt;span class="c"&gt;# 5. Inspect active reply debt in the CLI or launch the LAN web dashboard&lt;/span&gt;
npm run sloth &lt;span class="nt"&gt;--&lt;/span&gt; debt
npm run sloth &lt;span class="nt"&gt;--&lt;/span&gt; serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Sample CLI &amp;amp; Digest Output
&lt;/h3&gt;

&lt;p&gt;When the pipeline finishes, it writes a clean Markdown briefing to &lt;code&gt;./data/digests/&lt;/code&gt; and serves it at &lt;code&gt;http://localhost:3000&lt;/code&gt;:&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="gh"&gt;# 🦥 Sloth Digest — Executive Morning Briefing&lt;/span&gt;
&lt;span class="ge"&gt;**&lt;/span&gt; Lazy Payoff:&lt;span class="ge"&gt;**&lt;/span&gt; Saved &lt;span class="gs"&gt;**14.2 min**&lt;/span&gt; of reading (3,650 words condensed to 270 words @ 238 WPM)

&lt;span class="gu"&gt;## 🌅 Two-Line Executive Overview&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Processed 3 active personal threads overnight while skipping 4 bulk/transactional messages.
&lt;span class="p"&gt;-&lt;/span&gt; You have 2 open reply-debt items awaiting your response today (1 thread auto-resolved via Sent mail).

&lt;span class="gu"&gt;## 🔴 Active Reply Debt (Needs Your Reply)&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; &lt;span class="gs"&gt;**[NEW] Production Migration Window Confirmation**&lt;/span&gt; — from &lt;span class="sb"&gt;`sarah.chen@example.com`&lt;/span&gt;
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Ask:**&lt;/span&gt; Can you confirm if Sunday 02:00 UTC works for the database cutover?
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Cited Message-ID:**&lt;/span&gt; &lt;span class="sb"&gt;`&amp;lt;msg-prod-cutover-02@example.com&amp;gt;`&lt;/span&gt;
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**[PENDING 2d] Q4 Architecture Sign-off**&lt;/span&gt; — from &lt;span class="sb"&gt;`alex.rivera@example.com`&lt;/span&gt;
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Ask:**&lt;/span&gt; Please review the updated latency numbers in section 3 before Friday.
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Cited Message-ID:**&lt;/span&gt; &lt;span class="sb"&gt;`&amp;lt;msg-q4-arch-01@example.com&amp;gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Code
&lt;/h2&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/AndyKhang404" rel="noopener noreferrer"&gt;
        AndyKhang404
      &lt;/a&gt; / &lt;a href="https://github.com/AndyKhang404/sloth-digest" rel="noopener noreferrer"&gt;
        sloth-digest
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Private overnight Gmail digest and reply-debt tracker powered locally by Gemma, llama.cpp, Mastra, and Temporal.
    &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;🦥 Sloth Digest&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Private Overnight Gmail Executive Briefing &amp;amp; Reply-Debt Tracker&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Built for Hacktoberfest 2026 Week 3: "Slow and Steady"&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Lead Category:&lt;/strong&gt; Best Use of Gemma ($200) • &lt;strong&gt;Secondary:&lt;/strong&gt; Best Use of Temporal, Best Use of Mastra&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/AndyKhang404/sloth-digest/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a href="https://ai.google.dev/gemma" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fa8242e00af3a59e695f6e8b7bd9661756e6dd1dbb468327462957429cdaf1e4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f47656d6d612d34425f496e7374727563742d626c756576696f6c6574" alt="Gemma"&gt;&lt;/a&gt;
&lt;a href="https://github.com/ggerganov/llama.cpp" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7060debd3baef81e8616ebd6f2f835a99c5ddd447a1e1e7f6e8c6da30ccad189/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f52756e74696d652d6c6c616d612e6370705f474755462d6f72616e6765" alt="llama.cpp"&gt;&lt;/a&gt;
&lt;a href="https://temporal.io" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/851dfee59ed3ecbb7a9c1f410756210199ca17e89d02e3ba662871d425f16c77/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4f7263686573747261746f722d54656d706f72616c5f576f726b666c6f77732d626c61636b" alt="Temporal"&gt;&lt;/a&gt;
&lt;a href="https://mastra.ai" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/efe0fcbd1a47cfd178d56bfcfcd13be6531c372fb2d99c70287184fc6f447a7b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4167656e742d4d61737472615f547970655363726970742d696e6469676f" alt="Mastra"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📖 What is Sloth Digest?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;In honor of &lt;strong&gt;International Sloth Day&lt;/strong&gt; and the Hacktoberfest 2026 &lt;em&gt;"Slow and Steady"&lt;/em&gt; theme, &lt;strong&gt;Sloth Digest&lt;/strong&gt; channels your inner sloth by doing the dreaded morning email catch-up while you sleep.&lt;/p&gt;
&lt;p&gt;Instead of waking up to 40 unread emails, scrolling through noise, and dreading forgotten replies:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sleep through the night:&lt;/strong&gt; At 6:00 AM, a durable &lt;strong&gt;Temporal&lt;/strong&gt; workflow wakes up on your home server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-only triage:&lt;/strong&gt; It examines new emails in your Gmail inbox without modifying read flags or leaving your local network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local AI synthesis:&lt;/strong&gt; An open-weight &lt;strong&gt;Gemma 4B&lt;/strong&gt; model running on &lt;strong&gt;llama.cpp&lt;/strong&gt; categorizes bulk mail, summarizes personal threads into &lt;strong&gt;strictly two lines&lt;/strong&gt;, and extracts direct asks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable&lt;/strong&gt;…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AndyKhang404/sloth-digest" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





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

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    Gmail["Gmail IMAP (Read-Only EXAMINE)"] --&amp;gt;|UID Cursor Fetch| Temporal["Temporal Workflow (nightlyEmailDigestWorkflow)"]
    Temporal --&amp;gt; Ingest["1. fetchNewEmailsActivity (INBOX + Sent)"]
    Temporal --&amp;gt; Classify["2. classifyEmailsActivity (Gemma + Mastra)"]
    Temporal --&amp;gt; Summarize["3. summarizeThreadActivity (2-Line Thread Summary)"]
    Temporal --&amp;gt; Reduce["4. reduceAndGenerateDigestActivity (Reply-Debt Engine + SQLite)"]
    Temporal --&amp;gt; Notify["5. deliverNotificationActivity"]
    Classify &amp;lt;--&amp;gt; Llama["llama.cpp Server (Gemma GGUF on 127.0.0.1:8080)"]
    Summarize &amp;lt;--&amp;gt; Llama
    Reduce --&amp;gt; Dashboard["LAN Web Dashboard (:3000)"]
    Notify --&amp;gt; Ntfy["Self-Hosted ntfy Push (:8088)\nScheduled 07:00 Delivery"]&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  Joining the Community Conversation on Local AI &amp;amp; Email Agents
&lt;/h3&gt;

&lt;p&gt;While designing &lt;strong&gt;Sloth Digest&lt;/strong&gt;, three discussions from the DEV community directly shaped the architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero-rewrite local inference with &lt;code&gt;llama.cpp&lt;/code&gt;:&lt;/strong&gt; In &lt;a href="https://dev.to/dedupedai/how-to-serve-an-openai-compatible-local-api-with-llamacpp-3425"&gt;How to serve an OpenAI-compatible local API with llama.cpp&lt;/a&gt; by &lt;a href="https://dev.to/dedupedai"&gt;@dedupedai&lt;/a&gt;, the author demonstrates exposing a local GGUF model over &lt;code&gt;/v1/chat/completions&lt;/code&gt; with JSON schema constraints. We used this exact pattern so our &lt;strong&gt;Mastra&lt;/strong&gt; agents talk to &lt;code&gt;llama-server&lt;/code&gt; on &lt;code&gt;127.0.0.1:8080&lt;/code&gt; with &lt;code&gt;response_format: { type: 'json_object' }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic verifiers around small local models:&lt;/strong&gt; In &lt;a href="https://dev.to/akshatsoni26/beyond-vibes-architecting-closed-loop-ai-agents-with-local-slms-deterministic-evals-and-human-3kkf"&gt;Beyond Vibes: Architecting Closed-Loop AI Agents with Local SLMs, Deterministic Evals, and Human Learning Loops&lt;/a&gt; by &lt;a href="https://dev.to/akshatsoni26"&gt;@akshatsoni26&lt;/a&gt;, the core takeaway is &lt;em&gt;"Numbers owned by code, words owned by models"&lt;/em&gt; — never trust a small local model to invent IDs or calculate metrics unverified. Inspired by that guardrail pattern, &lt;strong&gt;Sloth Digest&lt;/strong&gt; cross-checks every &lt;code&gt;cited_message_ids&lt;/code&gt; entry returned by Gemma against a deterministic &lt;code&gt;Set&lt;/code&gt; of real RFC 822 &lt;code&gt;Message-ID&lt;/code&gt;s in that thread, and computes reading-time savings in pure TypeScript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why email is tough for agents:&lt;/strong&gt; In &lt;a href="https://dev.to/francofuji/giving-autonomous-agents-access-to-email-4kj5"&gt;Giving Autonomous Agents Access to Email&lt;/a&gt; by &lt;a href="https://dev.to/francofuji"&gt;@francofuji&lt;/a&gt;, the author highlights how traditional email polling and state management trip up autonomous agents. For personal inboxes, the biggest hazard is side effects (accidentally marking unread messages as &lt;code&gt;\Seen&lt;/code&gt; or choking on huge attachments), which drove our strict read-only &lt;code&gt;EXAMINE&lt;/code&gt; + 100 KB source cap design.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Implementation Details
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Strictly Read-Only IMAP with UID Cursors (&lt;code&gt;src/ingest/imap.ts&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;If an overnight script marks your emails as "Read" before you wake up, you will miss critical messages when you open your phone. We open both &lt;code&gt;INBOX&lt;/code&gt; and &lt;code&gt;[Gmail]/Sent Mail&lt;/code&gt; with &lt;code&gt;{ readOnly: true }&lt;/code&gt; (IMAP &lt;code&gt;EXAMINE&lt;/code&gt;) and track &lt;code&gt;(uidvalidity, last_uid)&lt;/code&gt; cursors in SQLite:&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;inboxLock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;imap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMailboxLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INBOX&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;readOnly&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="k"&gt;try&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;mailbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mailbox&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="nx"&gt;mailbox&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;mailbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;uidvalidity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mailbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uidValidity&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;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dbService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INBOX&lt;/span&gt;&lt;span class="dl"&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;lastUid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;uidvalidity&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;uidvalidity&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;last_uid&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="c1"&gt;// Fetch only UIDs &amp;gt; lastUid with a 100 KB source cap to skip heavy attachments&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;inboxLock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&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;h4&gt;
  
  
  2. Prompt-Injection Isolation &amp;amp; Citation Verification (&lt;code&gt;src/agent/mastra.ts&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Incoming emails are untrusted external input. Before passing any email into our &lt;strong&gt;Mastra&lt;/strong&gt; agents (&lt;code&gt;EmailTriageAgent&lt;/code&gt;, &lt;code&gt;ThreadSummarizerAgent&lt;/code&gt;, &lt;code&gt;DigestOverviewAgent&lt;/code&gt;), we strip scripts, styles, hidden zero-width/bidirectional Unicode characters, and wrap the text inside &lt;code&gt;&amp;lt;untrusted_email_data&amp;gt;&lt;/code&gt; tags with boundary neutralization. Then we validate the output with &lt;code&gt;zod&lt;/code&gt; and filter any hallucinated &lt;code&gt;Message-ID&lt;/code&gt; citations:&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ThreadSummarySchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;thread_summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()]),&lt;/span&gt;
  &lt;span class="na"&gt;needs_reply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;reply_reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;cited_message_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Deterministic validation: filter out any cited message IDs not actually in this thread&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validMessageIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&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;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message_id&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;validatedCitations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cited_message_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;validMessageIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Auto-Resolving Reply Debt via Sent Mail (&lt;code&gt;src/engine/debt.ts&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Instead of forcing you to manually check off to-do items every day, &lt;code&gt;reconcileSentReplies&lt;/code&gt; checks if you replied to the thread or &lt;code&gt;In-Reply-To&lt;/code&gt; message ID from your phone or desktop client, and clears the debt automatically:&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;normalizeMsgId&lt;/span&gt; &lt;span class="o"&gt;=&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="kr"&gt;string&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;id&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;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&amp;lt;+|&amp;gt;+$/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&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;reply&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;sentReplies&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;normalizedReplyTarget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inReplyTo&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;normalizeMsgId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inReplyTo&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="p"&gt;;&lt;/span&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;debt&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;openDebts&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="nx"&gt;debt&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="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matchesInReplyTo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalizedReplyTarget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;normalizedReplyTarget&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nf"&gt;normalizeMsgId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;debt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message_id&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;matchesThread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threadId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;debt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thread_id&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="nx"&gt;matchesInReplyTo&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;matchesThread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;dbService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolveReplyDebtByThread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;debt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thread_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;debt&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="s1"&gt;resolved&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Durable Orchestration with Temporal (&lt;code&gt;src/temporal/workflows.ts&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Home servers reboot, Wi-Fi blips, and CPU inference can spike. Wrapping the 5 pipeline stages in a &lt;strong&gt;Temporal&lt;/strong&gt; workflow (&lt;code&gt;nightlyEmailDigestWorkflow&lt;/code&gt;) with activity retries, exponential backoff, and heartbeats ensures the 6:00 AM digest finishes reliably before the 7:00 AM push notification fires.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Would Do Differently Next
&lt;/h3&gt;

&lt;p&gt;Right now, thread summarization runs sequentially per active thread to keep CPU and RAM usage gentle on low-power home servers. Next, I would add configurable concurrency limits in the Temporal worker alongside a lightweight local embedding index so follow-up threads from weeks ago can link prior context automatically.&lt;/p&gt;




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

&lt;p&gt;Your personal email inbox holds bank alerts, medical updates, private family conversations, and work contracts. Sending every unread email to a proprietary cloud LLM API every night means trading away your privacy and paying recurring token fees forever.&lt;/p&gt;

&lt;p&gt;Open innovation makes &lt;strong&gt;Sloth Digest&lt;/strong&gt; possible on three fronts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by Architecture (Open-Weight Gemma + &lt;code&gt;llama.cpp&lt;/code&gt;):&lt;/strong&gt; Because Gemma runs locally as a quantized GGUF model bound strictly to &lt;code&gt;127.0.0.1:8080&lt;/code&gt;, &lt;strong&gt;zero bytes of email content ever leave your LAN&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sloth Economics ($0/month Forever):&lt;/strong&gt; Nightly batch triage across dozens of emails costs &lt;strong&gt;$0.00 in API fees&lt;/strong&gt;. Once your container stack is up, it runs quietly on existing hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verifiable, Self-Hosted Stack:&lt;/strong&gt; Every component — from &lt;strong&gt;Mastra&lt;/strong&gt; agent schemas and &lt;strong&gt;Temporal&lt;/strong&gt; workflow orchestration to &lt;strong&gt;ntfy&lt;/strong&gt; push notifications and SQLite — runs in rootless local Docker containers that you can inspect, audit, and customize.&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;&lt;strong&gt;Sloth Digest&lt;/strong&gt; calculates your exact time saved on every run using the standard human reading speed benchmark of &lt;strong&gt;238 words per minute&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Cold Inbox Morning&lt;/th&gt;
&lt;th&gt;With Sloth Digest 🦥&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Morning Triage Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30–60 minutes scanning threads &amp;amp; promos&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~2 minutes&lt;/strong&gt; reading a 2-line-per-thread briefing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Forgotten Replies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Searching &lt;em&gt;"did I reply to that?"&lt;/em&gt; days later&lt;/td&gt;
&lt;td&gt;Auto-tracked &lt;strong&gt;Reply Debt&lt;/strong&gt; cleared when you hit Send&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud AI Exposure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100% of inbox sent to third-party servers&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0 bytes&lt;/strong&gt; leave your machine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Over a year, saving just 25 minutes each morning reclaims &lt;strong&gt;~150 hours&lt;/strong&gt; of focus — all while you sleep in.&lt;/p&gt;




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

&lt;p&gt;This project targets the following Hacktoberfest Week 3 partner categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Google's open-weight &lt;strong&gt;Gemma&lt;/strong&gt; model (served locally via &lt;code&gt;llama.cpp&lt;/code&gt;) powers all three stages of intelligence: inbox triage (&lt;code&gt;classifyEmailWithGemma&lt;/code&gt;), strict two-line thread summarization + ask extraction (&lt;code&gt;summarizeThreadWithGemma&lt;/code&gt;), and the morning executive briefing (&lt;code&gt;generateOverallOverviewWithGemma&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Temporal:&lt;/strong&gt; &lt;code&gt;nightlyEmailDigestWorkflow&lt;/code&gt; orchestrates the 5-step ingestion, classification, thread map-reduce summarization, SQLite ledger reconciliation, and notification delivery pipeline with activity timeouts, heartbeats, exponential backoff retries, and a 6:00 AM cron schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Mastra:&lt;/strong&gt; &lt;code&gt;@mastra/core&lt;/code&gt; defines our structured local agents (&lt;code&gt;EmailTriageAgent&lt;/code&gt;, &lt;code&gt;ThreadSummarizerAgent&lt;/code&gt;, &lt;code&gt;DigestOverviewAgent&lt;/code&gt;) paired with strict &lt;code&gt;zod&lt;/code&gt; schema validation (&lt;code&gt;EmailCategorySchema&lt;/code&gt;, &lt;code&gt;ThreadSummarySchema&lt;/code&gt;, &lt;code&gt;InboxOverviewSchema&lt;/code&gt;) and prompt-injection boundary sanitization.&lt;/li&gt;
&lt;/ul&gt;

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