mini-fam / README.md
eloigil6's picture
Fix Space front-matter so the push passes HF validation
4e0b300
|
Raw
History Blame Contribute Delete
16.1 kB

A newer version of the Gradio SDK is available: 6.20.0

Upgrade
metadata
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-server directly (the scripts/serve-minicpm.sh path) 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

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

  1. A family member types a message. The UI tells the agent who is talking, so it can address them and respect their preferences.
  2. The orchestrator builds the prompt. It injects today's date, the family roster, and everyone's food preferences, then calls complete(messages, tools).
  3. 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.
  4. Each tool reads or writes a plain file. Notes β†’ Markdown, recipes β†’ Markdown + frontmatter, meal plans β†’ Markdown, calendar β†’ iCalendar, roster β†’ JSON.
  5. 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 @tool registry. Adding a capability is just adding one decorated function in a new minifam/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.GPU function 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 cuda at module import and the @spaces.GPU function 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=False in 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-style tool_calls, so agent.py is identical across backends.
  • A paid-GPU alternative. On a paid GPU Space (L4 / A10G, GPU attached for the container's life), minifam/bootstrap.py instead stands up a background ollama serve and pulls qwen3: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/client calls 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.