OffGridSchedula / PLAN.md
ParetoOptimal's picture
Initial Commit
0366d65
|
Raw
History Blame Contribute Delete
12.4 kB

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.