Files
ThothII/docs/prd/2026-08-21-modularizzazione-disambiguazione-evidence-memory-prd.md
T

18 KiB
Raw Blame History

PRD — Modularizzazione a moduli incapsulati: disambiguazione, evidence, memory

Status: riflessione architetturale di plausibilità — nessuna decisione implementativa presa qui; il documento fissa confini, contratti condivisi e precondizioni da cui potranno nascere ticket e piani separati Data: 2026-08-21 Autore: analisi del codice su main (5f05754), su richiesta del proprietario; incrocia e non sostituisce i documenti evidence-canonica già esistenti (sez. 9) Uso: riferimento per il programma di sviluppo parallelo dei tre sottosistemi (disambiguazione, gestione evidence, gestione memory); definisce cosa deve essere vero prima che i tre flussi possano girare in parallelo senza impatti incrociati


1. L'obiettivo del proprietario

Avviare uno sviluppo che cambi in parallelo tre aree del sistema:

  1. disambiguazione (estratta e separata dal workflow);
  2. gestione delle evidence — nuovo componente che espone un'API verso il resto del workflow ma resta ben incapsulato al suo interno;
  3. gestione della memory — con lo stesso grado di forte incapsulamento.

Vincolo accettato: le modifiche fatte su un modulo non devono impattare gli altri moduli se non per eventuali ridefinizioni dei contratti (API) esposti.

2. Verdetto sintetico

Plausibile, e il codebase è insolitamente ben predisposto per farlo. Ma con tre precisazioni oneste:

  1. i tre componenti sono oggi a tre livelli di maturità diversi: evidence è quasi un modulo, memory è già incapsulata meglio di quanto sembri, la disambiguazione non è un componente di codice e "estrarla" è un lavoro di natura diversa (sez. 4);
  2. esistono due "quinti moduli" nascosti — il ledger dei tipi di decisione con workflow.yaml, e il gate tht-gate.js con il vector store — che il piano a tre vie deve contemplare, altrimenti il parallelismo si rompe dove non si sta guardando (ledger e gate sono spiegati in sez. 3; le implicazioni in sez. 5);
  3. gli accoppiamenti reali non sono negli import Python: sono nei decision type del ledger, nelle proiezioni search pack/search find, nel modello di contenuto della memory e negli id evidence citabili. Sono quelli i contratti da definire e versionare prima del parallelismo (sez. 6–7).

3. Ledger e gate: i due elementi cardine del workflow

Il resto del documento usa di continuo due concetti che conviene fissare qui, perché sono i punti dove i tre moduli si incontrano davvero: il ledger (dove lo stato viene registrato) e il gate (chi decide e chi scrive).

3.1 Il ledger — il registro delle decisioni (review_decisions.jsonl)

Una sessione è una directory: manifest + artefatti per fase (question.md, schema_linking.json, sql_final.sql, …) + un file review_decisions.jsonl. Il ledger è append-only: una riga JSON per ogni decisione del reviewer, con numero progressivo seq, timestamp, type, subject, detail, rationale (più i campi di rollback retracts/phase). I DecisionType definiti in tht/decisions.py sono esattamente 30.

Esempio concreto: a F1 il reviewer sceglie "per 'ablazione' intendo la colonna flag_ablazione della fact delle procedure" → nel ledger viene accodata una riga come {"seq": 3, "ts": "…", "type": "concept_clarified", "subject": "ablazione", "detail": "…", "rationale": "…"}.

Due proprietà lo rendono cardine:

  1. la fase corrente non è memorizzata: è calcolata rileggendo (fold) il ledger (tht/phase.py). Il ledger è la fonte di verità — "ciò che non è registrato non è accaduto". Il rollback non cancella nulla: aggiunge decision_retracted/ phase_reopened e la vista effettiva (effective_decisions) esclude le righe ritirate e quelle rimaste stale oltre il punto di rollback;
  2. è il canale di comunicazione tra i moduli: la disambiguazione scrive concept_clarified/ambiguity_open/value_grounded; l'evidence scrive evidence_accepted/evidence_rejected/concept_formula_approved|rejected; la memory legge concept_clarified (per costruire le promozioni) e scrive memory_promoted/ memory_promotion_declined. Per questo l'insieme dei decision type è un contratto: ridefinirlo (sez. 6.1) è l'equivalente di cambiare una API tra moduli.

3.2 Il gate — l'estensione che impone il human-in-the-loop (tht-gate.js)

