DEV Community

Ibnu Syifa Takbir Adha
Ibnu Syifa Takbir Adha

Posted on

Ofrenda: a private Gemma remembrance builder that is not allowed to invent memories

Hacktoberfest Open-Source AI Challenge: Ghost in the Machine Submission 👻🌼

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine

What I Built

I read "Ghost in the Machine" through the Día de los Muertos side of the prompt: the machine is not haunted, it is helping you remember. Ofrenda is a small command-line tool that builds a written remembrance for someone you miss. You give it facts (their name, favorite foods, songs, a story) in a JSON file. A local open-weight model writes a short, warm paragraph from those facts, and the tool prints a Markdown "ofrenda" with the paragraph plus the foods, songs and stories listed underneath, like items on an altar.

The interesting part is what the tool refuses to do. A language model asked to write about a dead grandmother will happily add a hometown, a year or a nickname that it made up, and those would be fabricated memories about a real person. So plain Python checks the paragraph against your facts and rejects any name, place or number that you did not provide, falling back to a simple, faithful sentence instead.

The example in the repo, "Abuela Rosa", is a made-up person for the demo. I did not build this for a real relative, so there is no "the moment it landed" story here and I won't invent one.

Demo

ollama pull gemma3
python3 ofrenda.py profile.json > ofrenda.md
Enter fullscreen mode Exit fullscreen mode

With the model unavailable, which is the only case I could run, the fallback produces this:

# Ofrenda for Abuela Rosa

We remember Abuela Rosa, who loved tamales and hot chocolate and sang along to La Llorona.

## Foods
- tamales
- hot chocolate

## Songs
- La Llorona

## Stories
- she taught me to fold corn husks at the kitchen table
Enter fullscreen mode Exit fullscreen mode

I have not run it against a real Gemma instance, so the quality of the generated paragraph is untested. The tests below use a scripted stand-in for the model.

Code

Stdlib-only Python, one file plus a test.

ofrenda.py:

#!/usr/bin/env python3
"""Ofrenda: a private, offline remembrance builder.

You give it facts about someone you miss (foods, songs, stories). A local open-weight
model (Gemma via Ollama) writes a short remembrance from those facts. Plain Python then
refuses anything the model INVENTED (names, places, years not in your facts) and falls
back to a plain, faithful text. A remembrance must never contain made-up memories.
Stdlib only.
"""
import json, re, sys, urllib.request

OLLAMA, MODEL = "http://localhost:11434/api/generate", "gemma3"

