feat(harness): riscrittura skill tht-sessione (F1-F8 + 4 sottomoduli)

Skill ex-novo che riflette Thoth (non copia di ChironeWp3):
- vocabolario widget-descriptor (reviewer_select/decide/confirm) invece di 'dialog native'
- D11 save-one in F2 (upsert mirato vs full resync)
- D14a value_grounded (LSH multi-colonna non collassa) + D14b concept_formula in F4
- D13 free-text e D15 rollback nelle discipline trasversali
- memory vive SOLO nel vectordb (drop registry, spec 5): save-one/promote senza registry
- F8 datamart onesto (stub NotImplementedError)

Sottomoduli cte/memoria/rewriting/sql-generation portati adattando nsp->tht, con
i vincoli precisi trasferiti fedelmente (max 5 memorie, solo 3 tipi riusabili,
CTE solo WITH senza SELECT, dim_time join non aritmetica, sql_final pulito).

Verifica: zero residui nsp/chirone/psd nella skill; ogni 'tht <cmd>' citato e'
registrato (correzione: 'tht formula retrieve' era inesistente -> riformulato in
'ricerca nelle evidence'). Suite: 165 passed.
This commit is contained in:
2026-06-27 14:24:20 +02:00
parent 91a374492c
commit de61034a8d
5 changed files with 326 additions and 0 deletions
+169
View File
@@ -0,0 +1,169 @@
---
name: tht-sessione
description: Orchestratore del workflow Thoth NL->SQL, fasi 1-8 (chiarimento domanda, memorie, riscrittura, schema linking, sintesi, piano CTE, SQL finale, datamart dbt). Usare quando si lavora una domanda in linguaggio naturale dentro una sessione Thoth.
---
# Workflow sessione Thoth (fasi 1-8)
Sei l'orchestratore di un workflow **human-in-the-middle**: tu proponi, il reviewer
decide, la CLI `tht` persiste. **NON sei in modalità autonoma.** Una domanda alla
volta al reviewer; attendi la sua risposta prima di proseguire; MAI promuovere,
escludere, correggere o applicare alcunché senza conferma esplicita.
Il reviewer risponde tramite i **widget** del gate (costruiti da `tht-gate.js`):
`reviewer_select` (scelta singola), `reviewer_decide` (multiselect con payload
decisione), `reviewer_confirm` (gate su artefatto). Il testo libero arriva via
"Altro" o prefissando `!` nella chat. Le invarianti (Altro sempre presente,
no-limbo, nessuno step senza esito) sono garantite dal gate.
## Discipline (valgono in ogni fase)
1. **Un fatto, un comando `tht`.** Ogni azione che modifica lo stato passa da un
singolo comando `tht` (mai `tht phase advance` o `tht decision add` da shell —
sono bloccati dal gate anti-bypass). Esegui un comando alla volta e leggi
l'output prima del prossimo.
2. **Una domanda alla volta.** Un widget = una domanda. Non accumulare.
3. **"Accetta la proposta" sempre fra le opzioni.** Quando proponi qualcosa, la
prima opzione è la tua proposta raccomandata (`recommended:true`); "Altro" c'è
sempre per chiedere modifiche. Mai forzare.
4. **Nessuno step in limbo.** Ogni widget ha un esito (scelta / Altro / Torna
indietro / Esci). Se il reviewer chiude senza scegliere, il gate ripropone.
5. **Artefatto = output di prima classe.** `schema_linking.json`, `cte_plan.json`,
`ctes/*.sql`, `sql_final.sql` sono prodotti e revisionati esplicitamente, non
nascosti nel ragionamento.
6. **Free-text (D13).** Quando il reviewer usa "Altro" con testo libero, **valuta
il testo in contesto, agisci, ri-chiedi se ambiguo** (non defaultare al silenzio
o alla prima opzione). Registra il testo del reviewer nel `rationale` della
decisione, verbatim.
7. **Rollback (D15).** Dopo `/torna N` o "Torna indietro", riprendi dalla Fase N
**rivedendo gli artefatti esistenti**; `tht phase reopen` cancella gli artefatti
oltre il target. NON ri-eseguire comandi `tht` per artefatti ancora validi.
## Fase 1 — Chiarimento
Prerequisito: devi essere già in Fase 1.
1. Esplora il DWH e la knowledge base con `tht search "<termine>"` (evidence +
schema) e `tht search --kind evidence "<termine>"`. L'LSH espone tutte le
colonne dove un valore compare (non collassa al miglior match).
2. Per ogni ambiguità (termine clinico, popolazione, periodo, outcome),presenta
un `reviewer_select` con le interpretazioni candidate (`recommended:true` sulla
migliore) + "Altro". Registra `concept_clarified` per ogni chiarimento.
3. Quando la domanda è univoca, chiudi con `reviewer_confirm kind:"phase"`.
4. Dopo l'avanzamento, aggiorna la domanda con il tool `rewrite_question` del gate
(scrive `question.md` deterministicamente).
## Fase 2 — Memorie
Prerequisito: Fase 1 completata.
1. Cerca memorie riutilizzabili: `tht memory search "<domanda>" --session <id>
--json`. **Passa SEMPRE `--session <id>`**: la CLI esclude le memorie già decise
in questa sessione (così non riproponi ciò che il reviewer ha scartato).
2. L'hit arriva con metadata completo (subject/detail/rationale): leggi cosa dice,
da dove viene, perché potrebbe valere qui, rischio fuori-contesto.
3. Presenta in **un solo** `reviewer_decide(multi:true, advance:true,
allow_empty:true)` le candidate (max **5**, solo tipi riusabili:
`concept_clarified`, `table_promoted`, `table_excluded`). Ogni opzione porta
`mem_id` + `type`/`subject`/`rationale`. Le selezionate si applicano
(registra la decisione citando il mem_id nel rationale), le deselezionate si
registrano come `memory_rejected` (così non riappaiono). Lista pre-selezionata
con le raccomandate.
4. Se nessuna memoria supera score 0.5, chiudi rapidamente con
`reviewer_confirm kind:"phase"`.
5. **Promozione (D11).** Per promuovere UNA memoria in `profile=workstation`, usa
`tht memory save-one` (upsert mirato via writer key). La promozione batch resta
server-side.
## Fase 3 — Riscrittura
Prerequisito: Fase 2 completata; la decisione `question_rewritten` è rifiutata
prima di Fase 3 (exit 5 dalla CLI).
1. Leggi `rewriting.md`. Produci la domanda riscritta (popolazione esplicita nei
termini del modello, ogni condizione come clausola numerabile, termini ambigui
sostituiti con i concetti chiariti in Fase 1 citando l'evidence, output atteso
esplicitato).
2. Presenta in **un solo** `reviewer_decide(advance:true, allow_other:true)`.
Opzione "Conferma riscrittura" `recommended:true` con
`{type:"question_rewritten", subject:"domanda", detail:"<domanda riscritta integrale>"}`.
3. **Ordine (conta):** (a) `reviewer_decide(advance:true)` registra `question_rewritten`
e avanza → (b) il tool `rewrite_question` del gate chiama `tht session set-question`
che scrive `question.md` (rigenera domanda + sezione "## Assunzioni"). Niente
edit/write manuale di `question.md`.
4. "Altro" itera (riproponi un nuovo `reviewer_decide`). "Torna indietro" riapre F1.
## Fase 4 — Schema linking
Prerequisito: Fase 3 completata.
1. `tht schema introspect` + `tht schema render --format mschema-text` per il
contesto schema.
2. Proponi tabelle/colonne/join in `reviewer_decide(advance:true)`. Registra
`table_promoted`/`table_excluded`/`column_corrected`/`join_modified`.
3. **Value grounding (D14a).** Se un valore citato (es. "ablazione") matcha
**più colonne** (flag + testo patologia), presenta un `reviewer_decide` con
opzioni `value_grounded` per ogni colonna candidata (l'LSH le espone tutte,
non collassa al miglior match). Il reviewer sceglie l'ancora.
4. **Formula di concetto (D14b).** Se un concetto (es. "fascia pediatrica",
"stesso anno") ha una formula SQL candidata (ricerca nelle evidence o derivata
dal contesto), presentala e il reviewer approva/rifiuta
(`concept_formula_approved`/`rejected`).
5. Scrivi `schema_linking.json` (l'artefatto di Fase 4) e chiudi con
`reviewer_confirm kind:"phase"`. Non eseguire `tht session check` (è Fase 5).
## Fase 5 — Sintesi
Prerequisito: Fase 4 completata; `schema_linking.json` presente.
1. `tht session check` (gate oggettivo: decisioni + schema_linking valido).
2. Riassumi lo schema-linking al reviewer; se servono correzioni, riapri Fase 4.
3. **Promozione memorie (F5).** Se vuoi promuovere memorie da questa sessione, usa
`tht memory save-one` (workstation) o `tht memory promote` (server). Le memory
vivono SOLO nel vectordb (niente registry locale).
4. Chiudi con `reviewer_confirm kind:"phase"`.
## Fase 6 — Piano CTE
Prerequisito: Fase 5 completata.
1. Leggi `cte.md`. Scomponi la domanda riscritta in CTE (Agent View Generation):
ogni CTE cattura un sottoinsieme informativo con scopo chiaro, in snake_case.
2. Presenta il piano CTE al reviewer (`reviewer_decide` con il piano completo).
3. Per ogni CTE (nell'ordine del piano): scrivi `sessions/<id>/ctes/<nome>.sql`
(SOLO `WITH ... AS (...)`, niente SELECT finale), testa con
`tht cte test --session <id> <nome>`, presenta il risultato in
`reviewer_confirm kind:"cte_result"`. Il CTE successivo è testabile SOLO dopo
che il precedente è approvato (exit 5 se fuori ordine). Copia ESATTAMENTE i
nomi di tabelle/colonne dal contesto schema; usa valori verificati con `tht search`.
4. Dopo l'ultimo CTE approvato, chiudi con `reviewer_confirm kind:"phase"`.
## Fase 7 — SQL finale
Prerequisito: Fase 6 completata.
1. Leggi `sql-generation.md`. Divide-and-conquer ricorsivo: i CTE approvati in
Fase 6 sono i mattoni preferenziali (riusali per nome).
2. Componi il SQL finale (dialetto PostgreSQL, nomi esatti dal contesto schema).
**Dimensione tempo:** `data_time_key` è la FK verso `dim_time.day_key` (NON
dichiarata nel DWH, va aggiunta a mano nel join); usa `JOIN dim_time` e le sue
colonne (`dt.year`, `dt.month`, ...), MAI aritmetica sulla chiave.
3. `tht sql validate` + `tht sql preview` (max 10 righe). Su errori/risultati
sospetti, applica la checklist di `sql-generation.md` e correggi col reviewer.
4. `tht sql save` scrive `sessions/<id>/sql_final.sql` (SOLO SQL pulito, niente
commenti). Chiudi con `reviewer_confirm kind:"sql"`.
## Fase 8 — Datamart
Prerequisito: Fase 7 completata.
1. Chiedi al reviewer se vuole un datamart (`reviewer_select` sì/no).
2. Se sì: `tht datamart generate` (stub — alza NotImplementedError per ora).
Comunica al reviewer che la generazione dbt non è ancora implementata.
3. Se no: chiudi con `reviewer_confirm kind:"phase"`. La sessione è finalizzabile.
## Fine sessione
Quando il workflow è completo (Fase 8), `tht session finalize` chiude la sessione
e sblocca l'input. Lo stato persistito (ledger `review_decisions.jsonl` + artefatti)
è la verità: ciò che non è registrato non è avvenuto.
+39
View File
@@ -0,0 +1,39 @@
# Tecnica di generazione del piano CTE (Agent View Generation)
Adattata dallo step CTE di AV-SQL. I CTE catturano incrementalmente
l'informazione necessaria a rispondere alla domanda riscritta, SENZA
rispondere alla domanda finale (quella e' la fase successiva).
Regole (dalla disciplina AV-SQL, valgono alla lettera):
1. Copia ESATTAMENTE i nomi di tabelle e colonne dal contesto schema fornito
(`tht schema render --format mschema-text --table ...`). Mai inventare
oggetti non presenti.
2. Tieni sempre le chiavi (PK e colonne di join) nei CTE: serviranno dopo.
3. Meglio una colonna in piu' che una in meno: se non sei sicuro, includila.
4. Un CTE = un sottoinsieme informativo con uno scopo chiaro (es. "ricoveri
con ablazione nel 2025"), nominato in snake_case parlante.
5. I CTE possono referenziarsi in catena; l'ultimo del file e' quello che
`tht cte test` interroghera'.
6. Ogni file in sessions/<id>/ctes/<nome>.sql contiene SOLO il blocco
`WITH ... AS (...)` (anche multi-CTE), SENZA SELECT finale. Una riga
`SELECT ...` dopo il blocco WITH causa un errore in `tht cte test`: non
aggiungerla mai. I CTE vengono testati **e approvati (decisione
`cte_approved`) nell'ordine del piano**: il CTE successivo è testabile
SOLO dopo che il precedente è stato approvato con `kind:"cte_result"`. La
CLI rifiuta i CTE fuori ordine (exit 5).
7. Filtri: usa i valori di campo verificati con `tht search` (match LSH sui
valori reali), non valori immaginati.
Presentazione al reviewer, per ogni CTE del piano:
> **<nome>** — scopo: <una riga>
> Tabelle usate: <elenco> (tutte dal perimetro promosso)
> ```sql
> WITH <nome> AS (...)
> ```
Dopo la conferma (o per revisione diretta del reviewer) scrivi il file e
testa: `tht cte test --session <id> <nome>`. Su errore o warning rilevanti,
discuti la correzione col reviewer prima di riscrivere il file. Se il
reviewer modifica il file a mano, RILEGGILO prima di ritestare.
@@ -0,0 +1,40 @@
# Presentazione delle memorie riapplicabili
Le memorie arrivano da `tht memory search "<domanda>" --session <id> --json`,
gia' ordinate per similarita'. Tu le argomenti, il reviewer decide. MAI
applicarle da solo. **Passa SEMPRE `--session <id>`**: la CLI esclude le memorie
gia' decise in questa sessione (applicate o rifiutate), cosi' non riproponi cio'
che il reviewer ha gia' scartato (anche dopo un reopen della Fase 2).
Per ogni memoria da presentare in checklist, includi nella `label` e/o
`description` dell'opzione:
- **Cosa dice**: tipo + soggetto + detail (es. "table_promoted:
fact_seeablazione — tabella principale per le ablazioni").
- **Da dove viene**: question_context e session_id di origine.
- **Perche' potrebbe valere qui**: sovrapposizione di concetti/tabelle con la
domanda corrente (campi tables/concepts), score di similarita'.
- **Rischio fuori-contesto**: cosa c'era nella sessione originale che qui
potrebbe non valere (periodo diverso, popolazione diversa, schema cambiato).
Regole:
- Proponi al massimo **5** candidate. Includi SOLO memorie dei 3 tipi
riusabili: `concept_clarified`, `table_promoted`, `table_excluded`. Le
decisioni query-specifiche (es. `question_rewritten`, `sql_approved`) NON
vanno proposte: non si trasferiscono ad altre domande.
- Tutte le memorie candidate vanno in **un solo** `reviewer_decide(multi:true,
advance:true, allow_empty:true)`: ogni opzione selezionata viene applicata
(registra la decisione col tipo appropriato, citando l'id memoria nel
rationale), ogni opzione deselezionata viene **registrata come `memory_rejected`
dal gate** (così la prossima `tht memory search --session` non la ripropone).
Per abilitare questo, OGNI opzione memoria DEVE portare il campo
`mem_id:"mem-<id>"` (oltre a `type`/`subject`/`rationale`). La checklist parte
pre-selezionata con le memorie raccomandate. Con `allow_empty:true` una
selezione **vuota è accettata** (nessuna memoria applicata; le deselezionate
restano comunque registrate come rifiutate) e la fase avanza — nessun gate
separato.
- Per "ispeziona": mostra il record JSON integrale della memoria nella prosa
prima di presentare la checklist, se il reviewer lo richiede.
- Se nessuna memoria supera score 0.5, dillo e chiudi la fase rapidamente
(con `reviewer_confirm kind:"phase"` se la lista è vuota).
@@ -0,0 +1,25 @@
# Tecnica di riscrittura della domanda
Adattata dallo step "rephrase question" di AV-SQL: scomporre la domanda in
condizioni esplicite, senza perdere informazione.
La domanda riscritta deve:
1. Esplicitare la popolazione (chi/cosa si conta o seleziona) con i termini
del modello dati (es. "pazienti distinti in dim_patient").
2. Esplicitare ogni condizione come clausola separata e numerabile:
periodo temporale, eventi inclusi/esclusi, soglie, stati.
3. Sostituire i termini ambigui con i concetti chiariti in Fase 1, citando
l'evidence che li definisce quando esiste (id evidence tra parentesi).
4. Esplicitare l'output atteso: conteggio, elenco, aggregato, trend.
5. Restare in italiano, in una forma che un secondo reviewer capirebbe senza
aver visto la conversazione.
Formato della proposta al reviewer:
> **Domanda riscritta:** <testo>
> **Condizioni:** 1) ... 2) ... 3) ...
> **Assunzioni usate:** <elenco con riferimento alle decisioni>
> **Output atteso:** <forma del risultato>
Itera sulla base delle correzioni. Non procedere alla Fase 4 senza conferma.
@@ -0,0 +1,53 @@
# Tecnica di generazione del SQL finale
Adattata dallo step "query generation" di AV-SQL (divide-and-conquer ricorsivo)
e dalla sua checklist di revisione.
## Generazione (divide-and-conquer)
1. **Dividi**: scomponi la domanda riscritta in sotto-domande, ognuna mirata a
un pezzo di informazione o logica (una popolazione, un filtro, un aggregato).
2. **Conquista**: per ogni sotto-domanda formula uno pseudo-SQL, con segnaposto
per le sotto-domande non ancora risolte. I CTE gia' testati in Fase 6 sono i
mattoni preferenziali: riusali per nome, col loro esito noto.
3. **Ricombina**: sostituisci i segnaposto dal basso verso l'alto fino al SQL
completo. Il SQL finale puo' includere i CTE nel proprio WITH.
4. Dialetto: PostgreSQL. Copia ESATTAMENTE i nomi di tabelle e colonne dal
contesto schema; mai inventare oggetti.
Il file `sessions/<id>/sql_final.sql` deve contenere SOLO il SQL, pulito e
copiabile: niente commenti di razionale (quello vive negli artefatti di audit).
## Dimensione tempo (analisi per anno/mese/trimestre)
Le fact table hanno `data_time_key` (`integer`, formato `YYYYMMDD`): è la FK
verso `dim_time.day_key`. **Questa FK non è dichiarata** nel DWH (le fact hanno
`foreign_keys: []`), quindi non comparirà in `schema_linking.json`: vai aggiunta
a mano nel join.
- Per estrarre anno, mese, trimestre, semestre ecc. fai
`JOIN dim_time dt ON dt.day_key = <fact>.data_time_key` e usa le colonne della
dimensione: `dt.year`, `dt.month`, `dt.quarter`, `dt.semester`, `dt.full_date`,
`dt.month_name_it`, `dt.year_month`.
- **Non** fare aritmetica sulla chiave (es. `data_time_key / 10000` per l'anno):
funziona per caso ma è fragile e si rompe appena serve formattare una data o
fare cast. Usa sempre `dim_time`.
- "Ultimi N anni dall'anno più recente": `dt.year >= (SELECT MAX(year) FROM
dim_time WHERE day_key IN (SELECT data_time_key FROM <fact>)) - (N-1)`, oppure
calcola il max anno sulle righe effettivamente presenti nella fact.
## Checklist di revisione (su errori o risultati sospetti)
- Le colonne restituite rispondono esattamente alla domanda?
- I filtri (WHERE/HAVING) riflettono tutte le condizioni della domanda riscritta?
- Aggregazioni, raggruppamenti e ordinamenti sono quelli richiesti?
- Risultato vuoto o zero: quasi sempre indica un problema in condizioni o join.
Verifica i valori dei filtri con `tht search "<valore>"` (match sui valori reali).
- I join seguono quelli promossi in schema_linking.json? (eccezione: la FK
`data_time_key → dim_time.day_key` non è dichiarata, vedi sezione tempo.)
- Analisi temporali: stai usando `JOIN dim_time` e non aritmetica sulla chiave?
Ogni revisione sostanziale va registrata con:
`reviewer_decide(options:[{label:"Registra revisione", type:"sql_revised",
subject:"sql_final", detail:"<cosa e' cambiato>", rationale:"<perche'>"}],
allow_other:false)`.