ThothII non esegue il workflow con codice: lo esegue un agente LLM (Pi) che segue SKILL.md. Il modello propone, ma non decide e non persiste direttamente. Il gate è l'estensione JavaScript di Pi (~2000 righe, harness/.pi/extensions/tht-gate.js) che svolge tre lavori:

  1. traduce le proposte del modello in widget per l'umano: il modello chiama un tool del gate — reviewer_select (scelta singola), reviewer_decide (multiselect), reviewer_confirm (gate su artefatto/fase) più i specializzati rewrite_question, reviewer_schema_linking, reviewer_datamart, reviewer_memory_promote, write_schema_linking, write_cte_sql, write_final_sql; il gate costruisce il descriptor del widget → RPC → backend → SSE → browser, dove il frontend lo renderizza dal registry src/widgets/;
  2. persiste attraverso un unico canale: alla risposta del reviewer, è il gate (non il modello) a invocare la CLI tht (es. tht memory promote --preview --json, tht session sync-schema-linking) e ad accodare la riga nel ledger;
  3. fa rispettare le regole (enforcement): l'hook anti-bypass blocca tht phase advance, tht decision add, tht memory promote|save-one se il modello prova a eseguirli direttamente dalla shell; i payload vengono validati (TypeBox) e gli artefatti mostrati al reviewer sono ricostruiti in modo deterministico (es. il widget CTE mostra il SQL persistito via tht cte info, non il testo del modello); le chiusure di fase deterministiche sono automatiche.

Esempio concreto (F8): il modello chiama reviewer_memory_promote; il gate esegue tht memory promote --preview --json, costruisce la checklist pre-selezionata; il reviewer conferma; il gate salva le memory scelte (memory save-one), registra memory_promoted/memory_promotion_declined nel ledger e finalizza la sessione. Il modello non ha mai scritto nulla di propria mano.

Perché è cardine per la modularizzazione: il gate è l'unico punto in cui i tre moduli appaiono all'utente e in cui le loro regole vengono imposte — ogni cambiamento a disambiguazione/evidence/memory ha quasi sempre una controparte nel gate (sez. 5.2).

4. Stato attuale dei tre componenti

4.1 Evidence — il più vicino a essere un modulo

Fatti verificati nel codice:

  • esiste già un port vero (harness/tht/ports/evidence.py: discover/acquire, credential-free, contratti Pydantic congelati) con tre adapter dietro il port (adapters/evidence/{filesystem,http,s3}.py);
  • la pipeline di canonizzazione/indicizzazione vive in harness/tht/corpus/ (sorgente → normalize → chunk → embed → vector store, generazioni isolate stage→publish);
  • la superficie di consumo del workflow è già quasi tutta incanalata in due comandi con JSON pulito: tht search pack (sezione evidence: top 5, proiezione {title, status, excerpt[:400]}) e tht search find [--kind evidence|formula]. Quella proiezione è già di fatto un'API: evidence-canonica può rifare tutto dietro di essa (card, involucro tipizzato, retrieval duale) senza che F1 se ne accorga, finché il contratto di proiezione regge;
  • l'inventario dei punti di consumo è già fatto: verifica-workflow.md elenca i 12 touchpoint (T1–T12) con meccanismo e requisito. Quel documento è la bozza del contratto esterno del modulo.

Ciò che manca all'incapsulamento sono esattamente i tre GAP lì identificati:

  • GAP 1 — identità citabile: il processo tratta l'id evidence come documento (F3 lo cita, F4 lo attacca ai candidati, il report di sessione lo risolve in file via resolve_evidence_file/rglob). La decisione già presa (id citabile doc#card) richiede l'estensione del resolver: è l'ultimo passo di contratto da chiudere;
  • GAP 2 — formula store: lettura cablata e funzionante, scrittura senza proprietario di produzione. La decisione già presa (le card famiglia 3 del corpus canonico sono fonte di verità; i .sql.md sono derivati della pipeline) assegna il proprietario senza toccare il contratto di lettura D14b;
  • GAP 3 — conoscenza di dominio nei testi istruzionali: dialetto, ricette temporali PSD, esempi clinici dentro SKILL.md e reference doc. È contenuto evidence sparso nel workflow: la migrazione verso il corpus canonico è parte del lavoro di confine, non un'attività cosmetica successiva.

Nota trasversale: il ciclo di vita evidence attraversa due linguaggi — materializzazione e ciclo di vita della collection stanno nel backend TypeScript (P4/P6), corpus e ricerca nello harness Python. L'API del modulo deve quindi essere il contratto CLI/JSON (che è già lo stile di casa: --json pristine), non un'interfaccia Python.

4.2 Memory — già incapsulata, con un accoppiamento semantico da esplicitare

