<?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: hongpengtan0-max</title>
    <description>The latest articles on DEV Community by hongpengtan0-max (@hongpengtan0max).</description>
    <link>https://dev.to/hongpengtan0max</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%2F4186243%2Fb1b00df6-0e0f-479f-8ed5-c28b6e838700.png</url>
      <title>DEV Community: hongpengtan0-max</title>
      <link>https://dev.to/hongpengtan0max</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hongpengtan0max"/>
    <language>en</language>
    <item>
      <title>An AI invented half the memories on an ofrenda card. Ofrenda Keeper writes down only what you said, and asks about the rest</title>
      <dc:creator>hongpengtan0-max</dc:creator>
      <pubDate>Wed, 28 Oct 2026 07:43:02 +0000</pubDate>
      <link>https://dev.to/hongpengtan0max/an-ai-invented-half-the-memories-on-an-ofrenda-card-ofrenda-keeper-writes-down-only-what-you-said-a9a</link>
      <guid>https://dev.to/hongpengtan0max/an-ai-invented-half-the-memories-on-an-ofrenda-card-ofrenda-keeper-writes-down-only-what-you-said-a9a</guid>
      <description>&lt;p&gt;&lt;em&gt;This 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;p&gt;&lt;strong&gt;Prize categories:&lt;/strong&gt; Best Use of Gemma, Best Use of Mastra&lt;/p&gt;

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

