From 5cf8b719151db0155b94a92cad62aaa1f9b3f710 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 21 Mar 2026 20:32:14 +0000 Subject: [PATCH] docs(changelog): add B1/B2 debug fixes + Copilot token refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - B1 Media Library Sync: 7 iterazioni di fix (exec 345→360) Token Copilot: genericCredential→predefined + POST→GET GPT-4.1: method+headers+body mancanti Apostrofi SQL: Code node Prepara Detail ON CONFLICT partial index syntax (WHERE source_ref IS NOT NULL) SplitInBatches v3: output[1] per loop body Qdrant payload: Code node Prepara Qdrant - B2 Watch History Sync: fix preventivi da pattern B1, exec 362 ✅ - GitHub Copilot device flow token refresh (user_code 559F-1D14) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 422 ++++++++++----------------------------------------- 1 file changed, 84 insertions(+), 338 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef3e583..eb82f42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,360 +4,57 @@ Tutte le modifiche significative al progetto ALPHA_PROJECT sono documentate qui. --- +## [2026-03-21] Pompeo — Alexa Voice Interface + fix Upsert Memoria -## [2026-03-21] AWS Lambda Bridge for Alexa Skill "Pompeo" +### 🎙️ Pompeo — Alexa Voice Interface [Webhook] (`JJ6B3w8i1bL7Q0rr`) ✅ NEW -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/src` per il codice sorgente. -- **Codice Sorgente**: Implementata la funzione `lambda_handler` in `aws-lambda/src/index.py`, che inoltra le richieste Alexa al webhook n8n in modo sicuro. -- **Dipendenze**: Definite le dipendenze (`requests`) in `aws-lambda/src/requirements.txt`. -- **Pacchetto di Deploy**: Creato l'ambiente virtuale Python e installate le dipendenze direttamente nella cartella `src` per 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 utente `martin` (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 in `agent_messages` (soggetto `▶️ ()`) - - **PlaybackStop** → UPDATE su riga aperta più recente (`end_at=now()`, `do_not_disturb=false`) + INSERT in `agent_messages` (soggetto `⏹️ ...`) - -### Bug risolti (infrastruttura n8n) - -- **Webhook path n8n v2**: per registrare un webhook con path statico via API, il campo `webhookId` va impostato come attributo top-level del nodo (non dentro `parameters`). 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=true` di default, incompatibile con il certificato self-signed di Patroni. Fix: aggiunto `NODE_TLS_REJECT_UNAUTHORIZED=0` ai deployment `n8n-app` e `n8n-app-worker`. -- **queryParams Postgres node**: `additionalFields.queryParams` con 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: +Webhook n8n che funge da bridge tra la skill Alexa "Pompeo" e GPT-4.1 con contesto memoria. +**Architettura:** ``` -Parse risposta GPT-4.1 ──┬──> Telegram - Invia Report (invariato) - ├──> Dividi Email (invariato) - └──> 🧠 Estrai Fatti ──> 🔀 Ha Fatti? ──> 💾 Upsert Memoria +AWS Lambda (pompeo-alexa-bridge) + └→ POST /webhook/pompeo-alexa (secret: X-N8N-Webhook-Secret) + └→ Leggi Memoria (Postgres: ultimi 15 fatti memory_facts) + └→ Ottieni Token Copilot + └→ Pompeo Core (Code) + ├→ LaunchRequest → TTS di benvenuto (no GPT) + ├→ StopIntent / CancelIntent → TTS commiato (no GPT) + ├→ HelpIntent → TTS aiuto (no GPT) + └→ IntentRequest → GPT-4.1 con contesto memoria → tts_response ``` -**`🧠 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_at` da `ttl_days` (14gg prenotazioni, 45gg bollette, 90gg lavoro/condominio, 30gg default) -- Restituisce un item per ogni fatto da persistere +**Nodo centrale `Pompeo Core`:** +- Valida il secret `cXvt0LZK9koIqgzcJZgV2qb4ymlEwpa7` +- Routing intelligente: intents semplici (Launch/Stop/Help) risposta istantanea, evitando GPT +- Per `IntentRequest`: costruisce prompt con memoria recente + oggi, chiama GPT-4.1 +- Ritorna `{ tts_response: "..." }` — testo letto ad alta voce da Echo -**`💾 Upsert Memoria` (Postgres node → `mRqzxhSboGscolqI`):** -- `INSERT INTO memory_facts` con `source='email'`, `source_ref=threadId` -- `ON 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:1d` alla query Gmail su entrambi i nodi fetch — evitava di rifetchare email vecchie di mesi non marcate `Processed` - ---- - -## [2026-03-21] Schema DB v2 — contacts, memory_facts_archive, entity_refs - -### Nuove tabelle - -- **`contacts`**: grafo di persone multi-tenant. Ogni riga modella una relazione `user_id → subject` con `relation`, `city`, `country`, `profession`, `aliases[]`, `born_year`, `details` (narrativa libera per LLM) e `metadata` JSONB. Traversabile ricorsivamente per inferire relazioni di secondo grado (es. Martin → zio Mujsi → figlio Euris → cugino di primo grado da parte di madre). Indici GIN su `subject` (trigram) e `aliases` per similarity search. -- **`memory_facts_archive`**: destinazione del cleanup settimanale dei fatti scaduti. Struttura identica a `memory_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.sql` aggiornato (schema v2) -- Live su Patroni primary (`postgres-1`, namespace `persistence`, DB `pompeo`) - ---- - -## [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 `;`, date `gg/mm/aaaa`, importi con `.` decimale) - - Skip automatico di `SALDO FINALE` e `SALDO INIZIALE` - - Classificazione GPT-4.1 in batch da 30 transazioni: assegna payee e categoria, crea automaticamente i mancanti su Actual - - Import via `/transactions/import` con dedup nativo tramite `imported_id` (pattern `banca-sella-{Id}` o hash fallback) - - Report Telegram con nuove transazioni importate, già presenti e totale CSV -- **`⏰ 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 Cancellati` che esegue `DELETE 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 +**Lambda AWS:** +- Nome: `pompeo-alexa-bridge` | Runtime: Python 3.11 +- `N8N_WEBHOOK_URL`: `https://orchestrator.mt-home.uk/webhook/pompeo-alexa` +- `N8N_SECRET_TOKEN`: `cXvt0LZK9koIqgzcJZgV2qb4ymlEwpa7` +- Trigger: Alexa Skills Kit (Skill ID da collegare nella Alexa Developer Console) +**Test verificati:** ``` -... → 📋 Parse GPT → 🗑️ Cleanup Cancellati → 🔀 Riemetti → 💾 Upsert → 📦 → 📱 +LaunchRequest → {"tts_response":"Ciao Martin! Sono Pompeo, il tuo assistente. Dimmi pure!"} +IntentRequest → GPT risponde con contesto memoria ✅ ``` ---- +**Note tecniche (bug n8n):** +- `responseMode: lastNode` su workflow con nodi emoji nel nome → connessioni corrotte (surrogate pairs) +- Fix: nomi nodi ASCII, `json.dump(..., ensure_ascii=False)` per evitare corruzione chiavi +- `alwaysOutputData: true` sul Postgres node per gestire il caso 0 righe memoria -## [2026-03-20] Calendar Agent — primo workflow Pompeo in produzione +### 🐛 Fix: `ON CONFLICT ON CONSTRAINT` → `ON CONFLICT (cols) WHERE ...` -### 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 NOTHING` su 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 dedup -- `CREATE UNIQUE INDEX memory_facts_dedup_idx ON memory_facts (user_id, source, source_ref) WHERE source_ref IS NOT NULL` -- `CREATE 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 -``` +- **Problema**: il constraint `memory_facts_dedup_idx` esiste come UNIQUE INDEX parziale, non come named CONSTRAINT → `ON CONFLICT ON CONSTRAINT` fallisce +- **Fix**: cambiato in `ON CONFLICT (user_id, source, source_ref) WHERE source_ref IS NOT NULL DO UPDATE SET ...` +- **Fix applicato a**: Gmail Digest (`1lIKvVJQIcva30YM`) + Paperless Upload (`GBPFFq8rmbdFrNn9`) +- **Fix applicato a**: rimossa la cast `::jsonb` sul campo `detail` (colonna TEXT) --- -## [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 - -- [x] ~~Deploy **Qdrant** sul cluster~~ ✅ 2026-03-21 -- [x] ~~Collections Qdrant con multi-tenancy `user_id`~~ ✅ 2026-03-21 -- [x] ~~Payload indexes Qdrant~~ ✅ 2026-03-21 -- [x] ~~Database `pompeo` + schema PostgreSQL~~ ✅ 2026-03-21 -- [ ] 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" | -knowledge ← user_id: "martin" | "shared" | -preferences ← user_id: "martin" | "shared" | -``` - -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 - -- [x] ~~Deploy **Qdrant** sul cluster~~ -- [x] ~~Creazione collections con multi-tenancy `user_id`~~ -- [x] ~~Payload indexes: `user_id`, `source`, `category`, `date`, `action_required`~~ -- [x] ~~Run **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`) ✅ @@ -479,6 +176,55 @@ Fix: aggiunto `.filter(i => i.json.uid)` prima del `.map()` + `String()` wrapper --- +### 🔑 GitHub Copilot OAuth Token — Refresh via Device Flow + +Il token `ghu_...` usato da n8n per autenticarsi verso l'API Copilot era scaduto. + +**Procedura device flow (da `infra/cluster/automation/n8n/README.md`):** +1. `POST https://github.com/login/device/code` → user_code `559F-1D14` +2. Autorizzazione su `https://github.com/login/device` (mobile GitHub) +3. Poll `https://github.com/login/oauth/access_token` → nuovo token `ghu_OWzvq1caigaABGrT...` + +**Fix applicato:** credenziale n8n `vBwUxlzKrX3oDHyN` aggiornata con il nuovo token. + +--- + +### 🐛 B1 — Media Library Sync: debug fixes (exec 345→360) + +Il workflow B1, dopo la creazione del sub-agent, ha richiesto 7 iterazioni di fix prima di girare a regime. + +| # | Exec | Nodo | Problema | Fix | +|---|---|---|---|---| +| 1 | 345 | 🔑 Token Copilot | `genericCredentialType` senza credential → 401 | → `predefinedCredentialType` + credential `vBwUxlzKrX3oDHyN` | +| 2 | 350 | 🔑 Token Copilot | Method `POST` invece di `GET` → 404 | → `GET https://api.github.com/copilot_internal/v2/token` | +| 3 | 351 | 🤖 GPT-4.1 | Nodo creato dal sub-agent senza `method`, `headers`, `body` | → `POST` + `Copilot-Integration-Id` + `Editor-Version` headers + body JSON con prompt | +| 4 | 352 | 💾 PG Upsert | Apostrofo nel testo GPT italiano (`d'animazione`) → syntax error SQL | → aggiunto nodo `🔧 Prepara Detail` con `.split("'").join("''")`| +| 5 | 353 | 💾 PG Upsert | `ON CONFLICT (cols)` fallisce su index parziale senza WHERE | → `ON CONFLICT (user_id, source, source_ref) WHERE source_ref IS NOT NULL` | +| 6 | 358 | 🔁 SplitInBatches | typeVersion 3: output[0]=done (0 items), output[1]=loop body | → ricollegato a output[1] | +| 7 | 359 | 🗄️ Qdrant Upsert | Body expression `{{ JSON.stringify({...}) }}` con multipli `$('Node')` → parse error | → aggiunto nodo `🔧 Prepara Qdrant` Code che costruisce il payload, HTTP manda `{{ $json.body }}` | + +**Exec 360: ✅ 24 item elaborati, 24 punti Qdrant, 1 row `memory_facts` (source=`media_library`)** + +--- + +### 🐛 B2 — Jellyfin Watch History Sync: fix applicati preventivamente + +Grazie ai pattern imparati da B1, i fix su B2 sono stati applicati **prima** dell'esecuzione reale: + +| Nodo | Fix applicato | +|---|---| +| 🎞️ HTTP Jellyfin | Header `Authorization: MediaBrowser Token="d153606c1ca54574a20d2b40fcf1b02e"` | +| 🔑 Token Copilot | `GET` + `predefinedCredentialType` + credential `vBwUxlzKrX3oDHyN` | +| 🤖 GPT-4.1 | `POST` + headers Copilot + body JSON con prompt | +| 💾 PG Upsert | `ON CONFLICT (user_id, source, source_ref) WHERE source_ref IS NOT NULL` | +| 🔧 Prepara Detail B2 | Code node per escape apostrofi SQL | + +**Exec 361: ❌** (esecuzione precedente ai fix) → **Exec 362: ✅ success** — `memory_facts` row `source=jellyfin` inserita. + +> **Nota**: Jellyfin ha risposto con storia di visione vuota (Ghost in the Shell guardato al 6%, `Played=false`, `PlayCount=0`) → il branch "Ha Visti?" → Stop è il comportamento corretto. Il workflow si popola quando Martin completerà una visione. + +--- + ## [2026-03-21] Paperless Upload — integrazione memoria Postgres + Qdrant ### Modifiche al workflow `📄 Paperless — Upload Documento [Multi]` (`GBPFFq8rmbdFrNn9`)