cd /news/ai-safety/marigold-a-private-ofrenda-that-catc… · home › topics › ai-safety › article
[ARTICLE · art-161304] src=dev.to ↗ pub= topic=ai-safety verified=true sentiment=↑ positive

Marigold — a private ofrenda that catches the ghosts AI writes into our memories

A developer built Marigold, an offline, single-file ofrenda (altar) generator that runs Gemma 3 4B locally via Ollama to write remembrances from family photos and user-supplied memories. Its core feature is a "ghost check" — roughly 120 lines of plain C# string matching in Ghosts.cs that flags any name, place, number, month or weekday in the model's output that does not appear in the user's input or the photos, catching hallucinated details before they end up in a memorial. The app is a .NET 8 ASP.NET Core minimal API bound to 127.0.0.1 with no third-party NuGet packages, embeds photos as data URIs into one printable HTML file, and ships with 33 self-tests run across Windows, macOS and Linux.

by read5 min views1 publishedOct 31, 2026
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine](https://dev.to/challenges/hacktoberfest-week4-2026-10-26)*

For Día de los Muertos, families build an **ofrenda**: an altar with photos of the people they've lost, their favourite foods, marigolds to guide them home, and the stories that keep them close.

Marigold helps you build one on your own computer:

The photos are public-domain archive photos and the memories are fictional; with your own family photos, it's their faces on the ofrenda.

My take on "Ghost in the Machine": the scariest ghost in AI isn't a spooky one. It's the confident little invention. Ask a model to write about your grandmother and it may "remember" a town she never lived in, a year that's off by one, or a sister who never existed. In most apps that's a bug. In a remembrance, it's a ghost that rewrites someone's life.

So Marigold has a ghost check. It doesn't just tell the model to stick to the facts; it checks. Every name, place, number, month and weekday in the written text is compared against everything you wrote and everything the model saw in the photos. Anything that came from nowhere is flagged as a 👻 and highlighted, so you can remove it, or add it to your memories if it's true. Here it is catching a city that was planted in a test:

Try it: download marigold-win-x64.zip (or the macOS/Linux build) from Releases, install Ollama, run ollama pull gemma3:4b, and double-click marigold. It opens in your browser at 127.0.0.1:5180.

A private, offline ofrenda for the people you remember.

Add a few old photos and write down what you remember: what they always said, what they cooked, the song they hummed. A local open-weight model (Gemma 3 4B via Ollama) looks at each photo and writes a short remembrance using only what you gave it. Marigold then builds an ofrenda page with their photos, your words and their favourite things, saved as one HTML file you can keep, print or share.

A language model writing about someone you loved will sometimes "remember" things that never happened: a town she never lived in, a year that's wrong, a name nobody had. In a remembrance, that's the worst kind of mistake.

So Marigold doesn't just ask the model to stick to the facts. It checks. Every name, place, number, month and weekday…

Stack: .NET 8 (an ASP.NET Core minimal API) serving one offline HTML page, with Ollama running Gemma 3 4B, which reads images as well as text. There are zero third-party NuGet packages, no CDN scripts and no web fonts.

Two jobs for the model. Both go through Ollama's /api/chat:

format makes the model return caption, description and era. The prompt says to describe only what's visible and The ghost check is plain C#, not another model. A prompt is a request, not a guarantee, so I check the output with about 120 lines of string matching (Ghosts.cs):

The ofrenda outlives the app. The page is a single file with photos embedded as data URIs, no scripts, no links and no fonts to download. It prints to PDF. Photos are resized in the browser before saving (canvas → JPEG), so a 12 MB scan becomes a few hundred KB without any image library.

Private by construction. The server binds to 127.0.0.1, rejects any other host name (blocking DNS-rebinding), and needs a custom header for every change, so another website in your browser can't read or edit anything. Uploaded files are checked by their first bytes (JPEG/PNG only), and photos are only ever served by ID from Marigold's own folder.

Tests. There are 33 built-in self-tests for the ghost check, photo validation, storage and the ofrenda page, run on Windows, macOS and Linux. Then CI does the real thing: it installs Ollama and gemma3:4b, downloads a few public-domain photos from Wikimedia Commons (licence checked, credits recorded), and a Playwright test uses the app like a person would. It adds a (fictional) grandmother, uploads the photos, waits for Gemma, writes the remembrance, checks the ghost check, then opens and downloads the ofrenda.

Here's the real Gemma 3 4B, on a GitHub runner with no GPU:

Result
Photos described 4 / 4, no names guessed
Era guesses vs. the archive's dates 1944 sewing photo → "1930s–1950s" ✅, c. 1942 radio photo → "1950s" (close), 1922 homestead → "1930s–1950s" (a bit late)
Time per photo / for the remembrance ~67 s / ~42 s
Invented details in the real remembrance 0 (in both the browser run and the command-line run)

And this is what it wrote, from the family's memories, with a clean ghost check:

Family photos and memories of someone who has died are about as private as data gets. To describe a photo, a model has to see it. With a closed API, your grandmother's face, her kitchen and your memories of her would be uploaded to a server you don't control, under terms that can change. With an open-weight model, Gemma looks at them on your own laptop. That isn't a promise in a privacy policy; it's how the app is built. Turn off the Wi-Fi and it still works.

Open also matters for time, and remembrance is about time. An ofrenda should still open in twenty years. A closed service can shut down, change its prices or retire the model. Marigold's output is a plain HTML file, the model weights sit on your disk, and the code is MIT. Nothing here needs anyone's permission to keep working.

And open let me build the safety I wanted instead of hoping for it. I could pin the exact model, set temperature and seed, choose when to force a JSON schema (and when not to), and put my own check on the output. I can swap in gemma3:12b or qwen2.5vl:7b with one flag if a bigger model writes better. With a closed API I'd get whatever the model does that day.

Best Use of Gemma. Gemma 3 4B runs locally through Ollama and does both halves of the work: its vision input describes the photos, and it writes the remembrance in English, Spanish, French, Portuguese, Punjabi or Hindi. A GitHub Actions job runs the real model end to end in a browser on every push.

── more in #ai-safety 4 stories · sorted by recency
── more on @marigold 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/marigold-a-private-o…] indexed:0 read:5min 2026-10-31 · —