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

280 lines
18 KiB
Markdown
Raw 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.
# 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).