&lt;p&gt;I asked Gemma 3 to make a Día de los Muertos ofrenda card for "Grandpa Hiro", a made-up grandfather from my test set. All I told it was that he taught me chess on a board with a missing knight replaced by a bottle cap, and that he liked jazz, &lt;em&gt;"but I can't remember which records."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The card came back with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a surname I never gave him (&lt;strong&gt;Tanaka&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;favorite foods: &lt;strong&gt;mochi, sushi, tempura, green tea ice cream&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;favorite artists: &lt;strong&gt;Miles Davis and Ella Fitzgerald&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;three wise sayings he never said&lt;/li&gt;
&lt;li&gt;"the scent of his pipe tobacco"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the ghost in the machine: a voice that sounds like memory but belongs to nobody.&lt;/p&gt;

&lt;p&gt;On an ofrenda, a made-up detail is worse than a blank line. So I built &lt;strong&gt;Ofrenda Keeper&lt;/strong&gt;, a local app that turns what you tell it about someone you miss into an altar card. Its one rule: &lt;strong&gt;if you didn't say it, it doesn't go on the card. If you weren't sure, it asks you instead of guessing.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every detail on the card carries the exact words it came from (hover to see them).&lt;/li&gt;
&lt;li&gt;When you hedge ("I think the green tamales, but maybe that was my mom"), the detail is held back and becomes a question.&lt;/li&gt;
&lt;li&gt;Missing pieces (a song, a saying, the years) become gentle follow-ups, not filler.&lt;/li&gt;
&lt;li&gt;A button shows what the &lt;em&gt;same model&lt;/em&gt; would have written without the guard, so you can see the difference.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;All the memories in this post, the demo and the test set are &lt;strong&gt;fictional samples&lt;/strong&gt; I wrote for testing. I didn't want to put a real person's story into a project I was still debugging.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/rnJqAOy4B-k" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;▶️ Video (about 2 min, recorded on a CPU-only box, real model latency): &lt;a href="https://github.com/hongpengtan0-max/ofrenda-keeper/blob/main/docs/demo.mp4" rel="noopener noreferrer"&gt;docs/demo.mp4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flci9xtwbmq5pm6zja9v0.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%2Flci9xtwbmq5pm6zja9v0.png" alt="Card after two fictional memories" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the demo I type a fictional memory: &lt;em&gt;"My grandmother Lucía made pan de muerto with orange zest… She liked boleros, I think, but I'm not sure which ones."&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pan de muerto and orange zest go on the card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boleros do not.&lt;/strong&gt; They show up under "I'd like to ask you" instead.&lt;/li&gt;
&lt;li&gt;When I add &lt;em&gt;"She always said 'Ya llegará.' She kept her rosary in a cookie tin"&lt;/em&gt;, the saying and the rosary appear, each with its quote.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkdeehqnq2lixeqlb2ziu.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%2Fkdeehqnq2lixeqlb2ziu.png" alt="Unguarded comparison" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "What would an unguarded AI write?" button asks the same Gemma model with a plain "fill every field" prompt. It immediately adds "a picture of Lucía wearing a bolero", "orange blossoms representing her love of orange zest", and more.&lt;/p&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/hongpengtan0-max" rel="noopener noreferrer"&gt;
        hongpengtan0-max
      &lt;/a&gt; / &lt;a href="https://github.com/hongpengtan0-max/ofrenda-keeper" rel="noopener noreferrer"&gt;
        ofrenda-keeper
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Turn memories of someone you miss into a Día de los Muertos ofrenda card, locally, without the AI inventing a single detail. Mastra + Gemma 3 via Ollama.
    &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;🕯️ Ofrenda Keeper 🌼&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Tell it about someone you miss, and it builds a Día de los Muertos ofrenda card from &lt;strong&gt;only the things you actually said&lt;/strong&gt;. Anything it is unsure about becomes a gentle question, not a made-up detail.&lt;/p&gt;
&lt;p&gt;It runs entirely on your own machine: &lt;a href="https://ai.google.dev/gemma" rel="nofollow noopener noreferrer"&gt;Gemma 3 4B&lt;/a&gt; (open weights) served by &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;, orchestrated by &lt;a href="https://mastra.ai" rel="nofollow noopener noreferrer"&gt;Mastra&lt;/a&gt; (agent + memory + tools + workflow). There are no API keys, and your family's stories never leave your computer.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/hongpengtan0-max/ofrenda-keeper/docs/screenshots/02_card_after_answer.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fhongpengtan0-max%2Fofrenda-keeper%2FHEAD%2Fdocs%2Fscreenshots%2F02_card_after_answer.png" alt="card"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Demo video (about 2 min): &lt;a href="https://github.com/hongpengtan0-max/ofrenda-keeper/docs/demo.mp4" rel="noopener noreferrer"&gt;&lt;code&gt;docs/demo.mp4&lt;/code&gt;&lt;/a&gt;. The sample memory in the demo is fictional.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;If you ask an LLM to "make an ofrenda card for my grandmother", it will happily give her favorite songs, sayings and foods she never had. On our fictional test set, &lt;strong&gt;49.7% of the claims an unguarded Gemma 3 4B made about the person were invented&lt;/strong&gt;. On a remembrance card, an invented memory…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/hongpengtan0-max/ofrenda-keeper" 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;p&gt;&lt;strong&gt;Stack, all open and all local:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 3 4B&lt;/strong&gt; (Q4_K_M GGUF) running in &lt;strong&gt;Ollama&lt;/strong&gt; on CPU, about 9 tokens/s on the 8-core box I built it on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mastra&lt;/strong&gt;: an &lt;code&gt;Agent&lt;/code&gt; with &lt;code&gt;Memory&lt;/code&gt; (LibSQL file), two &lt;code&gt;createTool&lt;/code&gt; tools, and a two-step &lt;code&gt;createWorkflow&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Express plus one HTML file for the altar card.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The pipeline, run every time you share a memory:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Remember.&lt;/strong&gt; The Mastra agent replies in a sentence or two and asks one question. Mastra &lt;code&gt;Memory&lt;/code&gt; stores the thread, so the card is always rebuilt from &lt;em&gt;everything you've said&lt;/em&gt;, not from the model's last reply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract&lt;/strong&gt; (workflow step 1). Gemma gets only your own messages and must return each detail with a &lt;strong&gt;verbatim quote&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify&lt;/strong&gt; (workflow step 2). The &lt;code&gt;verify-quote&lt;/code&gt; tool checks, in plain code:

&lt;ul&gt;
&lt;li&gt;the quote really exists in your words;&lt;/li&gt;
&lt;li&gt;the detail doesn't go beyond the quote;&lt;/li&gt;
&lt;li&gt;the sentence wasn't hedged ("I think", "maybe", "I can't remember").&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Anything that fails goes to the &lt;code&gt;ask-follow-up&lt;/code&gt; tool and becomes a question.&lt;/p&gt;

&lt;p&gt;One honest design note: Gemma 3 through Ollama doesn't do reliable native tool calling. So the Mastra tools are executed by the workflow steps, not picked by the model. I ended up liking that. The guard is code the model can't talk its way around, not a polite request in a prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the guard actually work? I measured it.
&lt;/h3&gt;

&lt;p&gt;I wrote 12 short &lt;strong&gt;fictional&lt;/strong&gt; memories with a few certain details each, and hedges in some ("I don't remember what music she liked"). Each one went through both the plain prompt and Ofrenda Keeper, once each, on the same model. Then an AI coding assistant labeled every detail against the source text, using a fixed rubric in the repo: grounded, embellished, invented, or generic decoration. I reviewed every label and the underlying result files before publishing the numbers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Unguarded prompt&lt;/th&gt;
&lt;th&gt;Ofrenda Keeper&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claims about the person&lt;/td&gt;
&lt;td&gt;167&lt;/td&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Invented claims&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;83 (49.7%)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cards with ≥1 invented claim&lt;/td&gt;
&lt;td&gt;11 of 11 that parsed&lt;/td&gt;
&lt;td&gt;0 of 12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Misattributed (right words, wrong person)&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;2 (3.3%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key details captured&lt;/td&gt;
&lt;td&gt;87.5%&lt;/td&gt;
&lt;td&gt;91.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Follow-up questions per card&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seconds per card on CPU&lt;/td&gt;
&lt;td&gt;26.1&lt;/td&gt;
&lt;td&gt;12.6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The unguarded cards also had 110 generic decorations (candles, marigolds, "Unknown"). I didn't count those as lies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it still fails:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two misattributions got through.&lt;/strong&gt; In one memory, "I always carried books" became the &lt;em&gt;neighbor&lt;/em&gt; carrying books. In another, Lucía carrying "my mother" became "her mother". Word overlap can't tell who did what, so the card still needs a human read-through. I say so in the UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is sometimes too strict.&lt;/strong&gt; "He liked jazz, but I can't remember which records" got the jazz held back entirely, because the sentence contains a hedge. That costs you one extra question, not a false memory, which is the trade-off I want here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first version was useless.&lt;/strong&gt; It captured only 37.5% of key details, because Gemma invented its own field names ("activities") and my code silently dropped them. Mapping field names fixed that. Better stemming then cut over-strict rejections from 9 to 3. All three result files are in the repo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Twelve memories is a small test. The assistant applied the rubric to every detail; I reviewed those labels and am responsible for the numbers. The rubric, the raw outputs, and every label are in &lt;a href="https://github.com/hongpengtan0-max/ofrenda-keeper/tree/main/eval" rel="noopener noreferrer"&gt;&lt;code&gt;eval/&lt;/code&gt;&lt;/a&gt; for anyone who wants to check the work.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The stories stay home.&lt;/strong&gt; Memories of someone who died are the most private data I can think of. Here they go into a SQLite file on your own disk and a model running on your own CPU. Nothing is uploaded, and nothing becomes training data for someone else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works with the lights off.&lt;/strong&gt; Once the GGUF is downloaded, the whole thing runs offline. That suits sitting with a grandparent who has no Wi-Fi, or an altar that should outlast any startup's free tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could put the guard in the right place.&lt;/strong&gt; Because the model and the orchestration are open, the fabrication check lives in my code, between the model and the card. With a closed chat product, I could only ask nicely in a prompt, and the unguarded column above shows how that goes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run.&lt;/strong&gt; That matters for something you might open once a year, every November.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's swappable.&lt;/strong&gt; Change &lt;code&gt;OFRENDA_MODEL&lt;/code&gt; to a bigger Gemma when you have the RAM. The eval tells you whether it got better or just more confident.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AI assistance disclosure
&lt;/h2&gt;

&lt;p&gt;I built this with an AI coding assistant. It wrote most of the code, the eval scripts, the demo recording script, and the first draft of this post. It also applied the eval rubric in the repo to label every unguarded and guarded detail against the source text (277 unguarded details; 61 guarded details plus verifier rejections). I directed the project, reviewed every label and the result files against that rubric, and am responsible for what is published here. All test memories are fictional, and no real person's story was used.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>mastra</category>
    </item>
    <item>
      <title>GigPitch: I lost 15 logo contests in a row, so I built a local AI that writes my bids</title>
      <dc:creator>hongpengtan0-max</dc:creator>
      <pubDate>Thu, 15 Oct 2026 08:06:49 +0000</pubDate>
      <link>https://dev.to/hongpengtan0max/gigpitch-i-lost-15-logo-contests-in-a-row-so-i-built-a-local-ai-that-writes-my-bids-1m2j</link>
      <guid>https://dev.to/hongpengtan0max/gigpitch-i-lost-15-logo-contests-in-a-row-so-i-built-a-local-ai-that-writes-my-bids-1m2j</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Since October 4, I've been trying to earn my first money as a freelance logo designer on 猪八戒 (Zhubajie), China's largest gig marketplace. I entered &lt;strong&gt;15 logo design contests (比稿)&lt;/strong&gt; and won &lt;strong&gt;zero&lt;/strong&gt; of them. Total earnings: &lt;strong&gt;¥0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Looking back, the designs weren't the main problem. The problem was everything around them: reading vague briefs, guessing a price, and writing the same bid again and again in Chinese and English, often at 2 a.m. Every losing contest cost me an hour of unpaid admin work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GigPitch&lt;/strong&gt; is the tool I wish I'd had from day one. You paste a gig brief from 猪八戒, Upwork, Fiverr, or anywhere else, and about 20 seconds later you get:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Brief analysis&lt;/strong&gt;: brand, industry, style keywords, whether VI or packaging is needed, urgency, and budget&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A quote suggestion&lt;/strong&gt;: calculated by transparent rules, with every line of the reasoning shown, and a warning when the client's budget is below it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bilingual bid text&lt;/strong&gt; in 中文 and English, citing &lt;em&gt;my own&lt;/em&gt; most relevant portfolio cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A deliverables checklist&lt;/strong&gt; (formats and versions), so scope is clear before the client says yes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three logo concept prompts&lt;/strong&gt; to kick off the actual design&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Who would pay for it?&lt;/strong&gt; Solo designers and small studios who bid on many small jobs. Saving 30 to 45 minutes per bid is worth far more than a small monthly fee, and because the tool runs locally, it costs almost nothing to operate.&lt;/p&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/AVw0skfFAnE" width="710" height="399"&gt;
  &lt;/iframe&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%2Flre5hf3g99xgrrhxoqk1.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%2Flre5hf3g99xgrrhxoqk1.png" alt="GigPitch result for a coffee-shop brief" width="800" height="953"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Futz5p1nz7j8gt0qsyop4.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%2Futz5p1nz7j8gt0qsyop4.png" alt="GigPitch result for an English yoga-studio brief" width="800" height="1061"&gt;&lt;/a&gt;&lt;/p&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/hongpengtan0-max" rel="noopener noreferrer"&gt;
        hongpengtan0-max
      &lt;/a&gt; / &lt;a href="https://github.com/hongpengtan0-max/gigpitch" rel="noopener noreferrer"&gt;
        gigpitch
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Local open-source AI bid assistant for freelance designers (Mastra + Gemma)
    &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;GigPitch 🎯&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;A local bid assistant for freelance designers.&lt;/strong&gt; Paste a gig brief from 猪八戒, Upwork, Fiverr, or anywhere else. GigPitch returns:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Brief analysis&lt;/strong&gt;: brand, industry, style keywords, whether VI is needed, urgency, budget&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quote suggestion&lt;/strong&gt;: computed by transparent pricing rules (not invented by the model), with the reasoning shown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bilingual bid text&lt;/strong&gt;: one Chinese and one English draft, citing your &lt;em&gt;own&lt;/em&gt; matched portfolio cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deliverables checklist&lt;/strong&gt;: file formats and versions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logo prompt ideas&lt;/strong&gt;: three concepts for an image generator&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Everything runs &lt;strong&gt;on your own machine&lt;/strong&gt;: &lt;a href="https://ai.google.dev/gemma" rel="nofollow noopener noreferrer"&gt;Gemma 3&lt;/a&gt; (open weights) served by &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;, orchestrated by a &lt;a href="https://mastra.ai" rel="nofollow noopener noreferrer"&gt;Mastra&lt;/a&gt; workflow. No API keys, no per-token bills, and client briefs never leave the machine.&lt;/p&gt;
&lt;p&gt;Built for the DEV × Hacktoberfest 2026 Open-Source AI Challenge, Week 2 ("Side Hustle").&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Architecture&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;brief ─▶ [Mastra step: analyze]  Gemma 3 → JSON (tolerant parser + rule guards)
      ─▶ [Mastra step:&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/hongpengtan0-max/gigpitch" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. Run it with &lt;code&gt;ollama pull gemma3:1b &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; npm start&lt;/code&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; Gemma 3 (open weights), run &lt;strong&gt;locally with Ollama&lt;/strong&gt;. I used &lt;code&gt;gemma3:1b&lt;/code&gt; because my box only had about 3 GB of free RAM, and it runs CPU-only at roughly 44 tokens per second. With more memory, switching to &lt;code&gt;gemma3:4b&lt;/code&gt; is a single environment variable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration: Mastra.&lt;/strong&gt; GigPitch is a Mastra &lt;code&gt;createWorkflow&lt;/code&gt; with three &lt;code&gt;createStep&lt;/code&gt;s around a Mastra &lt;code&gt;Agent&lt;/code&gt; backed by Gemma (via &lt;code&gt;ollama-ai-provider-v2&lt;/code&gt;):

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;analyze&lt;/code&gt;: Gemma extracts structured JSON from the brief.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;quote&lt;/code&gt;: a &lt;strong&gt;deterministic&lt;/strong&gt; pricing function plus retrieval from my portfolio file (&lt;code&gt;cases.md&lt;/code&gt;). I deliberately kept money out of the model's hands.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;write&lt;/code&gt;: four Gemma calls in parallel (Chinese bid, English bid, checklist, prompts).&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Making a 1B model reliable.&lt;/strong&gt; Small models return almost-valid JSON (I got a stray curly quote &lt;code&gt;”&lt;/code&gt; that broke &lt;code&gt;JSON.parse&lt;/code&gt;) and sometimes miss obvious facts. The fixes:

&lt;ul&gt;
&lt;li&gt;a tolerant, field-by-field JSON recovery parser&lt;/li&gt;
&lt;li&gt;rule-based guards for VI, urgency, budget (including &lt;code&gt;$&lt;/code&gt; budgets converted to ¥), and brand names in 「」 or quotes&lt;/li&gt;
&lt;li&gt;prompt rules plus post-filters: no self-praise like "经验丰富" or "experienced", no Chinese characters in the English bid, prices in the right currency, one &lt;code&gt;- [ ]&lt;/code&gt; checklist format, no &lt;code&gt;[Client Name]&lt;/code&gt; placeholders, and no claims that drafts already exist&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI:&lt;/strong&gt; one HTML file and a small Express endpoint. &lt;code&gt;npm test&lt;/code&gt; runs the whole workflow end to end.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost decides whether the business works.&lt;/strong&gt; My actual revenue so far is ¥0. A side hustle that loses money on API calls before it lands its first client is not a side hustle. With open weights on my own CPU, each extra bid costs nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client data stays private.&lt;/strong&gt; Briefs include unreleased brand names and business plans. Nothing leaves the machine, which I can tell clients truthfully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No lock-in, works across borders.&lt;/strong&gt; I'm moving between China and the US, and not every closed API works or bills smoothly in both places. Ollama + Gemma + Mastra works the same anywhere and keeps working even if a vendor changes its pricing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small models are good enough when the system is designed well.&lt;/strong&gt; With deterministic pricing and rule guards, a 1B open model handles a real niche task, and I can fine-tune it later on my own winning bids.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Mastra&lt;/strong&gt;: the whole pipeline is a Mastra workflow (&lt;code&gt;createWorkflow&lt;/code&gt; / &lt;code&gt;createStep&lt;/code&gt;) around a Mastra &lt;code&gt;Agent&lt;/code&gt; on an open model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt;: Gemma 3 runs locally through Ollama and handles every language task.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Built with help from an AI coding assistant; I directed the design and tested the outputs.&lt;/em&gt;&lt;/p&gt;

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