lifeos / docs /architecture.md
awaisaziz's picture
Add LifeOS: local-first personal assistant on Nemotron-3-Nano-4B
40cf485
|
Raw
History Blame Contribute Delete
6.29 kB

A newer version of the Gradio SDK is available: 6.20.0

Upgrade

LifeOS Architecture

LifeOS is a local-first personal assistant built for the Hugging Face Build Small hackathon (Track 1 — Backyard AI). One small model, one shared memory, three life domains, zero cloud calls.

Stack: NVIDIA Nemotron-3-Nano-4B (Q4_K_M GGUF, 2.84 GB) + nomic-embed-text (146 MB GGUF), both running through the llama.cpp runtime (llama-cpp-python), behind Gradio 6 Server mode with a hand-built HTML/CSS/JS frontend. Runs on the HF Spaces free CPU tier.

1. System overview

flowchart LR
    subgraph Browser ["Browser — static/ (hand-built, zero external requests)"]
        UI["index.html + style.css + app.js<br/>4 panels: Kitchen · Health · Money · Chat"]
    end

    subgraph Server ["app.py — gradio.Server (FastAPI + Gradio API engine)"]
        API["@app.api endpoints<br/>SSE streaming, queue, concurrency=1"]
        UP["FastAPI routes<br/>/upload/flyer · /upload/flyer_text · /upload/transactions"]
    end

    subgraph Features ["features/ — deterministic logic (no LLM)"]
        FOOD["food.py<br/>deal extraction · recipe scoring"]
        HEALTH["health.py<br/>weekly pattern aggregation"]
        MONEY["money.py<br/>recurring-charge detection"]
    end

    subgraph Engine ["engine.py — reasoning"]
        PROMPT["build_prompt(domain, memory, input)"]
        GEN["generate_stream — token streaming"]
    end

    subgraph Models ["llama.cpp runtime (100% local)"]
        LLM["Nemotron-3-Nano-4B<br/>Q4_K_M · 2.84 GB"]
        EMB["nomic-embed-text-v1.5<br/>Q8_0 · 146 MB"]
    end

    subgraph Memory ["memory"]
        STM["memory.py — SHORT-TERM<br/>data/memory.json (structured)"]
        LTM["rag.py — LONG-TERM<br/>data/longterm.json (notes + vectors)"]
    end

    UI -->|"fetch + SSE"| API
    UI -->|multipart| UP
    UP --> FOOD & MONEY
    API --> FOOD & HEALTH & MONEY
    API --> PROMPT --> GEN --> LLM
    PROMPT --> STM
    PROMPT -->|"recall(query, k=5)"| LTM
    LTM -->|embeddings| EMB
    API -->|"log / detect events"| STM & LTM

Design principle — deterministic first, model last. Parsing, scoring, and detection are plain Python; the 4B model only does the judgment/explanation layer on a small curated context. That division is what makes a 4B model on 2 vCPUs feel smart: short prompts, grounded outputs, no hallucinated data.

2. Request flow per feature

sequenceDiagram
    participant B as Browser
    participant S as gr.Server
    participant F as features/*
    participant M as memory (STM+LTM)
    participant E as engine.py
    participant L as llama.cpp

    Note over B,L: 🍳 Kitchen — flyer → 3 recipe picks
    B->>S: POST /upload/flyer (PDF/image)
    S->>F: food.extract_deals (pdfplumber / pytesseract)
    F-->>B: deals chips [{item, price}]
    B->>S: call/food_recommend (SSE)
    S->>F: shortlist(deals, recent meals, prefs) → top 6
    S->>M: meals last 7d + RAG recall
    S->>E: build_prompt("food") → generate_stream
    E->>L: create_chat_completion(stream=True)
    L-->>B: tokens stream into result card

    Note over B,L: 💰 Money — CSV → cancel list
    B->>S: POST /upload/transactions (CSV)
    S->>F: parse → normalize merchants → detect_recurring (25–35d cadence, ±15% amount)
    F->>M: set_subscriptions(detected)
    F-->>B: subscription table
    B->>S: call/money_review (SSE)
    S->>E: income + budget + subs → stream CANCEL/KEEP/WATCH verdicts

Health follows the same shape (log → deterministic weekly_pattern → model recommends tomorrow's session). Chat skips the feature layer: it gets all memory slices plus conversation history.

3. Memory system — short-term + long-term

flowchart TB
    subgraph Writes
        W1["log meal / workout (UI)"]
        W2["money: detected subscriptions"]
        W3["chat: 'remember ...' statements"]
    end

    subgraph STM ["SHORT-TERM — memory.py (structured JSON)"]
        S1["meals[] · workouts[] · finances{} · user_profile{}"]
    end

    subgraph LTM ["LONG-TERM — rag.py (local RAG)"]
        L1["notes: {text, kind: fact|event|preference, vec}"]
        L2["nomic-embed via llama.cpp (embedding=True)"]
        L3["numpy cosine top-k (no vector DB)"]
    end

    subgraph Read ["Prompt assembly (every request)"]
        R1["domain slice of STM<br/>(food → meals, health → workouts, money → finances, chat → all)"]
        R2["top-5 recalled LTM notes for the query"]
        R3["[system: persona + domain task] + [user: memory + notes + request]"]
    end

    W1 --> S1
    W2 --> S1
    W1 -->|auto-summarized note| L1
    W3 -->|explicit fact| L1
    L1 --- L2 --- L3
    S1 --> R1 --> R3
    L3 --> R2 --> R3
  • Short-term (data/memory.json): exact structured state — what you ate, trained, pay for. Regenerated from a seed with relative dates so the demo never goes stale.
  • Long-term (data/longterm.json): durable facts, preferences, and events ("knee needs rest days between runs", "FitnessPal unused since March"). Embedded once at write time; retrieved by cosine similarity at prompt time. At demo scale (10²–10³ notes) brute-force numpy beats any vector DB — and it keeps the dependency count tiny.

4. Why these models

Model Size Role
Reasoning nvidia/NVIDIA-Nemotron-3-Nano-4B-GGUF Q4_K_M 2.84 GB All recommendations + chat
Embeddings nomic-ai/nomic-embed-text-v1.5-GGUF Q8_0 146 MB Long-term memory recall

Nemotron-3-Nano is a hybrid Mamba-2 architecture (only 4 attention layers): near-constant memory per token and strong CPU throughput, which is exactly what the Spaces free tier (2 vCPU / 16 GB) needs. Reasoning traces are disabled via system prompt to keep latency down; max_tokens≈512.

5. Hackathon badge map

Badge How
📴 Off the Grid No cloud APIs anywhere: local GGUFs, raw-fetch frontend, system fonts, no CDN
🦙 Llama Champion Both models run through the llama.cpp runtime
🐜 Tiny Titan 3.97B params reasoning model
🎨 Off-Brand gr.Server + 100% hand-built frontend, no Gradio components