| # 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: |
|
|
| 1. **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"). |
| 2. **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 `.ics` files 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 the `text` column. Use the battle-tested |
| **`imessage-exporter`** (ReagentX, Rust) or the Python **`imessage_reader`** lib rather than |
| hand-rolling SQL. If hand-querying: join `message` β¨ `handle` β¨ `chat_message_join` β¨ `chat`, |
| track last seen `ROWID`, poll on an interval. |
| - Requires **Full Disk Access** for the running process (System Settings β Privacy & Security). |
| - Sends only new messages to the Space `/ingest` endpoint 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.md` frontmatter: `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-quantize` to Q4_K_M β |
| **publish GGUF to your HF repo** (satisfies *Well-Tuned*). Space downloads it at startup via |
| `huggingface_hub`. |
|
|
| ### 4. Custom Gradio UI (`ui/`, `static/`) β *Off-Brand* |
| - `gr.Blocks` with a custom `gr.themes.Base(...)` palette + injected `css=` (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 the `icalendar` lib; offer as a download in the UI. |
| - **Google Calendar (optional bonus):** `google-api-python-client` OAuth; 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 |
| |
| 1. **Hero path (no GPU):** Docker Space with custom-themed Gradio + the *stub* extractor β paste / |
| "Try a sample" / screenshot β review β `.ics` download, working end-to-end on a phone browser. |
| This is the parent's whole experience and must stand alone with no model. |
| 2. **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. |
| 3. **Accuracy upgrade (optional):** wire `llama-cpp-python` with a community `gemma-4-31B-it` GGUF on |
| a GPU Space; swap the stub for the model + JSON-schema prompt + pydantic validation. |
| 4. **Fine-tune (optional):** dataset β Unsloth QLoRA β GGUF β publish to HF β point the Space at it. |
| 5. **Optional auto-feed:** Mac `collector.py` reading `chat.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_saved` is 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 |
| `/ingest` and 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.db` schema / `attributedBody`.** Mitigated by using `imessage-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-hackathon`** submission |
| 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. |
|
|