Plan: Local-First iMessage β Calendar Agent (Gradio + llama.cpp + fine-tuned Gemma + an OpenBMB MiniCPM-planned agent)
Who this is for
One named person: a busy parent whose kid's school/activity events are buried in a noisy class group chat (picture day, the practice that moved, the RSVP). They read it once, mean to add it later, and miss it. Success = their day measurably improves β events captured from the chat, conflicts caught against their calendar, minutes saved β with zero setup: paste the thread or a screenshot from a phone browser. The local-LLM / fine-tune work below is a means to better extraction, not the point; the app must deliver value with no GPU (stub agent) first.
Context
You want an agent that reads iMessage-style threads, understands the conversation, and turns them
into calendar events/reminders β exposed through a custom Gradio UI deployed as a Hugging Face Space.
Two local models share the work: our fine-tuned Gemma does the reading (thread β validated
ActionPlan), and an OpenBMB MiniCPM planner does the orchestrating β the brain behind Run the agents,
driving the Space's own MCP tools (extract_events β check_conflicts β make_ics) as a visible
multi-step agent. The build competes in the Backyard AI track (general and OpenBMB prizes are
awarded per track) and satisfies the quests secondary to the user story above: Off the Grid (no
cloud AI APIs, local-first), Well-Tuned (a fine-tuned model on HF), Off-Brand (custom UI),
and Llama Champion (both Gemma and MiniCPM are served through llama.cpp).
Feasibility verdict: YES, with one re-architecture
The request as literally worded has two impossibilities, both solvable:
- No app or cloud can read iMessage on iOS. Apple exposes no API for iMessage/SMS content.
β Solved: you have a Mac. iMessages sync to
~/Library/Messages/chat.db; a small local collector reads it. This is the only supported path and it keeps data local ("off the grid"). - A model cannot "run on your phone," and a HF Space runs in the cloud, not on-device. β Solved: "on my phone" = used from your phone's browser. The Space does its own llama.cpp inference and calls no external AI service, so "hosted Space" and "off the grid" reconcile.
Confirmed decisions:
- Ingestion: Mac collector reading
chat.db. - Calendar output: local
.icsfiles first (strictly off-grid), with an optional Google Calendar push toggle as a bonus. - Extraction model: fine-tune Gemma, serve as GGUF via llama.cpp (production serves the
E4B edge fine-tune,
build-small-hackathon/gemma-4-cal-gguf). - Agent planner: OpenBMB MiniCPM (
openbmb/MiniCPM4.1-8B-GGUF, Q4; the 1B variant is a config switch) on a second llama-server β it plans, the MCP tools execute, every step visible.
Architecture
βββββββββββ Your Mac (local) βββββββββββ βββββββββ Hugging Face Space (Docker) βββββββββ
β collector.py (Full Disk Access) β HTTPS β Gradio (custom theme/CSS) ββ Off-Brand β
β β’ polls chat.db for new messages β +token β β β
β β’ parses text / attributedBody ββββββββββΆβ FastAPI /ingest βββΆ extraction pipeline β
β β’ POSTs new msgs to Space /ingest β β β β
βββββββββββββββββββββββββββββββββββββββββ β llama.cpp (llama-cpp-python) ββ Llama Champ β
β running YOUR fine-tuned gemma-4-31B GGUF β
View/approve from phone browser ββββββββββββββββΆβ β ββ Off the Grid (local) β
β JSON events β pydantic validate β
β ββββΆ .ics file (download) β
β ββββΆ optional Google Calendar push β
ββββββββββββββββββββββββββββββββββββββββββββββββ
Flow: messages β extraction prompt β model emits structured JSON of candidate events β
validated β shown in UI for review β user approves β .ics generated (and/or pushed to GCal).
Run the agents runs the same flow agentically: an OpenBMB MiniCPM planner (second local
llama-server, OpenAI-compatible) consumes the Space's own MCP tool surface β
extract_events β check_conflicts β make_ics β through smolagents, so the pipeline above is
demonstrated as multi-step tool use over the public tool contract, with the planner's trace on
screen (server/orchestrator.py). Stub/CI falls back to a scripted planner so the tab always works.
Components
1. Mac-side iMessage collector (collector/collector.py)
- Reuse, don't reinvent the DB parsing. Modern macOS stores message text in the
attributedBody(NSAttributedString) blob, not always thetextcolumn. Use the battle-testedimessage-exporter(ReagentX, Rust) or the Pythonimessage_readerlib rather than hand-rolling SQL. If hand-querying: joinmessageβ¨handleβ¨chat_message_joinβ¨chat, track last seenROWID, poll on an interval. - Requires Full Disk Access for the running process (System Settings β Privacy & Security).
- Sends only new messages to the Space
/ingestendpoint over HTTPS with a shared bearer token. - Config: which chats to watch, poll interval, Space URL, token (
.env, never committed).
2. HF Space backend (app.py, server/)
- Docker SDK Space (
README.mdfrontmatter:sdk: docker,app_port: 7860). - llama.cpp loads the fine-tuned GGUF and serves chat completions β satisfies Llama Champion; no external AI call satisfies Off the Grid.
- Agent orchestrator (
server/orchestrator.py): the OpenBMB MiniCPM planner behind Run the agents (its own llama-server) drives the Space's MCP tools as a multi-step agent β the OpenBMB per-track prize case, and the same extraction pipeline exercised through the public tool contract rather than private imports. /ingest(FastAPI, mounted alongside Gradio) receives messages, runs the extraction prompt, returns candidate events; results surface in the Gradio UI for review.- Compute: Q4_K_M GGUF of a 31B β 18β20 GB β does not fit the free CPU tier (16 GB / 2 cores). Serve on a GPU: ZeroGPU (free, H200/70 GB β but cold GGUF load per acquisition; document the caveat) or a paid GPU Space (e.g. L4/L40S) for a smooth always-warm demo. See Fallback.
3. Fine-tuning pipeline (training/)
- Task: conversation snippet β strict JSON list of events
{title, start, end, location, attendees, reminder_minutes, notes}. - Data: build a synthetic instruction dataset (~500β2000 examples) of realistic chat threads paired with the target JSON. Generation/augmentation for training data can use any tooling offline β the "no cloud API" rule applies to the running app's inference, not dataset prep. Include hard cases: relative dates ("next Thurs"), ranges, no-event chitchat (empty list), timezones, multiple events per thread.
- Method: QLoRA via Unsloth (Qwen3-0.6B GRPO experience applies), 4-bit, r=16, 1β3 epochs. 31B QLoRA needs an A100/H100 80 GB (Colab Pro+/RunPod/Lambda, ~hours).
- Export: merge LoRA β
convert_hf_to_gguf.py(llama.cpp) βllama-quantizeto Q4_K_M β publish GGUF to your HF repo (satisfies Well-Tuned). Space downloads it at startup viahuggingface_hub.
4. Custom Gradio UI (ui/, static/) β Off-Brand
gr.Blockswith a customgr.themes.Base(...)palette + injectedcss=(custom fonts, layout, cards) to push well past the default look.- Screens: connection/status, incoming-message feed, review queue (edit candidate events
inline, approve/reject), download
.ics, optional "Push to Google Calendar" toggle, settings.
5. Calendar output (calendar_out/)
.ics(default, off-grid): generate with theicalendarlib; offer as a download in the UI.- Google Calendar (optional bonus):
google-api-python-clientOAuth; behind a toggle so the off-grid demo path stays pure. Clearly labeled as the one optional cloud touchpoint.
Hackathon requirement mapping
| Track | How it's satisfied |
|---|---|
| Off the Grid (local-first, no cloud AI APIs) | All inference is local llama.cpp in the Space; data originates on your Mac; .ics is the default output. |
| Well-Tuned (fine-tuned model on HF) | QLoRA fine-tune of gemma-4-31B-it, GGUF published to your HF repo. |
| Off-Brand (custom UI) | Custom Gradio theme + CSS, not the stock look. |
| Llama Champion (llama.cpp) | Inference via llama-cpp-python. |
| Gradio app on HF Space | Docker Space serving Gradio + FastAPI /ingest. |
Build phases
- Hero path (no GPU): Docker Space with custom-themed Gradio + the stub extractor β paste /
"Try a sample" / screenshot β review β
.icsdownload, working end-to-end on a phone browser. This is the parent's whole experience and must stand alone with no model. - Measure impact: persisted This week panel (events captured, conflicts caught, minutes
saved) via
server/impact.py, recorded when the parent exports. Proves their day got better. - Accuracy upgrade (optional): wire
llama-cpp-pythonwith a communitygemma-4-31B-itGGUF on a GPU Space; swap the stub for the model + JSON-schema prompt + pydantic validation. - Fine-tune (optional): dataset β Unsloth QLoRA β GGUF β publish to HF β point the Space at it.
- Optional auto-feed: Mac
collector.pyreadingchat.dbβ POST/ingest(power users only).
Verification
- End-to-end (stub, phase 1): open Space in phone browser β tap Try a sample (or paste a
chat) β event appears in review queue β download
.icsβ import to a calendar, confirm date/time. - Impact (phase 2): after exporting, Activity β This week shows events captured and time
saved > 0; restart the app (same
IMPACT_PATH) and confirm the weekly numbers persist while the live tiles reset.minutes_savedis a stated estimate (IMPACT_MIN_PER_EVENT=8,IMPACT_MIN_PER_CONFLICT=15, env-overridable), not a measurement. - Collector (phase 2): send yourself a test iMessage ("lunch Tuesday 1pm") β confirm it reaches
/ingestand surfaces in the feed. - Model (phase 3+): curated eval set of chats with known expected events; measure JSON validity rate + field accuracy (esp. relative-date resolution); confirm empty-list on non-event chats.
- llama.cpp: confirm the Space logs show llama.cpp loading your GGUF, no external AI calls.
Risks & fallbacks
- 31B serving cost/latency. Q4 31B needs a GPU; ZeroGPU has cold-load + quota friction, paid GPU has cost. Fallback: fine-tune Gemma 4 E4B (edge variant) β runs on free CPU tier / fast on small GPU, far cheaper to fine-tune, and arguably more on-theme for "local-first." Keep 31B as the headline, E4B as the safety net for a reliable live demo.
chat.dbschema /attributedBody. Mitigated by usingimessage-exporter/imessage_reader.- Full Disk Access must be granted to the collector's process or reads return empty.
- Privacy: the autonomous Mac-collector path sends messages to the Space (token-gated); the
hero phone-paste path keeps data client-side (calendar tokens live in the browser, nothing
persists server-side). The Space now lives in the public
build-small-hackathonsubmission org, so the source is public β but user data still never lands on the server. - Relative-date accuracy is the main quality risk β pass the current datetime into the prompt and weight the dataset toward relative-date examples.