A newer version of the Gradio SDK is available: 6.20.0
title: MiniFam
emoji: π‘
colorFrom: yellow
colorTo: pink
sdk: gradio
sdk_version: 6.16.0
python_version: '3.12'
app_file: app.py
pinned: false
license: mit
short_description: π‘ A local AI assistant for family logistics
tags:
- track:backyard
- achievement:offgrid
- achievement:offbrand
- achievement:llama
- achievement:fieldnotes
MiniFam π‘
The family logistics assistant that stays home: notes, recipes, meal plans and a shared calendar, run by a small open model on your own machine. Built for the Build Small Hackathon.
π Badges I'm going for
MiniFam is my entry for the Build Small Hackathon. Here is the track and the badges I am submitting for:
- π‘ Backyard AI (the practical track): a small, local tool that solves a real problem for someone you know. For me, that someone is my own family β and they actually use it for notes, recipes and meal planning.
- π Off the Grid: no cloud AI. Every model runs on your own machine (Ollama / llama.cpp) or on the Space's own GPU (ZeroGPU), and your family's data lives in plain local files. Nothing is sent to a model provider.
- π¨ Off Brand: the UI is a fully custom React app served from
gradio.Server, miles past the default Gradio components. - π¦ Llama (llama.cpp): MiniFam speaks plain OpenAI-compatible HTTP, so it runs on llama.cpp's
llama-serverdirectly (thescripts/serve-minicpm.shpath) and locally through Ollama, which is built on llama.cpp. - π Field Notes: a write-up of the build and what I learned β this README, plus a longer blog post.
βΆ Live demo Β· π¬ Demo video: YouTube Β· π Blog post: dev.to
MiniFam is a warm little assistant for the chaos of family logistics. You talk to it like a person β "plan our dinners for the week, Maya is vegetarian", "save grandma's banana bread recipe", "write down that Leo's shoes are size 8 now" β and it keeps the shared notes, a recipe cookbook, a weekly meal plan that turns itself into a shopping list, and a family calendar. The model runs on your own machine (or on the Space's own GPU), and every piece of your family's data lives in plain files you can open yourself.
The story behind it
I built this while on parental leave, with a toddler who never stops moving and a baby who is still figuring out the world. People imagine parental leave as rest. It is not β it is beautiful, it is loud, and most days it is a happy little pandemonium. MiniFam became my project in the cracks of the day: twenty minutes during a nap, an hour after bedtime, something quiet and mine while the rest of the house was upside down.
The reason is simple. Family planning with one kid is a lot; with more than one it is honestly insane β two sets of food preferences, recipes you never remember, notes on every surface, appointments out of nowhere. I wanted a small assistant that takes a little of that weight off, that lives on our own computer, that I would actually trust with my kids' names and habits. Not a giant cloud product quietly collecting our family's life. And if it helps other families too, even better.
How it works
MiniFam is a gradio.Server app that serves a hand-built React frontend and exposes a small JSON API. Behind the chat is one orchestrator and a registry of tools β no agent swarm β and the model behind it is a single small open model that runs locally or on the Space's own GPU.
a family member types in the chat
β
βΌ
orchestrator (agent.py) ββ one model step + a tool loop, until it answers in plain text
β
ββββΊ complete(messages, tools) βββΊ the model:
β β’ local dev / paid-GPU Space: Ollama β Qwen3-30B-A3B
β β’ ZeroGPU Space: transformers in-process β Qwen3-30B-A3B (4-bit)
β
ββββΊ tool calls βββΊ @tool registry:
β notes Β· recipes Β· meal plan + shopping list Β· family roster Β· calendar
β
βΌ
plain, human-readable files βββΊ data/shared/notes.md Β· data/recipes/*.md Β· data/*.ics Β· data/members.json
β
βΌ
JSON βββΊ custom React UI: the reply, the "agent working" cards, and live views
A turn, step by step
- A family member types a message. The UI tells the agent who is talking, so it can address them and respect their preferences.
- The orchestrator builds the prompt. It injects today's date, the family roster, and everyone's food preferences, then calls
complete(messages, tools). - The model may call tools. If it does, MiniFam runs each one, appends the result, and loops (up to 12 rounds). Every tool error comes back as plain text, so the model can read it and recover instead of crashing.
- Each tool reads or writes a plain file. Notes β Markdown, recipes β Markdown + frontmatter, meal plans β Markdown, calendar β iCalendar, roster β JSON.
- The reply ships as JSON. Along with "agent working" cards (what it just did, with an optional Add to Google Calendar link on new events). The React UI renders them and refreshes the live views.
One model, a registry of tools (no agent swarm)
This is the architecture I am most happy with: small models fumble agent-to-agent handoffs, so MiniFam keeps it deliberately boring and sturdy.
- One orchestrator, one loop, a
@toolregistry. Adding a capability is just adding one decorated function in a newminifam/tools/module β the agent loop and the UI never change. - Backend-agnostic by design. The agent only knows
complete(messages, tools), which returns one assistant message in OpenAI shape. Two backends produce that shape (see below), so the same agent code runs on a laptop and on the Space. - Built for a model that wobbles. Explicit step-by-step instructions ("list the recipes, count the meals, then save the plan"), guards against blank / think-only replies, and tool errors surfaced as text the model can recover from.
Running the model on ZeroGPU
The live Space runs the whole thing on ZeroGPU, with the model loaded in-process via transformers (minifam/llm_zerogpu.py).
- In-process, not a server. ZeroGPU only attaches a GPU inside an
@spaces.GPUfunction and releases it afterwards, so there is no persistent server to host (Ollama / llama.cpp). MiniFam grabs the GPU for one generation per agent round; tool execution between rounds runs on the CPU and holds no GPU. - 4-bit, so a 30B fits. bitsandbytes
nf4(double-quant, bf16 compute) shrinks Qwen3-30B-A3B enough to fit the 48GB "large" tier. It is a mixture-of-experts, so only about 3B parameters are active per token β big on paper, light to run. - Loaded at import time. The model is placed on
cudaat module import and the@spaces.GPUfunction is registered at startup β the documented ZeroGPU pattern (a CUDA-emulation layer makes.to('cuda')work before a GPU is attached). - Thinking off.
enable_thinking=Falsein the chat template skips<think>blocks to save GPU seconds and quota. - Tool calls parsed by hand. Qwen's template emits
<tool_call>{...}</tool_call>; MiniFam parses those out of the generated text into OpenAI-styletool_calls, soagent.pyis identical across backends. - A paid-GPU alternative. On a paid GPU Space (L4 / A10G, GPU attached for the container's life),
minifam/bootstrap.pyinstead stands up a backgroundollama serveand pullsqwen3:30bβ the exact same setup as the dev Mac, no code changes, with a friendly "warming up" message while the ~18 GB model downloads.
The frontend (all hand-built, no default Gradio components)
- A fully custom React SPA (React 18 + Babel standalone over a CDN), served straight from
gradio.Server. - Screens: a chat with live "agent working" cards, the recipe box, the weekly meal plan + auto shopping list, the shared family notes, the calendar (events plus checkable tasks), and a settings / onboarding flow for the roster and food preferences.
@gradio/clientcalls the JSON endpoints, and each view refreshes live after a turn.- Served no-cache with
?v=asset versioning, so edits show up on reload.
API
| Endpoint | What it does |
|---|---|
chat(message, member, history) |
One agent turn. Returns {reply, tools, history}, where tools is the "agent working" cards. |
recipes / notes / meal_plan / calendar / members |
Live JSON for each screen. |
add_member / remove_member |
Add or remove a family member from the UI. |
set_task_done / remove_calendar_item |
Check off or delete a calendar item. |
GET / |
The React app. /app/* serves the frontend assets. |
Tech stack
- Python 3.12
- Gradio 6.16.0 (
gradio.Server, FastAPI / Starlette underneath) - openai β the client for the OpenAI-compatible backend
- python-frontmatter β recipes as Markdown + frontmatter
- React 18 (CDN + Babel standalone) and
@gradio/client - Ollama running Qwen3-30B-A3B locally β or transformers + bitsandbytes on ZeroGPU
- Storage: plain Markdown + iCalendar (
.ics) + JSON files
Run it locally
# 1. clone
git clone <repo-url> && cd mini-fam
# 2. the model β Ollama running Qwen3-30B-A3B (the default backend)
ollama pull qwen3:30b # served at http://localhost:11434
# 3. run with uv (deps come from pyproject.toml; uv run syncs the env on first use)
uv run python app.py
# open http://localhost:7860
The defaults already point at Ollama (localhost:11434, qwen3:30b), so nothing else is needed. All of the family's data is written to data/ as plain files you can open and edit by hand.
Smaller / experimental β the tiny MiniCPM3-4B on llama.cpp (lighter, but too weak for reliable orchestration in the time I had; see What I learned):
brew install llama.cpp
./scripts/serve-minicpm.sh # OpenAI-compatible server on :8080
MINIFAM_LLM_BASE_URL=http://localhost:8080/v1 MINIFAM_MODEL=minicpm3-4b uv run python app.py
Environment knobs
| Variable | Default | What it does |
|---|---|---|
MINIFAM_BACKEND |
openai |
openai (Ollama / llama.cpp HTTP) or zerogpu (in-process transformers) |
MINIFAM_LLM_BASE_URL |
http://localhost:11434/v1 |
the OpenAI-compatible endpoint |
MINIFAM_LLM_API_KEY |
ollama |
any non-empty value (local servers ignore it) |
MINIFAM_MODEL |
qwen3:30b |
model tag (Ollama) or HF repo id (zerogpu) |
MINIFAM_DATA_DIR |
./data |
where the family's files live |
MINIFAM_ZEROGPU_4BIT |
1 |
4-bit quantize on ZeroGPU so the 30B fits the 48GB tier |
MINIFAM_BOOTSTRAP_OLLAMA |
(unset) | on a paid GPU Space, auto-install + serve Ollama in the background |
Project layout
| Path | What's inside |
|---|---|
app.py |
gradio.Server entry: the React app + the JSON API |
minifam/agent.py |
the orchestrator β one model step + a tool loop |
minifam/llm.py |
backend seam: complete(messages, tools) β openai or zerogpu |
minifam/llm_zerogpu.py |
in-process transformers backend for ZeroGPU (4-bit Qwen3-30B-A3B) |
minifam/bootstrap.py |
stands up Ollama inside a paid GPU Space |
minifam/tools/ |
the tool registry β notes, recipes, meals, members, calendar (one each) |
minifam/config.py |
central config + the family roster |
minifam/storage.py Β· serialize.py Β· calendar_store.py Β· web.py |
Markdown store, JSON projections, iCalendar, recipe-URL fetcher |
frontend/ |
index.html + app/*.jsx (custom React) + minifam.css |
data/ |
the family's files: shared/notes.md, recipes/*.md, *.ics, members.json |
scripts/ |
serve-minicpm.sh + the Qwen tools chat template (the experimental tiny path) |
What I learned
A small model will not call your tools by accident. I really wanted to honor the "small" spirit, so I started on a tiny 4B model (OpenBMB's MiniCPM3) on llama.cpp. It simply would not call any tool β it just described what it would do, like a very polite intern who never presses the button. The culprit was its GGUF shipping a chat template with no tool-calling support, so I lent it Qwen2.5's <tool_call> template; I also had to turn flash attention off (no Metal kernel for its head dims) and curl the weights by hand (the downloader hung). A very honest "go read how the thing actually works" couple of days. βοΈπ
Know when to stop chasing "small" and ship what works. Even once the 4B was calling its tools, there was simply no way to make the orchestration reliable on a model that size in the time I had left β it lost the thread on multi-step jobs (a whole week of meals that respects everyone's preferences) and got relative dates wrong. With a newborn in the house, I made the call every tired parent eventually makes: I shipped the thing that worked. I moved to Qwen3-30B-A3B β still under the hackathon's 32B cap, still running on my own machine β and it just worked.
One brain with a toolbox beats a crew of agents. No agent swarm: one orchestrator, one loop, a registry of tools. Every handoff between agents is one more chance to get confused, and that fragility only grows the smaller your model is. Adding a feature is just adding one more @tool β the loop never changes.
Credits & license
- Models: Qwen3-30B-A3B (Qwen) via Ollama or
transformers; MiniCPM3-4B (OpenBMB) for the experimental llama.cpp path. - License: MIT.
