diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml new file mode 100644 index 00000000..6f9f49a8 --- /dev/null +++ b/.github/workflows/docs-pages.yml @@ -0,0 +1,58 @@ +name: Publish documentation + +on: + push: + branches: + - gh-pages + paths: + - "docs/**" + - "mkdocs.yml" + - ".github/workflows/docs-pages.yml" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Checkout documentation source + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + cache: pip + cache-dependency-path: docs/requirements.txt + + - name: Install MkDocs dependencies + run: python -m pip install -r docs/requirements.txt + + - name: Build documentation + # The repository intentionally links some docs to source files outside + # docs/. MkDocs reports those as warnings, but they must not block Pages. + run: mkdocs build + + - name: Configure GitHub Pages + uses: actions/configure-pages@v5 + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: site + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/gestione-memory.md b/docs/gestione-memory.md new file mode 100644 index 00000000..b718ce0c --- /dev/null +++ b/docs/gestione-memory.md @@ -0,0 +1,235 @@ +# Gestione delle memory + +Questo documento descrive l'organizzazione attuale delle memory nel workflow ThothII: modello concettuale, ciclo di vita, persistenza, ricerca semantica, gate di revisione, visualizzazione delle sessioni e principali limiti tecnici. + +## Sintesi architetturale + +Una memory è conoscenza di dominio riutilizzabile tra domande. Non è una copia dello schema-linking di una singola domanda. + +```text +F1: chiarimento di un concetto + │ + ▼ +decisione concept_clarified nel ledger della sessione + │ + ▼ +F8: il reviewer decide se promuoverla + │ + ├── registro globale registry.jsonl + └── indice semantico pgvector + │ + ▼ + F2 di una sessione futura + ricerca e proposta al reviewer +``` + +L'invariante principale è `REUSABLE_TYPES = {"concept_clarified"}`: le sole memory generabili, salvabili, ricercabili e proponibili sono i concetti chiariti. Le decisioni `table_promoted`, `table_excluded`, `column_promoted` e analoghe restano decisioni locali alla domanda. + +Implementazione principale: [harness/tht/memory.py](../harness/tht/memory.py:14) e [harness/tht/cli/memory_cmd.py](../harness/tht/cli/memory_cmd.py:368). + +## I tre livelli della gestione + +| Livello | Contenuto | Funzione | +| --- | --- | --- | +| Ledger della sessione | `concept_clarified`, `memory_promoted`, `memory_promotion_declined` | Audit e stato della singola sessione | +| Registro globale | Record `mem-XXXX` in `registry.jsonl` | Archivio canonico attuale delle memory | +| Indice pgvector | Embedding e metadati derivati dal registro | Ricerca semantica | + +Il ledger contiene la provenienza e le decisioni umane. Il record globale contiene il testo riutilizzabile. L'indice vettoriale è una proiezione per la ricerca, non il posto in cui il workflow registra direttamente le decisioni. + +## Che cosa può diventare una memory + +Durante F1 il workflow registra i chiarimenti come decisioni `concept_clarified`. Un chiarimento può esprimere: + +- definizioni di concetti clinici o organizzativi; +- criteri di inclusione ed esclusione di una popolazione; +- formule e metodi di calcolo; +- interpretazioni temporali; +- mapping verso tabelle e colonne specifiche; +- significato di flag, codici o indicatori. + +Il modello `MemoryRecord` contiene: + +- `id`, ad esempio `mem-0001`; +- timestamp, sessione e sequenza della decisione originale; +- `type`; +- `subject`; +- `detail`; +- `rationale`; +- `question_context`; +- `tables` e `concepts`. + +Per le nuove memory il tipo è sempre `concept_clarified` e `tables` viene inizializzato vuoto. Una tabella o una colonna può essere citata dentro la spiegazione come mapping tecnico; non può essere il concetto autonomo della memory. + +Esempio valido: + +> Per ablazione si intende una procedura con `ablazione_transcatetere = TRUE`, conteggiata con `COUNT(DISTINCT cod_paz)` per anno. + +Esempi non validi: + +- `fact_cardioversione` come memory approvata; +- `dim_time` come memory rifiutata; +- una decisione “includi questa tabella” salvata per domande future. + +La regola è documentata anche nella skill del workflow, in [harness/.pi/skills/tht-sessione/SKILL.md](../harness/.pi/skills/tht-sessione/SKILL.md:217). + +## Promozione alla fine della sessione: F8 + +Alla fine del workflow il gate `reviewer_memory_promote` esegue una preview deterministica: + +```text +tht memory promote --session --preview --json +``` + +La preview: + +1. legge le decisioni effettive della sessione; +2. considera solo `concept_clarified`; +3. scarta le decisioni già promosse; +4. scarta le sequenze già rifiutate in F8; +5. deduplica contenuti equivalenti; +6. propone al massimo cinque candidati. + +Il codice applica il filtro e la deduplica in [harness/tht/memory.py](../harness/tht/memory.py:199); il gate applica un ulteriore filtro difensivo in [harness/.pi/extensions/tht-gate.js](../harness/.pi/extensions/tht-gate.js:628). + +Il reviewer vede un'unica checklist, preselezionata. Per ogni candidato: + +- selezionato: viene eseguito `tht memory save-one` e poi viene registrato `memory_promoted`; +- deselezionato: viene registrato `memory_promotion_declined`; +- nessun candidato: F8 si chiude automaticamente. + +Il marker `memory_promoted` usa `detail: seq:N`, cioè un riferimento alla decisione `concept_clarified` originale. Il flusso è in [harness/.pi/extensions/tht-gate.js](../harness/.pi/extensions/tht-gate.js:1691). + +La promozione non viene eseguita in F2 e il modello non può inventare candidati F8. I comandi diretti di promozione sono inoltre protetti dal gate anti-bypass. + +## Persistenza globale + +### Registro JSONL + +Il registro attuale è: + +```text +/memory/registry.jsonl +``` + +La scrittura viene fatta tramite file temporaneo e `os.replace`, quindi la sostituzione del registro è atomica. L'idempotenza della promozione è basata sulla coppia `session_id + decision_seq`: la stessa decisione della stessa sessione non genera due record globali. + +### pgvector + +Dopo la promozione, `save-one` costruisce un solo `VectorRecord` e lo invia all'indice pgvector. Il testo indicizzato include: + +- tipo e soggetto; +- dettaglio; +- motivazione; +- domanda di contesto; +- eventuali concetti e mapping. + +Il record vettoriale usa l'id `memory:mem-XXXX`, mentre i metadati conservano `subject`, `detail`, `rationale`, `tables` e `concepts`. L'hash SHA-256 del contenuto impedisce di ricalcolare embedding e upsert quando il testo non è cambiato. + +Il comportamento è implementato in [harness/tht/memory.py](../harness/tht/memory.py:253) e [harness/tht/memory.py](../harness/tht/memory.py:305). + +### Fonte canonica attuale + +Oggi il registro JSONL è ancora la fonte canonica applicativa e pgvector è l'indice derivato. Il commento iniziale di [memory_cmd.py](../harness/tht/cli/memory_cmd.py:1) segnala un debito tecnico: l'architettura futura prevista sarebbe usare direttamente il vector DB come archivio unico, ma questa migrazione non è ancora completata. + +## Riutilizzo in F2 + +In una sessione futura F2 esegue: + +```text +tht memory search "" --session --json +``` + +Il comando: + +1. crea l'embedding della domanda; +2. cerca nel vector store solo record `kind=memory`; +3. risolve ogni hit nel registro JSONL tramite il suo `ref`; +4. scarta record assenti dal registro; +5. scarta qualsiasi tipo diverso da `concept_clarified`; +6. esclude le memory già decise nella sessione corrente; +7. restituisce i risultati ordinati per similarità. + +L'implementazione è in [harness/tht/cli/memory_cmd.py](../harness/tht/cli/memory_cmd.py:368). + +Le memory non vengono mai applicate automaticamente. Il modello deve presentarle in un'unica scelta `reviewer_decide`: + +- una memory selezionata viene registrata come nuovo `concept_clarified` nella sessione corrente; +- il rationale deve citare l'id originale `mem-XXXX`; +- una memory deselezionata significa “non applicarla ora”, non “cancellarla globalmente”. + +Se F2 viene riaperta, una memory deselezionata può quindi essere proposta di nuovo. `memory_rejected` resta supportato per decisioni e sessioni legacy, ma non rappresenta il normale comportamento della deselezione F2 attuale. + +## Ledger effettivo, rollback e riaperture + +Il ledger è append-only. Riaperture e ritrattazioni non cancellano le righe precedenti; cambiano però quali decisioni sono effettive. + +Gli helper memory usano `effective_decisions()` per: + +- escludere decisioni ritirate; +- ignorare decisioni appartenenti a fasi diventate stale dopo un rollback; +- impedire la promozione di chiarimenti non più validi. + +La vista effettiva è definita in [harness/tht/phase.py](../harness/tht/phase.py:82). + +## Visualizzazione nel riepilogo della sessione + +La sezione “Memories” del riepilogo viene proiettata a runtime dal ledger della sessione; non è una copia diretta del registro globale. + +La proiezione: + +- mostra prima le memory approved; +- mostra poi le memory declined; +- risolve `seq:N` verso il `concept_clarified` originale; +- nasconde marker il cui record originale non è `concept_clarified`; +- nasconde soggetti che corrispondono a tabelle dello schema-linking; +- nasconde soggetti autonomi con forma `fact_*` o `dim_*`; +- mantiene invece i riferimenti a tabelle e campi quando fanno parte della spiegazione concettuale. + +La logica è in [harness/tht/session/store.py](../harness/tht/session/store.py:237). Il rendering frontend usa una lista strutturata e tratta `subject`, `detail` e `rationale` come Markdown, evitando di mostrare il markdown grezzo. + +La trasformazione avviene in lettura: anche le sessioni storiche vengono organizzate con il layout e i filtri correnti senza riscrivere gli artefatti originali. + +## Invarianti applicate + +Le protezioni sono distribuite su più confini: + +1. `REUSABLE_TYPES` nel core Python; +2. filtro del comando `memory search`; +3. filtro della preview F8; +4. filtro e deduplica nel gate Pi; +5. esclusione delle table-memory nella proiezione UI. + +Questo evita che una singola modifica al prompt o a un solo componente reintroduca le tabelle come memory. + +## Limiti e rischi residui + +### Registro e indice non sono una singola transazione + +Il salvataggio segue sostanzialmente questa sequenza: + +```text +registro JSONL → pgvector → marker memory_promoted nel ledger +``` + +Se pgvector non è disponibile, il registro può contenere una memory non ancora ricercabile; il comando segnala che sarà necessario reindicizzare. + +Se il marker del ledger fallisce dopo il salvataggio nel vector DB, la memory può risultare globalmente presente ma senza audit completo nella sessione. Il gate restituisce un comando di recupero manuale. + +### Limite di cinque candidati + +F8 propone al massimo cinque memory. Se una sessione produce più di cinque concetti validi, gli elementi eccedenti non vengono mostrati e la sessione può essere finalizzata senza promuoverli. + +### Deduplica non globale + +La deduplica impedisce duplicati nella stessa proposta e l'idempotenza impedisce di ripromuovere la stessa decisione. Non esiste però una fusione globale di due memory semanticamente simili provenienti da sessioni diverse. + +### Vecchi record fisici + +Vecchi record `table_promoted` o `table_excluded` possono ancora esistere in artefatti o indici storici. Il codice attuale li rende non riutilizzabili filtrandoli per tipo e non li mostra nella proiezione delle sessioni. La loro eventuale rimozione fisica dal vector DB resta un'attività di bonifica separata. + +## Valutazione finale + +La gestione attuale è coerente con il requisito funzionale: una memory è una conoscenza concettuale riutilizzabile, non una scelta di schema-linking. + +La parte più solida è la difesa multilivello del tipo `concept_clarified`. Il principale debito tecnico riguarda invece la convivenza del registro JSONL con pgvector e l'assenza di una transazione unica tra archivio globale, indice semantico e ledger della sessione. diff --git a/mkdocs.yml b/mkdocs.yml index 42bd396a..307e31fb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,6 +48,7 @@ nav: - ThothII (Documentazione Tecnica): - Panoramica Architettura: architecture/overview.md - Installazione Docker (4 contesti): installazione-docker-4-contesti.md + - Gestione delle memory: gestione-memory.md - Specifiche di Design: - Architettura ThothII: superpowers/specs/2026-06-25-thothii-architecture-design.md - Backend: superpowers/specs/2026-06-27-backend-design.md