43 KiB
ALPHA_PROJECT — Changelog
Tutte le modifiche significative al progetto ALPHA_PROJECT sono documentate qui.
[2026-03-21] AWS Lambda Bridge for Alexa Skill "Pompeo"
Completata la pianificazione e l'implementazione della funzione AWS Lambda che funge da ponte tra la skill Alexa "Pompeo" e il backend n8n. Questo conclude la parte di sviluppo locale della "Phase 4 — Voice Interface (Pompeo)".
Pianificazione e Progettazione
- Creato
aws-lambda/README.md: Un piano di deploy dettagliato che include:- Analisi dei requisiti funzionali e non funzionali (costo zero, distribuzione privata).
- Progettazione dell'architettura (Lambda come "thin bridge").
- Specifiche per il ruolo IAM (
AWSLambdaBasicExecutionRole). - Design della logica della funzione in Python.
- Una guida passo-passo per la creazione delle risorse su AWS (IAM, Lambda) e sulla Alexa Developer Console.
- Ricerca e Scelte Tecniche:
- Confermato che il piano gratuito di AWS Lambda è sufficiente per un costo nullo.
- Stabilito che la distribuzione privata si ottiene mantenendo la skill in stato "In Sviluppo", senza pubblicarla.
- Definito l'uso di un "Invocation Name" di due parole (es. "maggiordomo Pompeo") per rispettare le policy di Alexa.
Implementazione
- Struttura del Progetto: Creata la sottocartella
aws-lambda/srcper il codice sorgente. - Codice Sorgente: Implementata la funzione
lambda_handlerinaws-lambda/src/index.py, che inoltra le richieste Alexa al webhook n8n in modo sicuro. - Dipendenze: Definite le dipendenze (
requests) inaws-lambda/src/requirements.txt. - Pacchetto di Deploy: Creato l'ambiente virtuale Python e installate le dipendenze direttamente nella cartella
srcper preparare il pacchetto di deploy. - Artefatto Finale: Generato il file
aws-lambda/pompeo-alexa-bridge.zip, pronto per essere caricato sulla console AWS Lambda.
[2026-03-21] Jellyfin Playback Agent — Blocco A completato
Nuovo workflow n8n
🎬 Pompeo — Jellyfin Playback [Webhook](AyrKWvboPldzZPsM): riceve webhook da Jellyfin (PlaybackStart / PlaybackStop), filtra per utentemartin(userId whitelist), e scrive su Postgres:- PlaybackStart → INSERT in
behavioral_context(event_type=watching_media,do_not_disturb=true, notes con item/device/session_id) + INSERT inagent_messages(soggetto▶️ <titolo> (<device>)) - PlaybackStop → UPDATE su riga aperta più recente (
end_at=now(),do_not_disturb=false) + INSERT inagent_messages(soggetto⏹️ ...)
- PlaybackStart → INSERT in
Bug risolti (infrastruttura n8n)
- Webhook path n8n v2: per registrare un webhook con path statico via API, il campo
webhookIdva impostato come attributo top-level del nodo (non dentroparameters). Senza di esso n8n genera il path dinamico{workflowId}/{nodeName}/{path}che il webhook pod non carica correttamente in queue mode. - SSL Postgres / Patroni: le credential Postgres create via API usavano SSL con
rejectUnauthorized=truedi default, incompatibile con il certificato self-signed di Patroni. Fix: aggiuntoNODE_TLS_REJECT_UNAUTHORIZED=0ai deploymentn8n-appen8n-app-worker. - queryParams Postgres node:
additionalFields.queryParamscon espressioni$json.*non funziona correttamente in n8n v2.5.2. Fix: valori inline nella SQL via espressioni n8n{{ $json.field }}.
Configurazione Jellyfin
- Webhook plugin Jellyfin configurato su
http://n8n-app-webhook.automation.svc.cluster.local/webhook/jellyfin-playback(POST, eventi: PlaybackStart + PlaybackStop)
[2026-03-21] Daily Digest — integrazione memoria Postgres
Modifiche al workflow 📬 Gmail — Daily Digest [Schedule] (1lIKvVJQIcva30YM)
Aggiunto branch parallelo di salvataggio fatti in memoria dopo la classificazione GPT:
Parse risposta GPT-4.1 ──┬──> Telegram - Invia Report (invariato)
├──> Dividi Email (invariato)
└──> 🧠 Estrai Fatti ──> 🔀 Ha Fatti? ──> 💾 Upsert Memoria
🧠 Estrai Fatti (Code):
- Filtra le email con
action != 'trash'e summary non vuoto - Chiama GPT-4.1 in batch per estrarre per ogni email:
fact,category,ttl_days,pompeo_note,entity_refs - Calcola
expires_atdattl_days(14gg prenotazioni, 45gg bollette, 90gg lavoro/condominio, 30gg default) - Restituisce un item per ogni fatto da persistere
💾 Upsert Memoria (Postgres node → mRqzxhSboGscolqI):
INSERT INTO memory_factsconsource='email',source_ref=threadIdON CONFLICT ON CONSTRAINT memory_facts_dedup_idx DO UPDATE→ aggiorna se lo stesso thread viene riprocessato- Campi salvati:
category,subject,detail(JSONB),action_required,action_text,pompeo_note,entity_refs,expires_at
Fix contestuale
- Aggiunto
newer_than:1dalla query Gmail su entrambi i nodi fetch — evitava di rifetchare email vecchie di mesi non marcateProcessed
[2026-03-21] Schema DB v2 — contacts, memory_facts_archive, entity_refs
Nuove tabelle
contacts: grafo di persone multi-tenant. Ogni riga modella una relazioneuser_id → subjectconrelation,city,country,profession,aliases[],born_year,details(narrativa libera per LLM) emetadataJSONB. Traversabile ricorsivamente per inferire relazioni di secondo grado (es. Martin → zio Mujsi → figlio Euris → cugino di primo grado da parte di madre). Indici GIN susubject(trigram) ealiasesper similarity search.memory_facts_archive: destinazione del cleanup settimanale dei fatti scaduti. Struttura identica amemory_facts+archived_at+archive_reason(expired|superseded|merged). I fatti archiviati vengono poi condensati in un episodio Qdrant settimanale.
Colonne aggiunte a memory_facts
pompeo_note TEXT: inner monologue dell'LLM al momento dell'insert — il "perché" del fatto (già in uso nel Calendar Agent, ora standardizzato su tutti i source).entity_refs JSONB: entità estratte dal fatto strutturato —{people: [], places: [], products: [], amounts: []}. Permette query SQL su persone/luoghi senza full-text scan (es.entity_refs->'people' ? 'euris vruzhaj').
Applicato a
alpha/db/postgres.sqlaggiornato (schema v2)- Live su Patroni primary (
postgres-1, namespacepersistence, DBpompeo)
[2026-03-21] Actual Budget — Import Estratto Conto via Telegram
Nuovi workflow
💰 Actual — Import Estratto Conto [Telegram](qtvB3r0cgejyCxUp): importa l'estratto conto Banca Sella (CSV) in Actual Budget tramite Telegram.- Trigger: documento Telegram con caption
Estratto conto - Parse CSV Banca Sella (separatore
;, dategg/mm/aaaa, importi con.decimale) - Skip automatico di
SALDO FINALEeSALDO INIZIALE - Classificazione GPT-4.1 in batch da 30 transazioni: assegna payee e categoria, crea automaticamente i mancanti su Actual
- Import via
/transactions/importcon dedup nativo tramiteimported_id(patternbanca-sella-{Id}o hash fallback) - Report Telegram con nuove transazioni importate, già presenti e totale CSV
- Trigger: documento Telegram con caption
⏰ Actual — Reminder Estratto Conto [Schedule](w0oJ1i6sESvaB5W1): reminder giornaliero (09:00) su Telegram se il task Google "Actual - Estratto conto" nella lista "Finanze" è scaduto.
Note tecniche
- Binary data letta con
getBinaryDataBuffer()(compatibile con filesystem binary mode di n8n) - Loop GPT gestito con iterazione interna nel Code node (no
splitInBatches— instabile con input multipli) - Payee/categorie mancanti creati al volo e riutilizzati nei batch successivi della stessa run
- Dedup Actual:
added= nuove,updated= già presenti
[2026-03-21] Calendar Agent — fix sincronizzazione e schedule
Problemi risolti
ON CONFLICT DO NOTHING→DO UPDATE: gli eventi modificati (orario, titolo) venivano ignorati. Ora vengono aggiornati in Postgres.- Cleanup eventi cancellati: aggiunto step
🗑️ Cleanup Cancellatiche esegueDELETE FROM memory_facts WHERE source_ref NOT IN (UID attuali da HA)per la finestra 7 giorni. Se Martin cancella un meeting, sparisce da Postgres al prossimo run. - Schedule
*/30 * * * *: da cron 06:30 giornaliero a ogni 30 minuti — il calendario Postgres è sempre allineato alla source of truth (HA/Google Calendar).
Flusso aggiornato
... → 📋 Parse GPT → 🗑️ Cleanup Cancellati → 🔀 Riemetti → 💾 Upsert → 📦 → 📱
[2026-03-20] Calendar Agent — primo workflow Pompeo in produzione
Cosa è stato fatto
Primo agente Pompeo deployato e attivo su n8n: 📅 Pompeo — Calendar Agent [Schedule] (ID 4ZIEGck9n4l5qaDt).
Design
- Sorgente dati: Home Assistant REST API usata come proxy Google Calendar — evita OAuth Google diretto in n8n e funziona per tutti i 25 calendari registrati in HA.
- Calendari tracciati (12): Lavoro, Famiglia, Spazzatura, Pulizie, Formula 1, WEC, Inter, Compleanni, Varie, Festività Italia, Films (Radarr), Serie TV (Sonarr).
- LLM enrichment: GPT-4.1 (via Copilot) classifica ogni evento: category, action_required, do_not_disturb, priority, behavioral_context, pompeo_note.
- Dedup:
memory_facts.source_ref= HA event UID;ON CONFLICT DO NOTHINGsu indice unico parziale. - Telegram briefing: ogni mattina alle 06:30, riepilogo eventi prossimi 7 giorni raggruppati per calendario.
Migrazioni DB applicate
ALTER TABLE memory_facts ADD COLUMN source_ref TEXT— colonna per ID esterno di dedupCREATE UNIQUE INDEX memory_facts_dedup_idx ON memory_facts (user_id, source, source_ref) WHERE source_ref IS NOT NULLCREATE INDEX idx_memory_facts_source_ref ON memory_facts (source_ref) WHERE source_ref IS NOT NULL
Credential n8n create
| ID | Nome | Tipo |
|---|---|---|
u0JCseXGnDG5hS9F |
Home Assistant API | HTTP Header Auth |
mRqzxhSboGscolqI |
Pompeo — PostgreSQL | Postgres (pompeo/martin) |
Flusso workflow
⏰ Schedule (06:30) → 📅 Range → 🔑 Token Copilot
→ 📋 Calendari (12 items) → 📡 HA Fetch (×12) → 🏷️ Estrai + Tag
→ 📝 Prompt (dedup) → 🤖 GPT-4.1 → 📋 Parse
→ 💾 Postgres Upsert (memory_facts) → 📦 Aggrega → 📱 Telegram
[2026-03-21] ADR — Message Broker: nessun broker dedicato
Decisione
Non verrà deployato un message broker dedicato (né NATS JetStream né Redis Streams). Il blackboard pattern viene implementato interamente su PostgreSQL + webhook n8n.
Ragionamento
Al momento della progettazione iniziale, il broker era necessario per disaccoppiare gli agenti dall'Arbiter. Con l'introduzione della tabella agent_messages nel database pompeo, questo obiettivo è già raggiunto:
Agente n8n → INSERT agent_messages (arbiter_decision = NULL)
Arbiter → SELECT WHERE arbiter_decision IS NULL (polling a cron)
→ UPDATE arbiter_decision = 'notify' | 'defer' | 'discard'
Il flusso high-priority (bypass immediato dell'Arbiter) viene gestito con una chiamata diretta al webhook n8n dell'Arbiter da parte dell'agente — zero infrastruttura aggiuntiva.
Alternative valutate
| Opzione | Esito | Motivazione |
|---|---|---|
agent_messages su PostgreSQL |
✅ Adottata | Già deployata, persistente, queryabile, audit log gratuito |
| Redis Streams | ⏸ Rimandato | Già in cluster, valutabile se volume cresce |
| NATS JetStream | ❌ Scartato | Nuovo componente da operare, overkill per il volume attuale (pochi msg/ora) e per il caso d'uso single-household |
Impatto su README.md
La sezione "Message Broker (Blackboard Pattern)" rimane valida concettualmente. Il campo agent e il message schema definiti nel README vengono rispettati nella tabella agent_messages — cambia solo il mezzo di trasporto (Postgres invece di NATS/Redis).
[2026-03-21] PostgreSQL — Database "pompeo" e schema ALPHA_PROJECT
Overview
Creato il database pompeo sul cluster Patroni (namespace persistence) e applicato lo schema iniziale per la memoria strutturata di Pompeo. Seconda milestone della Phase 0 — Infrastructure Bootstrap.
Modifica manifest Patroni
Aggiunto pompeo: martin nella sezione databases di infra/cluster/persistence/patroni/postgres.yaml. Il database è stato creato automaticamente dallo Zalando Operator senza downtime sugli altri database.
Script DDL idempotente disponibile in: alpha/db/postgres.sql
Design decision — Multi-tenancy anche in PostgreSQL
Coerentemente con la scelta adottata per Qdrant, tutte le tabelle includono il campo user_id TEXT NOT NULL DEFAULT 'martin'. I valori 'martin' e 'shared' sono seedati in user_profile come utenti iniziali del sistema.
Aggiungere un nuovo utente in futuro non richiede modifiche allo schema — è sufficiente inserire una riga in user_profile e usare il nuovo user_id negli INSERT.
Design decision — agent_messages come blackboard persistente
La tabella agent_messages implementa il blackboard pattern del message broker: ogni agente n8n inserisce le proprie osservazioni con arbiter_decision = NULL (pending). Il Proactive Arbiter legge i messaggi in coda, decide (notify / defer / discard) e aggiorna arbiter_decision, arbiter_reason e processed_at.
Rispetto a usare solo NATS/Redis come broker, questo approccio garantisce un audit log permanente di tutte le osservazioni e decisioni, interrogabile via SQL per debug, tuning e analisi storiche.
Schema creato
5 tabelle nel database pompeo:
| Tabella | Ruolo |
|---|---|
user_profile |
Preferenze statiche per utente (lingua, timezone, stile notifiche, quiet hours). Seed: martin, shared |
memory_facts |
Fatti episodici prodotti da tutti gli agenti, con TTL (expires_at) e riferimento al punto Qdrant (qdrant_id) |
finance_documents |
Documenti finanziari strutturati: bollette, fatture, cedolini. Include raw_text per embedding |
behavioral_context |
Contesto IoT/comportamentale per l'Arbiter: DND, home presence, tipo evento |
agent_messages |
Blackboard del message broker — osservazioni agenti + decisioni Arbiter |
15 index totali:
| Index | Tabella | Tipo |
|---|---|---|
idx_memory_facts_user_source_cat |
memory_facts |
(user_id, source, category) |
idx_memory_facts_expires |
memory_facts |
(expires_at) WHERE NOT NULL |
idx_memory_facts_action |
memory_facts |
(user_id, action_required) WHERE true |
idx_finance_docs_user_date |
finance_documents |
(user_id, doc_date DESC) |
idx_finance_docs_correspondent |
finance_documents |
(user_id, correspondent) |
idx_behavioral_ctx_user_time |
behavioral_context |
(user_id, start_at, end_at) |
idx_behavioral_ctx_dnd |
behavioral_context |
(user_id, do_not_disturb) WHERE true |
idx_agent_msgs_pending |
agent_messages |
(user_id, priority, created_at) WHERE pending |
idx_agent_msgs_agent_type |
agent_messages |
(agent, event_type, created_at) |
idx_agent_msgs_expires |
agent_messages |
(expires_at) WHERE pending AND NOT NULL |
Phase 0 — Stato aggiornato
Deploy Qdrant sul cluster✅ 2026-03-21Collections Qdrant con multi-tenancy✅ 2026-03-21user_idPayload indexes Qdrant✅ 2026-03-21Database✅ 2026-03-21pompeo+ schema PostgreSQL- Verify embedding endpoint via Copilot (
text-embedding-3-small) - Migrazione a Ollama
nomic-embed-text(quando LLM server è online)
[2026-03-21] Qdrant — Deploy e setup collections (Phase 0)
Overview
Completato il deploy di Qdrant v1.17.0 sul cluster Kubernetes (namespace persistence) e la creazione delle collections per la memoria semantica di Pompeo. Questa è la prima milestone della Phase 0 — Infrastructure Bootstrap.
Deploy infrastruttura
Qdrant deployato via Helm chart ufficiale (qdrant/qdrant) nel namespace persistence, coerente con il pattern infrastrutturale esistente (Longhorn storage, Sealed Secrets, ServiceMonitor Prometheus).
Risorse create:
| Risorsa | Dettaglio |
|---|---|
StatefulSet qdrant |
1/1 pod Running, image qdrant/qdrant:v1.17.0 |
PVC qdrant-storage-qdrant-0 |
20Gi Longhorn RWO |
Service qdrant |
ClusterIP — porte 6333 (REST), 6334 (gRPC), 6335 (p2p) |
SealedSecret qdrant-api-secret |
API key cifrata, namespace persistence |
ServiceMonitor qdrant |
Prometheus scraping su :6333/metrics, label release: monitoring |
Endpoint interno: qdrant.persistence.svc.cluster.local:6333
Manifest in: infra/cluster/persistence/qdrant/
Design decision — Multi-tenancy collections (Opzione B)
Problema affrontato: nominare le collections martin_episodes, martin_knowledge, martin_preferences avrebbe vincolato Pompeo ad essere esclusivamente un assistente personale singolo, rendendo impossibile — senza migration — estendere il sistema ad altri membri della famiglia in futuro.
Scelta adottata: architettura multi-tenant con 3 collection condivise e isolamento via campo user_id nel payload di ogni punto vettoriale.
episodes ← user_id: "martin" | "shared" | <futuri utenti>
knowledge ← user_id: "martin" | "shared" | <futuri utenti>
preferences ← user_id: "martin" | "shared" | <futuri utenti>
Il valore "shared" è riservato a dati della casa/famiglia visibili a tutti gli utenti (es. calendario condiviso, documenti di casa, finanze comuni). Le query n8n usano un filtro should: [user_id=martin, user_id=shared] per recuperare sia il contesto personale che quello condiviso.
Vantaggi: aggiungere un nuovo utente domani non richiede alcuna modifica infrastrutturale — solo includere il nuovo user_id negli upsert e nelle query.
Collections create
Tutte e 3 le collections sono operative (status green):
| Collection | Contenuto |
|---|---|
episodes |
Fatti episodici con timestamp (email, IoT, calendario, conversazioni) |
knowledge |
Documenti, note Outline, newsletter, knowledge base |
preferences |
Preferenze, abitudini e pattern comportamentali per utente |
Payload schema comune (5 index su ogni collection):
| Campo | Tipo | Scopo |
|---|---|---|
user_id |
keyword | Filtro multi-tenant ("martin", "shared") |
source |
keyword | Origine del dato ("email", "calendar", "iot", "paperless", …) |
category |
keyword | Dominio semantico ("finance", "work", "personal", …) |
date |
datetime | Timestamp del fatto — filtrabile per range |
action_required |
bool | Flag per il Proactive Arbiter |
Dimensione vettori: 1536 (compatibile con text-embedding-3-small via GitHub Copilot — bootstrap phase). Da rivedere alla migrazione verso nomic-embed-text su Ollama.
Phase 0 — Stato al momento del deploy Qdrant
Deploy Qdrant sul clusterCreazione collections con multi-tenancyuser_idPayload indexes:user_id,source,category,date,action_requiredRun PostgreSQL migrations su Patroni✅ completato nella sessione stessa
[2026-03-21] Media Agent completo (Blocco A + B1 + B2) + Calendar Agent fix
🎬 Blocco A — Jellyfin Playback Agent [Webhook] (AyrKWvboPldzZPsM) ✅
Webhook in real-time su PlaybackStart / PlaybackStop da Jellyfin. Workflow attivo e verificato end-to-end con Ghost in the Shell.
Flusso:
Jellyfin Plugin → POST /webhook/jellyfin-playback
└→ 🔀 Normalizza Evento (Code — JSON.parse body + field mapping)
└→ 🔀 Start o Stop?
├→ [start] 💾 PG - Apri Sessione + 💬 PG - Msg Start
└→ [stop] 💾 PG - Chiudi Sessione + 💬 PG - Msg Stop
Postgres:
behavioral_context: INSERT on start (do_not_disturb=true, notes:{item, device, item_type}), UPDATE on stop (end_at=now(),do_not_disturb=false)agent_messages: soggetto▶️ Ghost in the Shell (Chrome - PC)/⏹️ Ghost in the Shell (Chrome - PC)
Comportamento verificato (test reale):
- PlaybackStart → INSERT corretto,
do_not_disturb=true - PlaybackStop → fired solo alla chiusura del player (non alla pausa) — comportamento nativo Jellyfin
- Filtro utente: solo
UserId = 42369255a7c64917a28fc26d4c7f8265(Martin)
Bug fix applicati durante il debugging:
| # | Problema | Causa | Fix |
|---|---|---|---|
| 1 | Webhook 404 in queue mode | webhookId non era top-level nel nodo JSON → n8n genera path dinamico {workflowId}/{nodeName}/{path} che il webhook pod non carica |
webhookId impostato come campo top-level del nodo |
| 2 | SSL Patroni — self-signed cert reject | n8n Postgres node usa rejectUnauthorized=true di default; Patroni forza hostssl |
NODE_TLS_REJECT_UNAUTHORIZED=0 su n8n-app e n8n-app-worker deployments |
| 3 | Variable $1 out of range Postgres |
additionalFields.queryParams + $json.* non funziona in Postgres node v2 di n8n 2.5.2 |
Migrato a SQL inline con espressioni {{ $json.field }} |
| 4 | Code node filtra tutto — [] |
Jellyfin invia body come stringa JSON ($json.body = '{"ServerId":...}'), non oggetto |
Aggiunto JSON.parse($json.body) nel Code node |
| 5 | Campi undefined | Nomi campo reali Jellyfin: Name, DeviceName/Client, UserId (senza dashes) |
Aggiornati i riferimenti nel Code node |
| 6 | PlaybackStop non arrivava | Il plugin Jellyfin triggerizza Stop solo alla chiusura del player, non alla pausa | Documentato come comportamento atteso |
Recupero n8n API token: il token era corrotto nel contesto LLM (summarizzazione). Recuperato direttamente dal DB n8n: SELECT "apiKey", label FROM user_api_keys; sul database n8n.
🎬 Blocco B1 — Media Library Sync [Schedule] (o3uM1xDLTAKw4D6E) ✅
Weekly cron (domenica 03:00). Costruisce la memoria delle preferenze cinematografiche di Martin.
Flusso:
⏰ Cron domenica 03:00
└→ 🎬 HTTP Radarr (/radarr/api/v3/movie) ──┐
└→ 📺 HTTP Sonarr (/sonarr/api/v3/series) ──┤
🔀 Merge Libreria (Code)
└→ 🔑 Token Copilot
└→ 🤖 GPT-4.1 Analisi
└→ 💾 PG Upsert Preferenze
└→ 🔁 Loop Items
└→ 🔢 Ollama Embed
└→ 🗄️ Qdrant Upsert
Dati estratti dal GPT: top_genres, preferred_types, library_stats, taste_summary, notable_patterns
Postgres: memory_facts (source=media_library, source_ref=media_preferences_summary, expires +7d) — upsert ON CONFLICT
Qdrant media_preferences (collection creata, 768-dim Cosine):
- Embedding: Ollama
nomic-embed-textsu"{title} {year} {genres} {type}" - Payload:
{title, year, type, genres, status, source, source_id, expires_at (+6 mesi)} - Utilità per Pompeo: query semantica tipo "film sci-fi che piacciono a Martin"
Endpoint interni:
- Radarr:
http://radarr.media.svc.cluster.local:7878/radarr/api/v3/movie?apikey=922d1405ab1147019d98a2997d941765(23 film) - Sonarr:
http://sonarr.media.svc.cluster.local:8989/sonarr/api/v3/series?apikey=22140655993a4ff6bf12314813ec6982 - Ollama:
http://ollama.ai.svc.cluster.local:11434/api/embeddings— modelnomic-embed-text(768-dim, multilingual) ✅ operativo - Qdrant:
http://qdrant.persistence.svc.cluster.local:6333— api-key: sealed secretqdrant-api-secret(__Montecarlo00!)
Nota infrastruttura: Radarr e Sonarr girano entrambi nel pod
mediastack(namespacemedia), ma espongono serviziClusterIPseparati. Dall'esterno del cluster le NodePort (30878, 30989) erano irraggiungibili; dall'interno funzionano correttamente. Radarr risponde su/radarr/come base path (redirect 307 senza base path).
🎞️ Blocco B2 — Jellyfin Watch History Sync [Schedule] (K07e4PPANXDkmQsr) ✅
Daily cron (04:00). Costruisce la memoria della cronologia di visione di Martin.
Flusso:
⏰ Cron ogni giorno 04:00
└→ 🎞️ HTTP Jellyfin (/Users/{id}/Items?Recursive=true&SortBy=DatePlayed&Limit=100)
└→ 🔀 Filtra Visti (PlayCount>0, last 90 days)
└→ ❓ Ha Visti? (IF node)
├→ [no] ⛔ Stop
└→ [sì] 🔑 Token Copilot → 🤖 GPT-4.1 → 🔍 Parse → 💾 PG Upsert
Dati estratti dal GPT: recent_favorites, preferred_genres, watch_patterns, completion_rate, notes
Postgres: memory_facts (source=jellyfin, source_ref=watch_history_summary, expires +30d)
Jellyfin API token Pompeo:
- Creato via
POST /Auth/Keys?app=Pompeoautenticandosi comeadmin(password__Montecarlo00!, auth locale — separata dall'Authentik SSO) - Token:
d153606c1ca54574a20d2b40fcf1b02e - Martin UserId:
42369255a7c64917a28fc26d4c7f8265(da DB SQLite Jellyfin + confermato dai payload webhook)
Nota: Jellyfin usa Authentik SSO (OIDC) per il login via browser, ma
adminha ancora l'auth provider locale attivo. Il token API è separato dall'autenticazione SSO e non scade.
Fix workflow esistenti
📅 Calendar Agent (4ZIEGck9n4l5qaDt) — 2 bug fix
Il workflow falliva ogni 30 minuti con column "undefined" does not exist.
Bug 1 — 🗑️ Cleanup Cancellati: quando HA non ha eventi nel range (risposta vuota), Parse GPT restituisce [{json:{skip:true}}]. L'espressione nel Cleanup:
.all().map(i => "'" + i.json.uid.replace(/'/g,"''") + "'").join(',')
chiamava .replace() su undefined (uid non esiste sull'item skip) → l'intera espressione {{ }} valutava a undefined JavaScript → n8n lo inseriva senza virgolette nella SQL → PostgreSQL interpretava undefined come nome di colonna.
Fix: aggiunto .filter(i => i.json.uid) prima del .map() + String() wrapper.
Bug 2 — 💾 Postgres - Salva Evento: ON CONFLICT UPDATE includeva updated_at = NOW() ma la colonna updated_at non esiste in memory_facts. Rimosso dalla clausola DO UPDATE.
[2026-03-21] Paperless Upload — integrazione memoria Postgres + Qdrant
Modifiche al workflow 📄 Paperless — Upload Documento [Multi] (GBPFFq8rmbdFrNn9)
Aggiunto branch parallelo di salvataggio in memoria dopo Paperless - Patch Metadati:
Paperless - Patch Metadati ──┬──> Telegram - Conferma Upload (invariato)
└──> 🧠 Salva in Memoria ──> 💾 Upsert Memoria
🧠 Salva in Memoria (Code):
- Genera embedding del testo (
{title}\n\n{OCR excerpt}) via Ollamanomic-embed-text(768 dim) - Upsert in Qdrant collection
knowledgecon payload:user_id,source,doc_id,title,category,doc_type,correspondent,created_date,tags - Prepara record per Postgres con
source_ref=paperless-{doc_id}, TTL variabile per tipo doc (90gg ricevute, 180gg bollette, 365gg default, 730gg cedolini)
💾 Upsert Memoria (Postgres → mRqzxhSboGscolqI):
INSERT INTO memory_factsconsource='paperless', dedupON CONFLICT memory_facts_dedup_idx DO UPDATE- Salva anche
qdrant_id(UUID del punto Qdrant) per cross-reference futuro
Qdrant collections riconfigurate
Ricreate knowledge e episodes con size=768 (nomic-embed-text) — erano a 1536 (OpenAI legacy, 0 points).
[2026-03-21] Daily Digest — integrazione memoria Postgres
Modifiche al workflow 📬 Gmail — Daily Digest [Schedule] (1lIKvVJQIcva30YM)
Aggiunto branch parallelo di salvataggio fatti in memoria dopo la classificazione GPT:
Parse risposta GPT-4.1 ──┬──> Telegram - Invia Report (invariato)
├──> Dividi Email (invariato)
└──> 🧠 Estrai Fatti ──> 🔀 Ha Fatti? ──> 💾 Upsert Memoria
🧠 Estrai Fatti (Code):
- Filtra le email con
action != 'trash'e summary non vuoto - Chiama GPT-4.1 in batch per estrarre per ogni email:
fact,category,ttl_days,pompeo_note,entity_refs - Calcola
expires_atdattl_days(14gg prenotazioni, 45gg bollette, 90gg lavoro/condominio, 30gg default) - Restituisce un item per ogni fatto da persistere
💾 Upsert Memoria (Postgres node → mRqzxhSboGscolqI):
INSERT INTO memory_factsconsource='email',source_ref=threadIdON CONFLICT ON CONSTRAINT memory_facts_dedup_idx DO UPDATE→ aggiorna se lo stesso thread viene riprocessato- Campi salvati:
category,subject,detail(JSONB),action_required,action_text,pompeo_note,entity_refs,expires_at
Fix contestuale
- Aggiunto
newer_than:1dalla query Gmail su entrambi i nodi fetch — evitava di rifetchare email vecchie di mesi non marcateProcessed
[2026-03-21] Schema DB v2 — contacts, memory_facts_archive, entity_refs
Nuove tabelle
contacts: grafo di persone multi-tenant. Ogni riga modella una relazioneuser_id → subjectconrelation,city,country,profession,aliases[],born_year,details(narrativa libera per LLM) emetadataJSONB. Traversabile ricorsivamente per inferire relazioni di secondo grado (es. Martin → zio Mujsi → figlio Euris → cugino di primo grado da parte di madre). Indici GIN susubject(trigram) ealiasesper similarity search.memory_facts_archive: destinazione del cleanup settimanale dei fatti scaduti. Struttura identica amemory_facts+archived_at+archive_reason(expired|superseded|merged). I fatti archiviati vengono poi condensati in un episodio Qdrant settimanale.
Colonne aggiunte a memory_facts
pompeo_note TEXT: inner monologue dell'LLM al momento dell'insert — il "perché" del fatto (già in uso nel Calendar Agent, ora standardizzato su tutti i source).entity_refs JSONB: entità estratte dal fatto strutturato —{people: [], places: [], products: [], amounts: []}. Permette query SQL su persone/luoghi senza full-text scan (es.entity_refs->'people' ? 'euris vruzhaj').
Applicato a
alpha/db/postgres.sqlaggiornato (schema v2)- Live su Patroni primary (
postgres-1, namespacepersistence, DBpompeo)
[2026-03-21] Actual Budget — Import Estratto Conto via Telegram
Nuovi workflow
💰 Actual — Import Estratto Conto [Telegram](qtvB3r0cgejyCxUp): importa l'estratto conto Banca Sella (CSV) in Actual Budget tramite Telegram.- Trigger: documento Telegram con caption
Estratto conto - Parse CSV Banca Sella (separatore
;, dategg/mm/aaaa, importi con.decimale) - Skip automatico di
SALDO FINALEeSALDO INIZIALE - Classificazione GPT-4.1 in batch da 30 transazioni: assegna payee e categoria, crea automaticamente i mancanti su Actual
- Import via
/transactions/importcon dedup nativo tramiteimported_id(patternbanca-sella-{Id}o hash fallback) - Report Telegram con nuove transazioni importate, già presenti e totale CSV
- Trigger: documento Telegram con caption
⏰ Actual — Reminder Estratto Conto [Schedule](w0oJ1i6sESvaB5W1): reminder giornaliero (09:00) su Telegram se il task Google "Actual - Estratto conto" nella lista "Finanze" è scaduto.
Note tecniche
- Binary data letta con
getBinaryDataBuffer()(compatibile con filesystem binary mode di n8n) - Loop GPT gestito con iterazione interna nel Code node (no
splitInBatches— instabile con input multipli) - Payee/categorie mancanti creati al volo e riutilizzati nei batch successivi della stessa run
- Dedup Actual:
added= nuove,updated= già presenti
[2026-03-21] Calendar Agent — fix sincronizzazione e schedule
Problemi risolti
ON CONFLICT DO NOTHING→DO UPDATE: gli eventi modificati (orario, titolo) venivano ignorati. Ora vengono aggiornati in Postgres.- Cleanup eventi cancellati: aggiunto step
🗑️ Cleanup Cancellatiche esegueDELETE FROM memory_facts WHERE source_ref NOT IN (UID attuali da HA)per la finestra 7 giorni. Se Martin cancella un meeting, sparisce da Postgres al prossimo run. - Schedule
*/30 * * * *: da cron 06:30 giornaliero a ogni 30 minuti — il calendario Postgres è sempre allineato alla source of truth (HA/Google Calendar).
Flusso aggiornato
... → 📋 Parse GPT → 🗑️ Cleanup Cancellati → 🔀 Riemetti → 💾 Upsert → 📦 → 📱
[2026-03-20] Calendar Agent — primo workflow Pompeo in produzione
Cosa è stato fatto
Primo agente Pompeo deployato e attivo su n8n: 📅 Pompeo — Calendar Agent [Schedule] (ID 4ZIEGck9n4l5qaDt).
Design
- Sorgente dati: Home Assistant REST API usata come proxy Google Calendar — evita OAuth Google diretto in n8n e funziona per tutti i 25 calendari registrati in HA.
- Calendari tracciati (12): Lavoro, Famiglia, Spazzatura, Pulizie, Formula 1, WEC, Inter, Compleanni, Varie, Festività Italia, Films (Radarr), Serie TV (Sonarr).
- LLM enrichment: GPT-4.1 (via Copilot) classifica ogni evento: category, action_required, do_not_disturb, priority, behavioral_context, pompeo_note.
- Dedup:
memory_facts.source_ref= HA event UID;ON CONFLICT DO NOTHINGsu indice unico parziale. - Telegram briefing: ogni mattina alle 06:30, riepilogo eventi prossimi 7 giorni raggruppati per calendario.
Migrazioni DB applicate
ALTER TABLE memory_facts ADD COLUMN source_ref TEXT— colonna per ID esterno di dedupCREATE UNIQUE INDEX memory_facts_dedup_idx ON memory_facts (user_id, source, source_ref) WHERE source_ref IS NOT NULLCREATE INDEX idx_memory_facts_source_ref ON memory_facts (source_ref) WHERE source_ref IS NOT NULL
Credential n8n create
| ID | Nome | Tipo |
|---|---|---|
u0JCseXGnDG5hS9F |
Home Assistant API | HTTP Header Auth |
mRqzxhSboGscolqI |
Pompeo — PostgreSQL | Postgres (pompeo/martin) |
Flusso workflow
⏰ Schedule (06:30) → 📅 Range → 🔑 Token Copilot
→ 📋 Calendari (12 items) → 📡 HA Fetch (×12) → 🏷️ Estrai + Tag
→ 📝 Prompt (dedup) → 🤖 GPT-4.1 → 📋 Parse
→ 💾 Postgres Upsert (memory_facts) → 📦 Aggrega → 📱 Telegram
[2026-03-21] ADR — Message Broker: nessun broker dedicato
Decisione
Non verrà deployato un message broker dedicato (né NATS JetStream né Redis Streams). Il blackboard pattern viene implementato interamente su PostgreSQL + webhook n8n.
Ragionamento
Al momento della progettazione iniziale, il broker era necessario per disaccoppiare gli agenti dall'Arbiter. Con l'introduzione della tabella agent_messages nel database pompeo, questo obiettivo è già raggiunto:
Agente n8n → INSERT agent_messages (arbiter_decision = NULL)
Arbiter → SELECT WHERE arbiter_decision IS NULL (polling a cron)
→ UPDATE arbiter_decision = 'notify' | 'defer' | 'discard'
Il flusso high-priority (bypass immediato dell'Arbiter) viene gestito con una chiamata diretta al webhook n8n dell'Arbiter da parte dell'agente — zero infrastruttura aggiuntiva.
Alternative valutate
| Opzione | Esito | Motivazione |
|---|---|---|
agent_messages su PostgreSQL |
✅ Adottata | Già deployata, persistente, queryabile, audit log gratuito |
| Redis Streams | ⏸ Rimandato | Già in cluster, valutabile se volume cresce |
| NATS JetStream | ❌ Scartato | Nuovo componente da operare, overkill per il volume attuale (pochi msg/ora) e per il caso d'uso single-household |
Impatto su README.md
La sezione "Message Broker (Blackboard Pattern)" rimane valida concettualmente. Il campo agent e il message schema definiti nel README vengono rispettati nella tabella agent_messages — cambia solo il mezzo di trasporto (Postgres invece di NATS/Redis).
[2026-03-21] PostgreSQL — Database "pompeo" e schema ALPHA_PROJECT
Overview
Creato il database pompeo sul cluster Patroni (namespace persistence) e applicato lo schema iniziale per la memoria strutturata di Pompeo. Seconda milestone della Phase 0 — Infrastructure Bootstrap.
Modifica manifest Patroni
Aggiunto pompeo: martin nella sezione databases di infra/cluster/persistence/patroni/postgres.yaml. Il database è stato creato automaticamente dallo Zalando Operator senza downtime sugli altri database.
Script DDL idempotente disponibile in: alpha/db/postgres.sql
Design decision — Multi-tenancy anche in PostgreSQL
Coerentemente con la scelta adottata per Qdrant, tutte le tabelle includono il campo user_id TEXT NOT NULL DEFAULT 'martin'. I valori 'martin' e 'shared' sono seedati in user_profile come utenti iniziali del sistema.
Aggiungere un nuovo utente in futuro non richiede modifiche allo schema — è sufficiente inserire una riga in user_profile e usare il nuovo user_id negli INSERT.
Design decision — agent_messages come blackboard persistente
La tabella agent_messages implementa il blackboard pattern del message broker: ogni agente n8n inserisce le proprie osservazioni con arbiter_decision = NULL (pending). Il Proactive Arbiter legge i messaggi in coda, decide (notify / defer / discard) e aggiorna arbiter_decision, arbiter_reason e processed_at.
Rispetto a usare solo NATS/Redis come broker, questo approccio garantisce un audit log permanente di tutte le osservazioni e decisioni, interrogabile via SQL per debug, tuning e analisi storiche.
Schema creato
5 tabelle nel database pompeo:
| Tabella | Ruolo |
|---|---|
user_profile |
Preferenze statiche per utente (lingua, timezone, stile notifiche, quiet hours). Seed: martin, shared |
memory_facts |
Fatti episodici prodotti da tutti gli agenti, con TTL (expires_at) e riferimento al punto Qdrant (qdrant_id) |
finance_documents |
Documenti finanziari strutturati: bollette, fatture, cedolini. Include raw_text per embedding |
behavioral_context |
Contesto IoT/comportamentale per l'Arbiter: DND, home presence, tipo evento |
agent_messages |
Blackboard del message broker — osservazioni agenti + decisioni Arbiter |
15 index totali:
| Index | Tabella | Tipo |
|---|---|---|
idx_memory_facts_user_source_cat |
memory_facts |
(user_id, source, category) |
idx_memory_facts_expires |
memory_facts |
(expires_at) WHERE NOT NULL |
idx_memory_facts_action |
memory_facts |
(user_id, action_required) WHERE true |
idx_finance_docs_user_date |
finance_documents |
(user_id, doc_date DESC) |
idx_finance_docs_correspondent |
finance_documents |
(user_id, correspondent) |
idx_behavioral_ctx_user_time |
behavioral_context |
(user_id, start_at, end_at) |
idx_behavioral_ctx_dnd |
behavioral_context |
(user_id, do_not_disturb) WHERE true |
idx_agent_msgs_pending |
agent_messages |
(user_id, priority, created_at) WHERE pending |
idx_agent_msgs_agent_type |
agent_messages |
(agent, event_type, created_at) |
idx_agent_msgs_expires |
agent_messages |
(expires_at) WHERE pending AND NOT NULL |
Phase 0 — Stato aggiornato
Deploy Qdrant sul cluster✅ 2026-03-21Collections Qdrant con multi-tenancy✅ 2026-03-21user_idPayload indexes Qdrant✅ 2026-03-21Database✅ 2026-03-21pompeo+ schema PostgreSQL- Verify embedding endpoint via Copilot (
text-embedding-3-small) - Migrazione a Ollama
nomic-embed-text(quando LLM server è online)
[2026-03-21] Qdrant — Deploy e setup collections (Phase 0)
Overview
Completato il deploy di Qdrant v1.17.0 sul cluster Kubernetes (namespace persistence) e la creazione delle collections per la memoria semantica di Pompeo. Questa è la prima milestone della Phase 0 — Infrastructure Bootstrap.
Deploy infrastruttura
Qdrant deployato via Helm chart ufficiale (qdrant/qdrant) nel namespace persistence, coerente con il pattern infrastrutturale esistente (Longhorn storage, Sealed Secrets, ServiceMonitor Prometheus).
Risorse create:
| Risorsa | Dettaglio |
|---|---|
StatefulSet qdrant |
1/1 pod Running, image qdrant/qdrant:v1.17.0 |
PVC qdrant-storage-qdrant-0 |
20Gi Longhorn RWO |
Service qdrant |
ClusterIP — porte 6333 (REST), 6334 (gRPC), 6335 (p2p) |
SealedSecret qdrant-api-secret |
API key cifrata, namespace persistence |
ServiceMonitor qdrant |
Prometheus scraping su :6333/metrics, label release: monitoring |
Endpoint interno: qdrant.persistence.svc.cluster.local:6333
Manifest in: infra/cluster/persistence/qdrant/
Design decision — Multi-tenancy collections (Opzione B)
Problema affrontato: nominare le collections martin_episodes, martin_knowledge, martin_preferences avrebbe vincolato Pompeo ad essere esclusivamente un assistente personale singolo, rendendo impossibile — senza migration — estendere il sistema ad altri membri della famiglia in futuro.
Scelta adottata: architettura multi-tenant con 3 collection condivise e isolamento via campo user_id nel payload di ogni punto vettoriale.
episodes ← user_id: "martin" | "shared" | <futuri utenti>
knowledge ← user_id: "martin" | "shared" | <futuri utenti>
preferences ← user_id: "martin" | "shared" | <futuri utenti>
Il valore "shared" è riservato a dati della casa/famiglia visibili a tutti gli utenti (es. calendario condiviso, documenti di casa, finanze comuni). Le query n8n usano un filtro should: [user_id=martin, user_id=shared] per recuperare sia il contesto personale che quello condiviso.
Vantaggi: aggiungere un nuovo utente domani non richiede alcuna modifica infrastrutturale — solo includere il nuovo user_id negli upsert e nelle query.
Collections create
Tutte e 3 le collections sono operative (status green):
| Collection | Contenuto |
|---|---|
episodes |
Fatti episodici con timestamp (email, IoT, calendario, conversazioni) |
knowledge |
Documenti, note Outline, newsletter, knowledge base |
preferences |
Preferenze, abitudini e pattern comportamentali per utente |
Payload schema comune (5 index su ogni collection):
| Campo | Tipo | Scopo |
|---|---|---|
user_id |
keyword | Filtro multi-tenant ("martin", "shared") |
source |
keyword | Origine del dato ("email", "calendar", "iot", "paperless", …) |
category |
keyword | Dominio semantico ("finance", "work", "personal", …) |
date |
datetime | Timestamp del fatto — filtrabile per range |
action_required |
bool | Flag per il Proactive Arbiter |
Dimensione vettori: 1536 (compatibile con text-embedding-3-small via GitHub Copilot — bootstrap phase). Da rivedere alla migrazione verso nomic-embed-text su Ollama.
Phase 0 — Stato al momento del deploy Qdrant
Deploy Qdrant sul clusterCreazione collections con multi-tenancyuser_idPayload indexes:user_id,source,category,date,action_requiredRun PostgreSQL migrations su Patroni✅ completato nella sessione stessa