Fatti verificati:

  • il modulo vive in harness/tht/memory.py + tht/solved.py + cli/memory_cmd.py, con registro JSONL a scrittura atomica e dedup del contenuto reviewer-visibile;
  • i punti di contatto col workflow sono esattamente tre: F2 (ricerca + applicazione via gate), F8 (gate di promozione), finalize (indicizzazione solved_question, best-effort);
  • il gate consuma la memory solo tramite CLI: tht memory promote --preview --json e memory save-one; la logica di calcolo dei candidati (dedup, decisioni storie post-rollback, seq rifiutate, esclusione delle già decise nella sessione) è tutta dentro memory.py. È incapsulamento vero, già operativo.

L'accoppiamento che conta non è di codice ma semantico: REUSABLE_TYPES = {"concept_clarified"} — il modello di contenuto della memory è definito dal tipo di output della disambiguazione. Se il ridisegno F1 cambia cosa sia un chiarimento (target group, tassonomia dell'ambiguità, caso unanswerable), cambia anche cosa la memory può promuovere e come. Il contratto disambiguazione→memory va definito prima di far girare i due flussi in parallelo, o si scontreranno esattamente lì.

4.3 Disambiguazione — non è un componente di codice

Il punto onesto: non c'è niente da "estrarre" nel senso classico. Oggi la disambiguazione è il comportamento dell'LLM in F1/F3/F4, governato da (ledger e gate: sez. 3):

  • prosa (SKILL.md + rewriting.md): una sola ambiguità per volta, opzioni informate, "lascia aperta l'ambiguità" esplicita;
  • retrieval: pack iniettato (retrieval_pack.md), tht search find, tht schema render, LSH con aggregate_lsh_multi che espone tutte le colonne dove un valore appare (es. "ablazione" su flag booleano + campo patologia testuale);
  • ledger: concept_clarified, ambiguity_open, value_grounded, question_rewritten;
  • gate: i widget che presentano le interpretazioni candidate.

"Extractorla" significa quindi, in ordine:

  1. definire il suo contratto input/output — in: domanda + contesto di retrieval; out: ambiguità con target group, opzioni candidate, segnale unanswerable distinto dall'ambiguo. È ciò che impact-ridisegno-disambiguazione.md §3 già elenca (retrieval duale lessicale+semantico, target group, unanswerable);
  2. darle una superficie che l'orchestratore consuma (sezione del pack, comando, sezione skill dedicata) invece di comportamento libero del modello;
  3. spostare la conoscenza che usa fuori da SKILL.md e dentro le evidence (GAP 3).

Rischio specifico e strutturale: l'LLM è colla che può bypassare qualunque contratto su carta. In questo codebase l'unico enforcement che funziona è a livello gate/CLI — il pattern è già dimostrato dall'hook anti-bypass che blocca tht phase advance/decision add da shell e tht memory promote/ save-one fuori dal gate. Finché il contratto di disambiguazione non è enforcement (hook + CLI + skill come contratto completo), la proprietà "le modifiche non impattano altri moduli" è una speranza, non una proprietà verificabile.

5. I due quinti moduli nascosti

5.1 Il ledger + workflow.yaml — la colonna vertebrale condivisa

Tutti e tre i moduli leggono/scrivono il decision ledger (review_decisions.jsonl, sez. 3.1): la disambiguazione emette concept_clarified/value_grounded; l'evidence emette evidence_accepted/evidence_rejected/ concept_formula_approved|rejected; la memory legge concept_clarified ed emette memory_*. Il campo emits di workflow.yaml accoppia fasi e decision type; phase.py piega il ledger per calcolare la fase corrente.

Questo è un bene: il ledger è già un contratto a eventi tra i moduli. Ma è superficie condivisa — tutti e tre i flussi vorranno toccarla. Va trattata come contratto a sé (quarto), con controllo dei cambiamenti, non come territorio libero.

5.2 Il gate (tht-gate.js) e il vector store — i punti caldi di merge

  • Il gate (sez. 3; ~2000 righe + builders/enrich/artifact-contracts) renderizza candidati evidence, dedup opzioni memory, costruisce il widget schema-linking con id evidence, guida F2/F8 memory e fa enforcement anti-bypass. Tutti e tre i flussi avranno bisogno di modifiche al gate: se non viene spaccato lungo gli stessi confini dei moduli, diventa il punto caldo di merge del programma parallelo.
  • Il vector store: una collection Qdrant per workspace, kind-separata (schema_table, schema_column, evidence, memory, solved_question); il contratto di collection e payload-index è di proprietà del backend TS (P4), lettura/scrittura sono Python. I record schema/evidence sono revision-scoped (P3), memory/solved workspace-wide. Se evidence-canonica cambia la forma dei record/payload (il frontmatter già fluisce libero nei metadata), gli assunti di indicizzazione della memory ne risentono. Il vector store va designato come infrastruttura condivisa con contratto proprio, non come territorio implicito di uno dei tre.

