This commit is contained in:
+145
-136
@@ -1,57 +1,65 @@
|
||||
# Gestione delle memory
|
||||
# Memory management
|
||||
|
||||
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.
|
||||
This document describes how Memory currently works in ThothII: its conceptual model, lifecycle, persistence, semantic search, review gates, session display, and main technical limits.
|
||||
|
||||
## Sintesi architetturale
|
||||
## Architectural summary
|
||||
|
||||
Una memory è conoscenza di dominio riutilizzabile tra domande. Non è una copia dello schema-linking di una singola domanda.
|
||||
A Memory item is domain knowledge that can be reused across questions. It is not a copy of one question's schema linking.
|
||||
|
||||
```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 Qdrant
|
||||
│
|
||||
▼
|
||||
F2 di una sessione futura
|
||||
ricerca e proposta al reviewer
|
||||
```mermaid
|
||||
flowchart TB
|
||||
CLARIFY["F1 concept clarified"] --> REVIEW["F8 reviewer review"]
|
||||
REVIEW -->|"accepted"| REGISTRY["registry.jsonl"]
|
||||
REVIEW -->|"declined"| LOCAL["Session decision only"]
|
||||
REGISTRY --> VECTOR["Qdrant semantic index"]
|
||||
VECTOR --> FUTURE["Future F2 retrieval"]
|
||||
FUTURE --> PROPOSAL["Reviewer proposal"]
|
||||
```
|
||||
|
||||
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.
|
||||
```text
|
||||
F1: clarify a concept
|
||||
│
|
||||
▼
|
||||
concept_clarified decision in the session ledger
|
||||
│
|
||||
▼
|
||||
F8: reviewer decides whether to promote it
|
||||
│
|
||||
├── global registry registry.jsonl
|
||||
└── Qdrant semantic index
|
||||
│
|
||||
▼
|
||||
F2 in a future session
|
||||
search and proposal to the reviewer
|
||||
```
|
||||
|
||||
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).
|
||||
The main invariant is `REUSABLE_TYPES = {"concept_clarified"}`: only clarified concepts can be generated, saved, searched, or proposed as Memory. Decisions such as `table_promoted`, `table_excluded`, and `column_promoted` remain local to the question.
|
||||
|
||||
## I tre livelli della gestione
|
||||
## The three management levels
|
||||
|
||||
| Livello | Contenuto | Funzione |
|
||||
| Level | Content | Function |
|
||||
| --- | --- | --- |
|
||||
| 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 Qdrant | Embedding e metadati derivati dal registro | Ricerca semantica |
|
||||
| Session ledger | `concept_clarified`, `memory_promoted`, `memory_promotion_declined` | Audit and state for one session |
|
||||
| Global registry | `mem-XXXX` records in `registry.jsonl` | Current canonical Memory archive |
|
||||
| Qdrant index | Embeddings and metadata derived from the registry | Semantic search |
|
||||
|
||||
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.
|
||||
The ledger contains provenance and human decisions. The global record contains reusable text. The vector index is a search projection, not the place where the workflow records decisions directly.
|
||||
|
||||
## Che cosa può diventare una memory
|
||||
## What can become Memory
|
||||
|
||||
Durante F1 il workflow registra i chiarimenti come decisioni `concept_clarified`. Un chiarimento può esprimere:
|
||||
During F1, the workflow records clarifications as `concept_clarified` decisions. A clarification can express:
|
||||
|
||||
- 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.
|
||||
- definitions of production or organizational concepts;
|
||||
- inclusion and exclusion criteria for a product line;
|
||||
- formulas and calculation methods;
|
||||
- interpretations of time periods;
|
||||
- mappings to specific tables and columns;
|
||||
- the meaning of flags, codes, or indicators.
|
||||
|
||||
Il modello `MemoryRecord` contiene:
|
||||
The `MemoryRecord` model contains:
|
||||
|
||||
- `id`, ad esempio `mem-0001`;
|
||||
- timestamp, sessione e sequenza della decisione originale;
|
||||
- `id`, such as `mem-0001`;
|
||||
- the timestamp, session, and sequence of the original decision;
|
||||
- `type`;
|
||||
- `subject`;
|
||||
- `detail`;
|
||||
@@ -59,177 +67,178 @@ Il modello `MemoryRecord` contiene:
|
||||
- `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.
|
||||
For new Memory items, the type is always `concept_clarified` and `tables` starts empty. A table or column may appear in the explanation as a technical mapping, but it cannot be the Memory item's standalone concept.
|
||||
|
||||
Esempio valido:
|
||||
Valid example:
|
||||
|
||||
> Per ablazione si intende una procedura con `ablazione_transcatetere = TRUE`, conteggiata con `COUNT(DISTINCT cod_paz)` per anno.
|
||||
> Ablation means a procedure with `ablazione_transcatetere = TRUE`, counted with `COUNT(DISTINCT cod_paz)` by year.
|
||||
|
||||
Esempi non validi:
|
||||
Invalid examples:
|
||||
|
||||
- `fact_cardioversione` come memory approvata;
|
||||
- `dim_time` come memory rifiutata;
|
||||
- una decisione “includi questa tabella” salvata per domande future.
|
||||
- `fact_cardioversione` as approved Memory;
|
||||
- `dim_time` as rejected Memory;
|
||||
- an "include this table" decision saved for future questions.
|
||||
|
||||
La regola è documentata anche nella skill del workflow, in [harness/.pi/skills/tht-sessione/SKILL.md](../harness/.pi/skills/tht-sessione/SKILL.md:217).
|
||||
The workflow skill also documents this rule in [harness/.pi/skills/tht-sessione/SKILL.md](../harness/.pi/skills/tht-sessione/SKILL.md:217).
|
||||
|
||||
## Promozione alla fine della sessione: F8
|
||||
## Promotion at the end of the session: F8
|
||||
|
||||
Alla fine del workflow il gate `reviewer_memory_promote` esegue una preview deterministica:
|
||||
At the end of the workflow, the `reviewer_memory_promote` gate runs a deterministic preview:
|
||||
|
||||
```text
|
||||
tht memory promote --session <id> --preview --json
|
||||
```
|
||||
|
||||
La preview:
|
||||
The 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.
|
||||
1. reads the session's effective decisions;
|
||||
2. considers only `concept_clarified`;
|
||||
3. discards decisions already promoted;
|
||||
4. discards sequences already declined in F8;
|
||||
5. deduplicates equivalent content;
|
||||
6. proposes at most five candidates.
|
||||
|
||||
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).
|
||||
The code applies filtering and deduplication in
|
||||
[harness/tht/memory/core.py](../harness/tht/memory/core.py); the gate applies an additional defensive filter in
|
||||
[harness/.pi/extensions/gate/memory/index.js](../harness/.pi/extensions/gate/memory/index.js).
|
||||
|
||||
Il reviewer vede un'unica checklist, preselezionata. Per ogni candidato:
|
||||
The reviewer sees one preselected checklist. For each candidate:
|
||||
|
||||
- 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.
|
||||
- selected: `tht memory save-one` runs, followed by a `memory_promoted` record;
|
||||
- deselected: records `memory_promotion_declined`;
|
||||
- no candidates: F8 closes automatically.
|
||||
|
||||
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).
|
||||
The `memory_promoted` marker uses `detail: seq:N`, a reference to the original `concept_clarified` decision. The flow is 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.
|
||||
Promotion does not run in F2, and the model cannot invent F8 candidates. Direct promotion commands are also protected by the anti-bypass gate.
|
||||
|
||||
## Persistenza globale
|
||||
## Global persistence
|
||||
|
||||
### Registro JSONL
|
||||
### JSONL registry
|
||||
|
||||
Il registro attuale è:
|
||||
The current registry is:
|
||||
|
||||
```text
|
||||
<artifacts>/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.
|
||||
The registry is written through a temporary file and `os.replace`, so replacement is atomic. Promotion is idempotent on the `session_id + decision_seq` pair: the same decision from the same session cannot create two global records.
|
||||
|
||||
### Qdrant
|
||||
|
||||
Dopo la promozione, `save-one` costruisce un solo `VectorRecord` e lo invia all'indice Qdrant. Il testo indicizzato include:
|
||||
After promotion, `save-one` builds one `VectorRecord` and sends it to the Qdrant index. The indexed text includes:
|
||||
|
||||
- tipo e soggetto;
|
||||
- dettaglio;
|
||||
- motivazione;
|
||||
- domanda di contesto;
|
||||
- eventuali concetti e mapping.
|
||||
- type and subject;
|
||||
- detail;
|
||||
- rationale;
|
||||
- question context;
|
||||
- any concepts and mappings.
|
||||
|
||||
Il record vettoriale usa l'id `memory:mem-XXXX`, mentre i metadati conservano `subject`, `detail`, `rationale`, `tables`, `concepts` e il discriminante `kind`. L'hash SHA-256 del contenuto impedisce di ricalcolare embedding e upsert quando il testo non è cambiato.
|
||||
The vector record uses the ID `memory:mem-XXXX`. Its metadata stores `subject`, `detail`, `rationale`, `tables`, `concepts`, and the `kind` discriminator. The content's SHA-256 hash prevents embedding and upsert work when the text has not changed.
|
||||
|
||||
Il comportamento è implementato in [harness/tht/memory.py](../harness/tht/memory.py:253) e [harness/tht/memory.py](../harness/tht/memory.py:305).
|
||||
This behavior is implemented in
|
||||
[harness/tht/memory/core.py](../harness/tht/memory/core.py).
|
||||
|
||||
### Fonte canonica attuale
|
||||
### Current canonical source
|
||||
|
||||
Oggi il registro JSONL è ancora la fonte canonica applicativa e Qdrant resta un indice derivato ma persistente. Il workflow non registra direttamente le decisioni nel vector DB: usa Qdrant come proiezione interrogabile del registro e del ledger effettivo.
|
||||
The JSONL registry remains the application's canonical source, while Qdrant is a derived but persistent index. The workflow does not record decisions directly in the vector database. It uses Qdrant as a searchable projection of the registry and effective ledger.
|
||||
|
||||
## Riutilizzo in F2
|
||||
## Reuse in F2
|
||||
|
||||
In una sessione futura F2 esegue:
|
||||
In a future session, F2 runs:
|
||||
|
||||
```text
|
||||
tht memory search "<domanda>" --session <id> --json
|
||||
tht memory search "<question>" --session <id> --json
|
||||
```
|
||||
|
||||
Il comando:
|
||||
The command:
|
||||
|
||||
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à.
|
||||
1. creates an embedding for the question;
|
||||
2. searches the vector store for records with `kind=memory` only;
|
||||
3. resolves each hit in the JSONL registry through its `ref`;
|
||||
4. discards records missing from the registry;
|
||||
5. discards every type other than `concept_clarified`;
|
||||
6. excludes Memory already decided in the current session;
|
||||
7. returns results ordered by similarity.
|
||||
|
||||
L'implementazione è in [harness/tht/cli/memory_cmd.py](../harness/tht/cli/memory_cmd.py:368).
|
||||
Memory is never applied automatically. The model must present it in one `reviewer_decide` choice:
|
||||
|
||||
Le memory non vengono mai applicate automaticamente. Il modello deve presentarle in un'unica scelta `reviewer_decide`:
|
||||
- a selected Memory item is recorded as a new `concept_clarified` in the current session;
|
||||
- the rationale must cite the original `mem-XXXX` ID;
|
||||
- a deselected Memory item means "do not apply it now", not "delete it globally".
|
||||
|
||||
- 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”.
|
||||
If F2 is reopened, a deselected Memory item can be proposed again. `memory_rejected` remains supported for legacy decisions and sessions, but it is not the normal behavior for current F2 deselection.
|
||||
|
||||
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.
|
||||
## Effective ledger, rollback, and reopening
|
||||
|
||||
## Ledger effettivo, rollback e riaperture
|
||||
The ledger is append-only. Reopening and withdrawing decisions do not delete earlier rows, but they change which decisions are effective.
|
||||
|
||||
Il ledger è append-only. Riaperture e ritrattazioni non cancellano le righe precedenti; cambiano però quali decisioni sono effettive.
|
||||
Memory helpers use `effective_decisions()` to:
|
||||
|
||||
Gli helper memory usano `effective_decisions()` per:
|
||||
- exclude withdrawn decisions;
|
||||
- ignore decisions from phases that became stale after a rollback;
|
||||
- prevent promotion of clarifications that are no longer valid.
|
||||
|
||||
- escludere decisioni ritirate;
|
||||
- ignorare decisioni appartenenti a fasi diventate stale dopo un rollback;
|
||||
- impedire la promozione di chiarimenti non più validi.
|
||||
The effective view is defined in [harness/tht/phase.py](../harness/tht/phase.py:82).
|
||||
|
||||
La vista effettiva è definita in [harness/tht/phase.py](../harness/tht/phase.py:82).
|
||||
## Display in the session summary
|
||||
|
||||
## Visualizzazione nel riepilogo della sessione
|
||||
The "Memories" section of the summary is projected at runtime from the session ledger; it is not a direct copy of the global registry.
|
||||
|
||||
La sezione “Memories” del riepilogo viene proiettata a runtime dal ledger della sessione; non è una copia diretta del registro globale.
|
||||
The projection:
|
||||
|
||||
La proiezione:
|
||||
- shows approved Memory first;
|
||||
- then shows declined Memory;
|
||||
- resolves `seq:N` to the original `concept_clarified`;
|
||||
- hides markers whose original record is not `concept_clarified`;
|
||||
- hides subjects that match schema-linking tables;
|
||||
- hides standalone subjects shaped like `fact_*` or `dim_*`;
|
||||
- keeps table and field references when they are part of the conceptual explanation.
|
||||
|
||||
- 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.
|
||||
The logic is in [harness/tht/session/store.py](../harness/tht/session/store.py:237). The frontend renders a structured list and treats `subject`, `detail`, and `rationale` as Markdown instead of showing raw Markdown.
|
||||
|
||||
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.
|
||||
The transformation happens on read. Historical sessions use the current layout and filters without rewriting their original artifacts.
|
||||
|
||||
La trasformazione avviene in lettura: anche le sessioni storiche vengono organizzate con il layout e i filtri correnti senza riscrivere gli artefatti originali.
|
||||
## Enforced invariants
|
||||
|
||||
## Invarianti applicate
|
||||
The protections are distributed across several boundaries:
|
||||
|
||||
Le protezioni sono distribuite su più confini:
|
||||
1. `REUSABLE_TYPES` in the Python core;
|
||||
2. the `memory search` command filter;
|
||||
3. the F8 preview filter;
|
||||
4. filtering and deduplication in the Pi gate;
|
||||
5. exclusion of table Memory from the UI projection.
|
||||
|
||||
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.
|
||||
This prevents one prompt or component change from reintroducing tables as Memory.
|
||||
|
||||
Questo evita che una singola modifica al prompt o a un solo componente reintroduca le tabelle come memory.
|
||||
## Remaining limits and risks
|
||||
|
||||
## Limiti e rischi residui
|
||||
### The registry and index are not one transaction
|
||||
|
||||
### Registro e indice non sono una singola transazione
|
||||
|
||||
Il salvataggio segue sostanzialmente questa sequenza:
|
||||
Saving broadly follows this sequence:
|
||||
|
||||
```text
|
||||
registro JSONL → Qdrant → marker memory_promoted nel ledger
|
||||
JSONL registry → Qdrant → memory_promoted marker in the ledger
|
||||
```
|
||||
|
||||
Se Qdrant non è disponibile, il registro può contenere una memory non ancora ricercabile; il comando segnala che sarà necessario reindicizzare.
|
||||
If Qdrant is unavailable, the registry can contain Memory that is not yet searchable. The command reports that reindexing is required.
|
||||
|
||||
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.
|
||||
If the ledger marker fails after the vector database save, the Memory item can exist globally without a complete session audit. The gate returns a manual recovery command.
|
||||
|
||||
### Limite di cinque candidati
|
||||
### Five-candidate limit
|
||||
|
||||
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.
|
||||
F8 proposes at most five Memory items. If a session produces more than five valid concepts, the extra items are not shown and the session can be finalized without promoting them.
|
||||
|
||||
### Deduplica non globale
|
||||
### Deduplication is not global
|
||||
|
||||
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.
|
||||
Deduplication prevents duplicates within one proposal, and idempotency prevents the same decision from being promoted twice. There is no global merge of semantically similar Memory items from different sessions.
|
||||
|
||||
### Vecchi record fisici
|
||||
### Old physical records
|
||||
|
||||
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.
|
||||
Old `table_promoted` or `table_excluded` records may still exist in historical artifacts or indexes. The current code makes them unusable by filtering by type and does not show them in session projections. Physically removing them from the vector database remains a separate cleanup task.
|
||||
|
||||
## Valutazione finale
|
||||
## Final assessment
|
||||
|
||||
La gestione attuale è coerente con il requisito funzionale: una memory è una conoscenza concettuale riutilizzabile, non una scelta di schema-linking.
|
||||
The current implementation matches the functional requirement: Memory is reusable conceptual knowledge, not a schema-linking choice.
|
||||
|
||||
La parte più solida è la difesa multilivello del tipo `concept_clarified`. Il principale debito tecnico riguarda invece la convivenza del registro JSONL con Qdrant e l'assenza di una transazione unica tra archivio globale, indice semantico e ledger della sessione.
|
||||
The strongest part is the multilayer protection of the `concept_clarified` type. The main technical debt is the coexistence of the JSONL registry and Qdrant, with no single transaction spanning the global archive, semantic index, and session ledger.
|
||||
|
||||
Reference in New Issue
Block a user