def ask_model(prompt):
    body = json.dumps({"model": MODEL, "prompt": prompt, "stream": False}).encode()
    req = urllib.request.Request(OLLAMA, body, {"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=300) as r:
        return json.loads(r.read())["response"].strip()

def facts_text(p):
    return " ".join([p["name"], p.get("relation", "")] + p.get("foods", []) + p.get("songs", []) + p.get("stories", []))

def words(s):
    return re.findall(r"[A-Za-zÀ-ÿ']+|\d+", s)

def invented(text, profile):
    """Names, places or numbers in `text` that do not appear in the profile facts."""
    known = {w.lower() for w in words(facts_text(profile))}
    out = []
    for sent in re.split(r"(?<=[.!?])\s+", text):
        toks = words(sent)
        for i, w in enumerate(toks):
            if w.isdigit() and w not in known:
                out.append(w)
            elif i > 0 and w[0].isupper() and w.lower() not in known and w != "I":
                out.append(w)               # capitalised mid-sentence = likely a name or place
    return sorted(set(out))

def prompt_for(p):
    return (f"Write a warm 3-sentence remembrance of {p['name']} ({p.get('relation','')}). "
            "Use ONLY these facts. Do not invent names, places, dates or events.\n"
            f"Favorite foods: {', '.join(p.get('foods', []))}\n"
            f"Songs: {', '.join(p.get('songs', []))}\n"
            f"Stories: {' | '.join(p.get('stories', []))}")

def fallback(p):
    bits = []
    if p.get("foods"): bits.append("loved " + " and ".join(p["foods"]))
    if p.get("songs"): bits.append("sang along to " + " and ".join(p["songs"]))
    return f"We remember {p['name']}, who " + (" and ".join(bits) if bits else "is in our hearts") + "."

def remembrance(p, ask=ask_model):
    try:
        text = ask(prompt_for(p))
    except Exception:
        return fallback(p), "model unavailable"
    bad = invented(text, p)
    return (fallback(p), f"rejected invented details: {bad}") if bad else (text, "ok")

def render(p, ask=ask_model):
    text, status = remembrance(p, ask)
    lines = [f"# Ofrenda for {p['name']}", "", text, ""]
    for title, key in (("Foods", "foods"), ("Songs", "songs"), ("Stories", "stories")):
        if p.get(key): lines += [f"## {title}"] + [f"- {x}" for x in p[key]] + [""]
    return "\n".join(lines), status

def main():
    p = json.load(open(sys.argv[1] if len(sys.argv) > 1 else "profile.json"))
    md, status = render(p)
    print(md); print(f"<!-- remembrance status: {status} -->", file=sys.stderr)

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

profile.json:

{"name": "Abuela Rosa", "relation": "grandmother",
 "foods": ["tamales", "hot chocolate"], "songs": ["La Llorona"],
 "stories": ["she taught me to fold corn husks at the kitchen table"]}
Enter fullscreen mode Exit fullscreen mode

test_ofrenda.py (passes, using a fake model):

import json
import ofrenda as o
P = json.load(open("profile.json"))

good = "Abuela Rosa filled the kitchen with tamales and hot chocolate. I still hear La Llorona when I fold corn husks at the table."
assert o.invented(good, P) == []

# model invents a place and a year -> flagged
bad = "Abuela Rosa grew up in Guadalajara and made tamales every year since 1987."
assert o.invented(bad, P) == ["1987", "Guadalajara"]

# end to end: good text is kept, invented text is replaced by the faithful fallback
text, status = o.remembrance(P, ask=lambda p: good)
assert text == good and status == "ok"
text, status = o.remembrance(P, ask=lambda p: bad)
assert "Guadalajara" not in text and "1987" not in text and "tamales" in text and "rejected" in status

# model down -> fallback, never a crash
def boom(p): raise RuntimeError("down")
text, status = o.remembrance(P, ask=boom)
assert text.startswith("We remember Abuela Rosa") and status == "model unavailable"

md, _ = o.render(P, ask=lambda p: good)
assert md.startswith("# Ofrenda for Abuela Rosa") and "- La Llorona" in md
print("all tests passed")
Enter fullscreen mode Exit fullscreen mode

How I Built It

  • Open-weight model: Gemma through Ollama's local HTTP API. The prompt tells it to use only the supplied facts, but I do not rely on it obeying.
  • Fabrication check: the paragraph is split into sentences. Any number that is not in your facts, and any capitalized word in mid-sentence that is not in your facts (a likely name or place), marks the whole paragraph as invented. It is then replaced by a plain sentence built directly from your facts.
  • Fail faithful: if the model is down, the tool does not crash; it prints the fallback and the list of facts.
  • Zero dependencies, and no network call except to your own Ollama on localhost.

Known limits, and they matter: this is a heuristic. It catches invented names, places and numbers, but not an invented event written in plain lowercase words ("she once won a baking contest"), and it can wrongly reject a harmless capitalized word such as a holiday name. A stricter version would ask the model for a list of claims and verify each one. I would not treat this as proof that nothing was made up, only as a guard against the most common kind of invention.

Why Does Open Innovation Matter?

Family stories are some of the most private data there is: names, photos, the way someone laughed. I would not want to paste my grandmother's recipes and stories into a service I do not control, or build something I love on an API that might change its terms or disappear. With an open-weight model running locally, the stories stay on the laptop, the tool works with the lights and the internet off, and the Markdown file it produces is mine to keep forever. Swapping the model is one string, and because the fabrication check lives in plain code, a small local model is good enough.

My Agent Session

Not included.

The Spirit of It

I have no real moment to show, since I have not used this with a real family's stories. The example profile is fictional. What I can point to is the design choice: for a remembrance, a boring true sentence is better than a beautiful false one.

Prize Categories

Best Use of Gemma: Gemma, served locally via Ollama, writes the remembrance at the core of the project.

Top comments (0)