6. I contratti che sono le vere API

Il parallelismo è protetto da quattro contratti, nessuno dei quali è un'interfaccia Python:

  1. Decision type del ledger (workflow.yaml emits + decisions.py): l'interfaccia a eventi tra i tre moduli e il workflow. Ogni ridefinizione è una modifica di contratto, va versionata e comunicata;
  2. Proiezioni di consumo evidence (search pack / search find --kind evidence|formula): la forma di ciò che F1/F3/F4 vedono. È il cuore del ticket "Consumo per fase", che già porta i requisiti della F1 ridisegnata e non va congelato prima che il disegno F1 abbia almeno uno schizzo;
  3. Modello di contenuto memory (REUSABLE_TYPES + campi del record + proiezione promote --preview --json): dipende semanticamente dal contratto di output della disambiguazione (sez. 4.2);
  4. Id evidence citabile (doc#card + estensione del resolver id→file): il contratto che tiene insieme F3 (citazioni), F4 (candidati), decisions (subject) e report di sessione.

7. Ordine di sviluppo consigliato

Coerente con quanto già deciso in map.md e nell'impact doc:

  1. schizzo del disegno F1 della disambiguazione (anche non finale): target group, retrieval duale, unanswerable — perché il contratto di consumo va progettato contro la F1 ridisegnata;
  2. chiusura del contratto "Consumo per fase" (cerniera tra evidence-canonica e F1): proiezioni, filtri, id citabili doc#card con resolver esteso;
  3. definizione del contratto disambiguazione→memory (cosa è riusabile, con quali campi);
  4. solo allora i tre flussi girano in parallelo, ciascuno nel suo worktree.

Strumento di protezione del parallelismo già disponibile in casa: il pattern dei port-contract test (test_evidence_port_contract.py, test_vector_port_contract.py, test_dwh_port_contract.py). Estenderlo ai nuovi confini (proiezioni evidence, contratto memory, enforcement di disambiguazione) è il modo più economico per rendere l'indipendenza dei moduli verificabile invece che sperata: un flusso rompe l'altro se e solo se rompe un test di contratto, non per effetti nascosti.

8. Rischi e mitigazioni

Rischio Natura Mitigazione
L'LLM orchestratore bypassa il contratto di disambiguazione strutturale (il workflow è LLM-driven) enforcement gate/CLI sul modello dell'anti-bypass esistente; SKILL come contratto completo, non consiglio
Collisione disambiguazione↔memory su concept_clarified semantica contratto esplicito (sez. 6.3) definito prima del parallelismo
tht-gate.js punto caldo di merge organizzativa spaccare il gate lungo gli stessi confini dei moduli (builders per modulo)
Deriva del contratto vector store tra TS e Python infrastrutturale contratto di collection/payload/kind di proprietà unica, test di contratto su entrambi i lati
modifiche contagiose a workflow.yaml/ledger processo change control sul ledger come quarto contratto; emits data-driven già aiuta
contratti congelati troppo presto (Consumo per fase) sequenza ordine sez. 7: prima schizzo F1, poi chiusura contratto

9. Riferimenti

  • docs/wayfinder/evidence-canonica/map.md — mappa del sottoprogetto evidence-canonica (destinazione, decisioni chiuse, nebbie);
  • docs/wayfinder/evidence-canonica/prototypes/verifica-workflow.md — i 12 touchpoint evidence (T1–T12) e i GAP 1–3: l'inventario dell'interfaccia esterna del futuro modulo evidence;
  • docs/wayfinder/evidence-canonica/prototypes/impact-ridisegno-disambiguazione.md — valutazione d'impatto del ridisegno F1 (requisiti: retrieval duale, target group, unanswerable; coordinamento dei due progetti);
  • docs/wayfinder/evidence-canonica/prototypes/PROTOTIPO-formato-unita-canonica.md e tipologia-evidence.md — formato card+involucro chiuso il 2026-08-21 e casistica a 7 famiglie;
  • docs/disambiguazione-iniziale.md — stato attuale della disambiguazione;
  • docs/gestione-memory.md — disegno della gestione memory;
  • harness/workflow.yaml, harness/.pi/skills/tht-sessione/SKILL.md, harness/.pi/extensions/tht-gate.js — le fonti di codice citate (fasi, discipline, gate);
  • docs/prd/2026-08-09-workspace-preprocessing-prd.md — il PRD da cui nasce l'attuale catena preprocessing/registry (contesto P2–P6 citato in sez. 4.1 e 5.2).