Files
Alpha/README.md
Martin afc182292a feat: Media Agent Blocco A+B + Calendar Agent fix
Blocco A — Jellyfin Playback Agent (AyrKWvboPldzZPsM):
- Webhook PlaybackStart/Stop → behavioral_context + agent_messages
- Fix: JSON.parse Jellyfin body, SSL Patroni, Postgres queryParams inline

Blocco B1 — Media Library Sync (o3uM1xDLTAKw4D6E):
- Weekly cron: Radarr+Sonarr → GPT-4.1 → memory_facts + Qdrant
- Qdrant collection media_preferences creata (768-dim nomic-embed-text)

Blocco B2 — Jellyfin Watch History Sync (K07e4PPANXDkmQsr):
- Daily cron: Jellyfin history (90d) → GPT-4.1 → memory_facts
- Jellyfin API token Pompeo creato via admin auth

Calendar Agent fix (4ZIEGck9n4l5qaDt):
- Cleanup: filter(i=>i.json.uid) per evitare undefined non quotato in SQL
- Salva Evento: rimosso updated_at=NOW() (colonna non esistente)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-21 18:51:11 +00:00

602 lines
31 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ALPHA_PROJECT — System Context for GitHub Copilot
## Who I am
I am a Cloud Architect with 6 years of Big4 consulting experience (currently at manager level at EY), working daily on Azure, Dynamics 365 / Dataverse, .NET, and enterprise integration patterns. I run a production-grade homelab on Kubernetes at home, and I am building ALPHA_PROJECT as a personal initiative in my spare time (evenings and weekends).
---
## What ALPHA_PROJECT is
ALPHA_PROJECT is a **proactive, multi-agent personal AI assistant** built entirely on self-hosted infrastructure. It is not a chatbot. It is an autonomous system that:
- Monitors my digital life (email, calendar, home automation, finances, infrastructure)
- Maintains a persistent, structured memory of facts, habits, and preferences
- Takes initiative to notify me of relevant events, correlations, and pending actions
- Interacts with me via voice (Amazon Echo / Alexa custom skill named **"Pompeo"**) and Telegram
- Runs local LLMs on dedicated hardware — no cloud AI inference (except GitHub Copilot completions, available via EY license at zero cost)
The assistant is named **Pompeo** (the Alexa skill wake word).
---
## Infrastructure
### LLM Server (new, dedicated node — outside the Kubernetes cluster)
- **CPU**: AMD Ryzen 5 4500
- **RAM**: 16 GB DDR4
- **GPU**: NVIDIA GeForce RTX 3060 (16 GB VRAM)
- **Runtime**: Ollama (API-compatible with OpenAI)
- **Primary model**: Qwen2.5-14B-Instruct Q4_K_M (fits entirely in VRAM, no offload)
- **Secondary model**: Qwen2.5-Coder-14B-Instruct Q4_K_M (for code-related tasks)
- **Embedding model**: TBD — to be served via Ollama (e.g. `nomic-embed-text`)
- **Constraint**: zero RAM offload — all models must fit entirely in 16 GB VRAM
### Kubernetes Homelab Cluster
Production-grade self-hosted stack. Key components relevant to ALPHA_PROJECT:
| Component | Role |
|---|---|
| **n8n** | Primary orchestrator and workflow engine for all agents |
| **Node-RED** | Event-driven automation, Home Assistant bridge |
| **Patroni / PostgreSQL** | Persistent structured memory store — `postgres.persistence.svc.cluster.local:5432/pompeo` |
| **Qdrant** | Vector store for semantic/episodic memory — `qdrant.persistence.svc.cluster.local:6333` |
| **Home Assistant** | IoT hub — device tracking, automations, sensors, Google Calendar proxy |
| **MikroTik** | Network — VLANs, firewall rules, device presence detection |
| **Paperless-ngx** | Document archive (`docs.mt-home.uk`) |
| **Actual Budget** | Personal finance |
| **Mealie** | Meal planning / recipes |
| **Immich** | Photo library |
| **Outline** | Internal wiki / knowledge base |
| **Radarr / Sonarr** | Media management |
| **Jenkins** | CI/CD |
| **AdGuard** | DNS filtering |
| **WireGuard** | VPN |
| **Minio** | S3-compatible object storage |
| **Longhorn** | Distributed block storage |
| **Velero** | Disaster recovery / backup |
### External Services (in use)
- **Gmail** — primary email
- **Google Calendar** — calendar (multiple calendars: Work, Family, Formula 1, WEC, Inter, Birthdays, Tasks, Pulizie, Spazzatura, Festività Italia, Varie)
- **Amazon Echo** — voice interface for Pompeo
- **AWS Lambda** — bridge between Alexa skill and n8n webhook
- **Telegram** — notifications, logging, manual document upload
- **GitHub Copilot** (GPT-4.1 via `api.githubcopilot.com`) — LLM completions at zero cost (EY license)
### Internal Services / Custom
- `orchestrator.mt-home.uk` — n8n instance
- `docs.mt-home.uk` — Paperless-ngx
- `filewizard.home.svc.cluster.local:8000` — custom OCR microservice (async, job-based API)
---
## Architecture Overview
### Multi-Agent Design
ALPHA_PROJECT uses specialized agents, each responsible for a specific data domain. All agents are implemented as **n8n workflows**.
| Agent | Trigger | Responsibility |
|---|---|---|
| **Mail Agent** | Cron every 15-30 min | Read Gmail, classify emails, extract facts, detect invoices/bills |
| **Finance Agent** | Triggered by Mail Agent or Telegram | Process PDF invoices/bills, archive to Paperless, persist to memory |
| **Calendar Agent** | Cron + on-demand | Read Google Calendar, detect upcoming events, cross-reference with other agents |
| **Infrastructure Agent** | Cron + alert webhooks | Monitor Kubernetes cluster health, disk usage, failed jobs |
| **IoT Agent** | Event-driven (Home Assistant webhooks) | Monitor device presence, home state, learn behavioral patterns |
| **Newsletter Agent** | Cron morning | Digest newsletters, extract relevant articles |
| **Proactive Arbiter** | Cron (adaptive frequency) + high-priority queue messages | Consume agent outputs, correlate, decide what to notify |
### Message Broker (Blackboard Pattern)
Agents do not call each other directly. They write observations to the **`agent_messages` table** in PostgreSQL (blackboard pattern). The **Proactive Arbiter** polls this table, batches low-priority messages, and immediately processes high-priority ones. High-urgency events trigger a direct n8n webhook call bypassing the queue.
**ADR: No dedicated message broker** — Postgres is sufficient for the expected message volume and avoids operational overhead. Revisit if throughput exceeds 1k messages/day.
### Memory Architecture
Three layers of persistence:
**1. Structured memory — PostgreSQL (Patroni)**
Episodic facts, finance records, reminders, behavioral observations. Fast, queryable, expirable.
```sql
-- Generic episodic facts
CREATE TABLE memory_facts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source TEXT NOT NULL, -- 'email', 'calendar', 'iot', 'paperless', ...
category TEXT, -- 'finance', 'personal', 'work', 'health', ...
subject TEXT,
detail JSONB, -- flexible per-source payload
action_required BOOLEAN DEFAULT false,
action_text TEXT,
created_at TIMESTAMP DEFAULT now(),
expires_at TIMESTAMP, -- facts have a TTL
qdrant_id UUID -- FK to vector store
);
-- Finance documents (frequent structured queries)
CREATE TABLE finance_documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
paperless_doc_id INT,
correspondent TEXT,
amount NUMERIC(10,2),
currency TEXT DEFAULT 'EUR',
doc_date DATE,
doc_type TEXT,
tags TEXT[],
created_at TIMESTAMP DEFAULT now()
);
-- Behavioral context (used by IoT agent and Arbiter)
CREATE TABLE behavioral_context (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_type TEXT, -- 'sport_event', 'dog_walk', 'work_session', ...
start_at TIMESTAMP,
end_at TIMESTAMP,
do_not_disturb BOOLEAN DEFAULT false,
home_presence_expected BOOLEAN,
notes TEXT
);
```
**2. Semantic memory — Qdrant**`qdrant.persistence.svc.cluster.local:6333`
Vector embeddings for similarity search. Three collections with **multi-tenant design**: isolation via `user_id` payload field (`"martin"`, `"shared"`, future users).
| Collection | Content |
|---|---|
| `episodes` | Conversations, episodic facts with timestamp |
| `knowledge` | Documents, Outline notes, newsletters, knowledge base |
| `preferences` | Preferences, habits, behavioral patterns |
Each Qdrant point includes a metadata payload for pre-filtering (`user_id`, `source`, `date`, `category`, `action_required`) to avoid full-scan similarity searches.
**3. Profile memory — PostgreSQL (static table)**
User preferences, fixed facts, communication style. Updated manually or via explicit agent action.
---
## IoT Agent — Design Notes
### Data Source: Home Assistant
Home Assistant (`http://10.30.20.100:8123`, HA OS 2026.3.2, Alzano Lombardo BG) is the primary hub for physical-world context. It aggregates Google Pixel 10, Pixel Watch 4, smart home devices, and 25 Google Calendars.
**Person allowlist** (permanent by design — `person.ajada_tahiraj` is explicitly excluded):
| Person | Entity | Notes |
|---|---|---|
| Martin Tahiraj | `person.martin_tahiraj` | ✅ Tracked |
| Ajada Tahiraj | `person.ajada_tahiraj` | ❌ Excluded (sister — privacy) |
**Key sensors for Martin:**
| Sensor | Entity ID | Signal |
|---|---|---|
| Activity (Google) | `sensor.pixel_10_detected_activity` | still / walking / running / in_vehicle |
| Geocoded location | `sensor.pixel_10_geocoded_location` | Human-readable street address |
| EY laptop | `device_tracker.ey_hp` | Router tracker — online = laptop on home WiFi |
| Spotify | `media_player.spotify_martin` | Current track, playing/paused |
| Sleep duration | `sensor.pixel_10_sleep_duration` | Pixel Watch 4 |
| Next alarm | `sensor.pixel_10_next_alarm` | Scheduled wake-up |
| Work Profile | `binary_sensor.pixel_10_work_profile` | Android Work Profile active |
| Screen on | `binary_sensor.pixel_10_interactive` | Phone screen on/off |
| Do Not Disturb | `binary_sensor.pixel_10_do_not_disturb` | DND mode |
| Daily steps | `sensor.pixel_10_daily_steps` | Pixel Watch 4 |
| Heart rate | `sensor.pixel_10_heart_rate` | Pixel Watch 4 |
| GPS Zone | `person.martin_tahiraj` | home / not_home / zone name |
Room presence sensors (PIR-based) are considered **unreliable** — excluded for now.
### Sensor Allowlist — `ha_sensor_config`
Instead of hardcoded rules, the IoT Agent uses a dynamic allowlist stored in Postgres. Sensors are matched by **regex pattern**, allowing glob-style additions:
```sql
CREATE TABLE ha_sensor_config (
id SERIAL PRIMARY KEY,
pattern TEXT NOT NULL, -- regex pattern, e.g. 'sensor\.pixel_10_.*'
user_id TEXT NOT NULL,
group_name TEXT NOT NULL, -- 'mobile_device' | 'work_presence' | 'entertainment' | ...
description TEXT,
active BOOLEAN NOT NULL DEFAULT true
);
-- Seed entries
INSERT INTO ha_sensor_config (pattern, user_id, group_name, description) VALUES
('sensor\.pixel_10_.*', 'martin', 'mobile_device', 'All Pixel 10 sensors'),
('device_tracker\.ey_hp', 'martin', 'work_presence', 'EY Laptop router tracker'),
('media_player\.spotify_martin', 'martin', 'entertainment', 'Spotify'),
('binary_sensor\.pixel_10_.*', 'martin', 'mobile_device', 'Pixel 10 binary sensors'),
('person\.martin_tahiraj', 'martin', 'presence', 'Martin GPS zone state');
```
This allows adding new sensors (e.g. `sensor.pixel_watch_.*`) without workflow changes.
### Activity State Machine (LLM-based — no fixed rules)
The IoT Agent sends a snapshot of all allowlisted sensor values to GPT-4.1 and asks it to infer the current activity label and confidence. **No if/else rules are coded** — the LLM performs inference.
Example LLM output:
```json
{
"activity": "home_working",
"confidence": 0.92,
"do_not_disturb": true,
"location": "home",
"notes": "Laptop EY online, work profile attivo, orario lavorativo 09-18"
}
```
Activity labels: `sleeping`, `home_relaxing`, `home_working`, `commuting`, `at_office`, `out_errands`, `out_with_dog`, `exercising`, `traveling`, `unknown`.
### Three-Layer Data Flow
| Layer | Trigger | Frequency | Output |
|---|---|---|---|
| Webhook | HA automation (zone change, motion) | Event-driven | Immediate `agent_messages` entry |
| Polling | n8n cron | Every 20 min | Sensor snapshot → LLM → `behavioral_context` |
| Daily cron | n8n cron midnight | Once/day | Day summary → Qdrant `episodes` embedding |
### Historical Bootstrap
One-time job: last 12 months of HA sensor history → daily LLM summaries → Qdrant `episodes`.
- Source: HA History API (`/api/history/period/{start}?filter_entity_id=...`)
- Output: one Qdrant point per day per user, with full behavioral context
### Confidence-Gated Clarification
When activity inference confidence < 0.6, or when Pompeo detects a potential life change (new employer from emails, travel pattern, etc.), it asks Martin directly via Telegram:
> "Ciao Martin, sto notando email di Avanade — lavori ancora per EY o sei passato lì? 🤔"
Pompeo updates `user_profile` or `memory_facts` with the confirmed fact and adjusts its confidence threshold.
---
## Calendar Agent — Design Notes
### Design Decisions
- **Data source**: Google Calendar events fetched via **Home Assistant REST API** (`/api/calendars/{entity_id}?start=&end=`) — HA proxies all 25 calendars and removes the need for a direct Google OAuth credential in n8n.
- **Dedup**: `memory_facts.source_ref` stores the HA event UID; `ON CONFLICT (user_id, source, source_ref) WHERE source_ref IS NOT NULL DO NOTHING` prevents duplicates.
- **LLM enrichment**: GPT-4.1 classifies each event in batch (category, action_required, do_not_disturb, priority, behavioral_context, pompeo_note).
- **No Qdrant embedding yet** (Phase 2): individual events go to Postgres only; a weekly aggregated embedding will be added later.
### Calendars Tracked
| Calendar | Entity ID | Category | User |
|---|---|---|---|
| Lavoro | `calendar.calendar` | work | martin |
| Famiglia | `calendar.famiglia` | personal | martin |
| Spazzatura | `calendar.spazzatura` | chores | martin |
| Pulizie | `calendar.pulizie` | chores | martin |
| Formula 1 | `calendar.formula_1` | leisure | martin |
| WEC | `calendar.lm_wec_fia_world_endurance_championship` | leisure | martin |
| Inter | `calendar.inter_calendar` | leisure | martin |
| Compleanni | `calendar.birthdays` | social | martin |
| Varie | `calendar.varie` | misc | martin |
| Festività Italia | `calendar.festivita_in_italia` | holiday | shared |
| Films (Radarr) | `calendar.films` | leisure | martin |
| Serie TV (Sonarr) | `calendar.serie_tv` | leisure | martin |
### n8n Workflow
**`📅 Pompeo — Calendar Agent [Schedule]`** — ID `4ZIEGck9n4l5qaDt`
```
⏰ Schedule (06:30) → 📅 Imposta Range → 🔑 Token Copilot
→ 📋 Prepara Calendari (12 items)
→ 📡 HA Fetch (×12, one per calendar)
→ 🏷️ Estrai ed Etichetta (tagged events, flat)
→ 📝 Prepara Prompt (dedup + LLM prompt)
→ 🤖 GPT-4.1 (batch classify all events)
→ 📋 Parse Risposta
→ 💾 Postgres Upsert (memory_facts, per event, ON CONFLICT DO NOTHING)
→ 📦 Aggrega → ✍️ Prepara Messaggio → 📱 Telegram Briefing
```
### Embedding Strategy
- Embeddings are generated via Ollama (`nomic-embed-text` or equivalent) once the LLM server is online
- During bootstrap phase: embeddings generated via GitHub Copilot (`text-embedding-3-small` at `api.githubcopilot.com/embeddings`) — same token acquisition pattern already in use
- Never embed raw content — always embed **LLM-generated summaries + extracted entities**
### Proactive Notification Logic
The Arbiter runs on an **adaptive schedule**:
| Time slot | Frequency | Behavior |
|---|---|---|
| 23:0007:00 | Never | Silence |
| 07:0009:00 | Once | Morning briefing (calendar, reminders, pending actions) |
| 09:0019:00 | Every 2-3h | Only high-priority or correlated events |
| 19:0022:00 | Once | Evening recap + next day preview |
High-priority queue messages bypass the schedule and trigger immediate notification.
Notification is sent via **Amazon Echo / Pompeo** (TTS) for voice, and **Telegram** for logging. Every Arbiter decision (notify / discard / defer) is logged to a dedicated Telegram audit channel.
### Voice Interface (Pompeo)
- Amazon Echo → **Alexa Custom Skill****AWS Lambda** (bridge) → **n8n webhook** → Ollama (Qwen2.5-14B) → TTS response back to Echo
- Wake phrase: "Pompeo"
- Lambda is intentionally thin — it only translates the Alexa request format to the n8n webhook payload and returns the TTS response
---
## Existing n8n Workflows (already in production)
### 📬 Gmail — Daily Digest [Schedule] (`1lIKvVJQIcva30YM`)
- Runs every 3 hours (+ test webhook)
- Fetches unread emails from the last 3 hours
- Calls GPT-4.1 (via Copilot) to classify each email: category, sentiment, labels, action_required, whether it has a Paperless-relevant PDF attachment
- Applies Gmail labels, marks as read, trashes spam
- If a bill/invoice PDF is detected → triggers the **Upload Bolletta** webhook
- Sends a digest report to Telegram
### 📄 Paperless — Upload Documento [Multi] (`GBPFFq8rmbdFrNn9`) ✅ Active
Replaces the two retired workflows below. Single core pipeline with two entry points:
- **Trigger 1 — Telegram**: PDF sent to bot with caption starting with "Documento" → downloads file
- **Trigger 2 — Webhook** (`POST /webhook/paperless-upload`): called by Daily Digest with `{email_id, attachment_id, filename, hint, from}` → downloads attachment from Gmail API
Both paths converge:
- FileWizard OCR (async, polls job) → GPT-4.1 metadata inference → Paperless upload → PATCH metadata → Telegram confirmation → FileWizard cleanup
- Dedup: if Paperless returns "duplicate", patches the existing document's metadata instead
> 🔴 **Retired** (deactivated):
> - `vbzQ3fgUalOPdcOq` — Paperless — Upload Bolletta [Email]
> - `ZX5rLSETg6Xcymps` — Paperless — Upload Documento [Telegram]
---
### 💰 Actual — Import Estratto Conto [Telegram] (`qtvB3r0cgejyCxUp`) ✅ Active
Imports bank CSV statements (Banca Sella format) into Actual Budget via Telegram upload.
- **Trigger**: Telegram bot, send CSV file with caption starting with "Estratto"
- Fetches token Copilot → downloads CSV binary from Telegram
- Parses CSV: skips SALDO rows, extracts transactions with `Id. XXXXXXXXX` as dedup key
- Calls Actual HTTP API to fetch existing payees, categories, and transactions since `min_date`
- Deduplicates: skips transactions already present (by `imported_id = banca-sella-{Id}`)
- Splits remaining transactions into batches of 30
- **Per batch**: GPT-4.1 classifies each transaction (payee, category, notes) → creates missing payees/categories on Actual → imports transactions
- Final Telegram report: imported count, skipped, new payees/categories created
- After report: marks Google Task "Actual - Estratto conto" in list "Finanze" as completed (non-blocking)
**CSV format** (Banca Sella): `;` separator, `dd/mm/yyyy` dates, `.` decimal, Row 1 = header, Row 2 = SALDO FINALE (skip), Last row = SALDO INIZIALE (skip).
### ⏰ Actual — Reminder Estratto Conto [Schedule] (`w0oJ1i6sESvaB5W1`) ✅ Active
- Daily cron at 09:00 → checks Google Tasks for "Actual - Estratto conto" in "Finanze" list
- If task exists and is not completed (due date ≤ today or no due date) → sends Telegram reminder
- Reminder instructs to upload CSV via Telegram with caption `Estratto [mese]`
**Common pattern across Paperless + Actual workflows**: GitHub Copilot token is obtained fresh at each run (`GET https://api.github.com/copilot_internal/v2/token`), then used for `POST https://api.githubcopilot.com/chat/completions` with model `gpt-4.1`.
### 🎬 Pompeo — Jellyfin Playback Agent [Webhook] (`AyrKWvboPldzZPsM`) ✅ Active
Webhook-based — triggered in real time by the Jellyfin Webhook plugin on every `PlaybackStart` / `PlaybackStop` event.
- Webhook path: `jellyfin-playback` (via `https://orchestrator.mt-home.uk/webhook/jellyfin-playback`)
- Normalizes the Jellyfin payload (body is a JSON-encoded string → `JSON.parse` required)
- On **PlaybackStart**: INSERT into `behavioral_context` (`event_type=watching_media`, `do_not_disturb=true`, metadata in `notes` JSONB: `item`, `device`, `item_type`)
- On **PlaybackStop**: UPDATE `behavioral_context` → set `end_at=now()`, `do_not_disturb=false`
- Both events write to `agent_messages` (subject: `▶️ {title} ({device})` or `⏹️ …`)
- **User filter**: only processes events for `UserId = 42369255a7c64917a28fc26d4c7f8265` (Martin)
- **Jellyfin stop behavior**: `PlaybackStop` fires only on player close, NOT on pause
Known quirks fixed:
- Jellyfin webhook plugin sends body as `$json.body` (JSON string) — must `JSON.parse()` before reading fields
- Real Jellyfin field names: `Name` (not `ItemName`), `DeviceName` / `Client`, `UserId` (no dashes)
---
### 🎬 Pompeo — Media Library Sync [Schedule] (`o3uM1xDLTAKw4D6E`) ✅ Active
Weekly cron — every **Sunday at 03:00**.
- Fetches all movies from **Radarr** (`/radarr/api/v3/movie`) and all series from **Sonarr** (`/sonarr/api/v3/series`)
- Merges into unified list: `{type, title, year, genres, status: available|missing|monitored|unmonitored, source, source_id}`
- **GPT-4.1 analysis**: extracts `top_genres`, `preferred_types`, `library_stats`, `taste_summary`, `notable_patterns`
- **Postgres upsert**: `memory_facts` (source=`media_library`, source_ref=`media_preferences_summary`, expires +7d)
- **Per-item loop**: for each movie/series → Ollama `nomic-embed-text` (768-dim) → Qdrant upsert into `media_preferences` collection
- Qdrant payload: `{title, year, type, genres, status, source, source_id, expires_at (+6 months)}`
Internal endpoints used:
- `http://radarr.media.svc.cluster.local:7878/radarr/api/v3/movie?apikey=922d1405ab1147019d98a2997d941765`
- `http://sonarr.media.svc.cluster.local:8989/sonarr/api/v3/series?apikey=22140655993a4ff6bf12314813ec6982`
- `http://ollama.ai.svc.cluster.local:11434/api/embeddings` (model: `nomic-embed-text`)
- `http://qdrant.persistence.svc.cluster.local:6333` (api-key: sealed secret `qdrant-api-secret`)
---
### 🎞️ Pompeo — Jellyfin Watch History Sync [Schedule] (`K07e4PPANXDkmQsr`) ✅ Active
Daily cron — every day at **04:00**.
- Fetches last 100 played/partially-played items for Martin from Jellyfin API
- Endpoint: `/Users/42369255a7c64917a28fc26d4c7f8265/Items?Recursive=true&IncludeItemTypes=Movie,Episode&SortBy=DatePlayed&SortOrder=Descending&Limit=100`
- Auth: `Authorization: MediaBrowser Token="d153606c1ca54574a20d2b40fcf1b02e"` (Pompeo API key)
- Filters items with `PlayCount > 0` and `LastPlayedDate` within 90 days
- **GPT-4.1 analysis**: extracts `recent_favorites`, `preferred_genres`, `watch_patterns`, `completion_rate`, `notes`
- **Postgres upsert**: `memory_facts` (source=`jellyfin`, source_ref=`watch_history_summary`, expires +30d)
- Skips silently if no played items found (IF node guards the LLM call)
Jellyfin credentials:
- API Token (app: Pompeo): `d153606c1ca54574a20d2b40fcf1b02e` — created via `POST /Auth/Keys?app=Pompeo` with admin session
- Martin UserId: `42369255a7c64917a28fc26d4c7f8265` (from Jellyfin SQLite DB / webhook payload)
- Admin user `admin` with password `__Montecarlo00!` (local auth, SSO via Authentik is separate)
---
### 📅 Pompeo — Calendar Agent [Schedule] (`4ZIEGck9n4l5qaDt`) ✅ Active
Runs every morning at 06:30 (and on-demand via manual trigger).
- Fetches events for the next 7 days from 12 Google Calendars via **Home Assistant REST API** (calendar proxy — no Google OAuth needed in n8n)
- Tags each event with calendar name, category, user_id
- **GPT-4.1 batch classification**: category, action_required, do_not_disturb, priority, behavioral_context, pompeo_note
- **Postgres upsert** → `memory_facts` (source=calendar, source_ref=HA event UID, dedup ON CONFLICT DO NOTHING)
- **Telegram briefing**: daily grouped summary sent to the notification channel
Calendars: Lavoro, Famiglia, Spazzatura, Pulizie, Formula 1, WEC, Inter, Compleanni, Varie, Festività Italia, Films (Radarr), Serie TV (Sonarr).
### n8n Credentials (IDs)
| ID | Name | Type |
|---|---|---|
| `qvOikS6IF0H5khr8` | Gmail OAuth2 | OAuth2 |
| `uTXHLqcCJxbOvqN3` | Telegram account | Telegram API |
| `vBwUxlzKrX3oDHyN` | GitHub Copilot OAuth Token | HTTP Header Auth |
| `uvGjLbrN5yQTQIzv` | Paperless-NGX API | HTTP Header Auth |
| `ZIVFNgI3esCKuYXc` | Google Calendar account | Google Calendar OAuth2 (also used for Tasks API) |
| `u0JCseXGnDG5hS9F` | Home Assistant API | HTTP Header Auth (long-lived HA token) |
| `mRqzxhSboGscolqI` | Pompeo — PostgreSQL | Postgres (database: `pompeo`, user: `martin`) |
| `u0JCseXGnDG5hS9F` | Home Assistant API | HTTP Header Auth (long-lived HA token) |
### Qdrant Collections
| Collection | Dimensions | Distance | Model | Used by |
|---|---|---|---|---|
| `media_preferences` | 768 | Cosine | `nomic-embed-text` (Ollama) | Media Library Sync (B1) |
Qdrant API key: sealed secret `qdrant-api-secret` in namespace `persistence``__Montecarlo00!`
---
## Coding Conventions
- **n8n workflows**: nodes named in Italian, descriptive emoji prefixes on trigger nodes
- **Workflow naming**: `{icon} {App} — {Azione} {Tipo} [{Sorgente}]` (e.g. `📄 Paperless — Upload Documento [Telegram]`)
- **HTTP nodes**: always use `predefinedCredentialType` for authenticated services already configured in n8n credentials
- **GPT body**: use `contentType: "raw"` + `rawContentType: "application/json"` + `JSON.stringify({...})` inline expression — never `specifyBody: string`
- **LLM output parsing**: always defensive — handle missing `choices`, malformed JSON, empty responses gracefully
- **Copilot token**: always fetched fresh per workflow run, never cached across executions
- **Binary fields**: Telegram node `file.get` with `download: true` stores binary in field named `data` (not `attachment`)
- **Postgres**: use UUID primary keys with `gen_random_uuid()`, JSONB for flexible payloads, always include `created_at`
- **Qdrant upsert**: always include full metadata payload for filtering; use `message_id` / `thread_id` / `doc_id` as logical dedup keys
---
## TO-DO
### Phase 0 — Infrastructure Bootstrap *(prerequisite for everything)*
- [x] ~~Deploy **Qdrant** on the Kubernetes cluster~~ ✅ 2026-03-21
- Collections: `episodes`, `knowledge`, `preferences` (multi-tenant via `user_id` payload field)
- Payload indexes: `user_id`, `source`, `category`, `date`, `action_required`
- Endpoint: `qdrant.persistence.svc.cluster.local:6333`
- [x] ~~Run **PostgreSQL migrations** on Patroni~~ ✅ 2026-03-21
- Database `pompeo` creato (Zalando Operator)
- Tabelle: `user_profile`, `memory_facts` (+ `source_ref` + dedup index), `finance_documents`, `behavioral_context`, `agent_messages`
- Multi-tenancy: campo `user_id` su tutte le tabelle, seed `martin` + `shared`
- Script DDL: `alpha/db/postgres.sql`
- [x] Verify embedding endpoint via Copilot (`text-embedding-3-small`) as bootstrap fallback
- [x] ~~Plan migration to local Ollama embedding model once LLM server is online~~ ✅ Active — `nomic-embed-text` via `http://ollama.ai.svc.cluster.local:11434` (768-dim, multilingual)
- [ ] Create `ha_sensor_config` table in Postgres and seed initial sensor patterns
---
### Phase 1 — Memory Integration into Existing Workflows
- [ ] **Daily Digest**: after `Parse risposta GPT-4.1`, add:
- Postgres INSERT into `memory_facts` (source=email, category, subject, detail JSONB, action_required, expires_at)
- Embedding generation (Copilot endpoint) → Qdrant upsert into `episodes` (user_id=martin)
- Thread dedup: use `thread_id` as logical key, update existing Qdrant point if thread already exists
- [ ] **Upload Bolletta** + **Upload Documento (Telegram)**: after `Paperless - Patch Metadati`, add:
- Postgres INSERT into `finance_documents` (correspondent, amount, doc_date, doc_type, tags, paperless_doc_id)
- Postgres INSERT into `memory_facts` (source=paperless, category=finance, cross-reference)
- Embedding of OCR text chunks → Qdrant upsert into `knowledge` (user_id=martin)
---
### Phase 2 — New Agents
- [x] ~~**Calendar Agent**~~ ✅ 2026-03-20 — `4ZIEGck9n4l5qaDt`
- 12 calendari Google via HA proxy, fetch next 7 days
- GPT-4.1 batch classification → `memory_facts` (dedup by HA event UID)
- Telegram daily briefing at 06:30
- **Phase 2**: add weekly Qdrant embedding for semantic retrieval
- [x] ~~**Jellyfin Playback Agent**~~ ✅ 2026-03-21 — `AyrKWvboPldzZPsM`
- Webhook: PlaybackStart → `behavioral_context` INSERT (`do_not_disturb=true`), PlaybackStop → UPDATE (`end_at`, `do_not_disturb=false`)
- `agent_messages` populated with `▶️`/`⏹️` + title + device
- User filter: Martin only (UserId `42369255…`)
- [x] ~~**Media Library Sync (B1)**~~ ✅ 2026-03-21 — `o3uM1xDLTAKw4D6E`
- Weekly (Sunday 03:00): Radarr + Sonarr → GPT-4.1 taste analysis → `memory_facts` + Qdrant `media_preferences` (768-dim, nomic-embed-text)
- [x] ~~**Jellyfin Watch History Sync (B2)**~~ ✅ 2026-03-21 — `K07e4PPANXDkmQsr`
- Daily (04:00): Jellyfin play history (90d window) → GPT-4.1 pattern analysis → `memory_facts`
- [ ] **Finance Agent** (extend beyond Paperless)
- Read Actual Budget export or API
- Persist transactions, monthly summaries to `finance_documents`
- Trend analysis prompt for periodic financial summary
- [ ] **Infrastructure Agent**
- Webhook receiver for Kubernetes/Longhorn/Minio alerts
- Cron-based cluster health check (disk, pod status, backup freshness)
- Publishes to message broker with `priority: high` for critical alerts
- [ ] **IoT Agent***design complete, implementation pending*
- Sensor allowlist via `ha_sensor_config` Postgres table (regex-based)
- No fixed rules: GPT-4.1 infers activity label + confidence from sensor snapshot
- Three layers: webhook (events) + polling 20min (behavioral_context) + daily cron (Qdrant episodes)
- Historical bootstrap: 12 months HA history → daily LLM summaries → Qdrant `episodes`
- Confidence-gated clarification: ask Martin via Telegram if confidence < 0.6
- [ ] **Newsletter Agent**
- Separate Gmail label for newsletters (excluded from Daily Digest main flow)
- Morning cron: summarize + extract relevant articles → `knowledge`
---
### Phase 3 — Message Broker + Proactive Arbiter
- [ ] Implement **Proactive Arbiter** n8n workflow:
- Adaptive schedule (morning briefing, midday, evening recap)
- Consume `agent_messages` batch → LLM correlation prompt → structured `notify/defer/discard` output
- High-priority bypass path (direct webhook)
- All decisions logged to Telegram audit channel
- [ ] Implement **correlation logic**: detect when 2+ agents report related events (e.g. IoT presence + calendar event + open reminder)
---
### Phase 4 — Voice Interface (Pompeo)
- [ ] Create Alexa Custom Skill ("Pompeo")
- [ ] AWS Lambda bridge (thin translator: Alexa request → n8n webhook → TTS response)
- [ ] n8n webhook handler: receive transcribed text → prepend memory context → Ollama inference → return TTS string
- [ ] TTS response pipeline back to Echo
- [ ] Proactive push: Arbiter → Lambda → Echo notification (Alexa proactive events API)
---
### Phase 5 — Generalization and Backlog
- [ ] **OCR on email attachments in Daily Digest**: generalize the ingest pipeline to extract text from any PDF attachment (not just bills), using FileWizard OCR — produce richer embeddings and enable full-text retrieval on any emailed document
- [ ] **Flusso Cedolino** (payslip pipeline):
- Trigger: Gmail label `Lavoro/Cedolino` or Telegram upload
- PDF → FileWizard OCR → GPT-4.1 metadata extraction (month, gross, net, deductions)
- Paperless upload with tag `Cedolino`
- Persist structured data to `finance_documents` (custom fields for payslip)
- Trend embedding in `knowledge` for finance agent queries
- [ ] Behavioral habit modeling: aggregate `behavioral_context` records over time, generate periodic "habit summary" embeddings in `preferences`
- [ ] Outline → Qdrant pipeline: sync selected Outline documents into `knowledge` on edit/publish event
- [ ] Chrome browsing history ingestion (privacy-filtered): evaluate browser extension or local export → embedding pipeline for interest/preference modeling
- [ ] "Posti e persone" graph: structured contact/location model in Postgres, populated from email senders, calendar attendees, Home Assistant presence data
- [ ] Local embedding model: migrate from Copilot `text-embedding-3-small` to Ollama-served model (e.g. `nomic-embed-text`) once LLM server is stable