ADR 0018 — Espansione parent-child a livello di store
Persistere il contesto small-to-big, collegare la giunzione Chunker attraverso l'ingestione ed espandere solo dopo il filtraggio RESTRICTED mantenendo oneste le citazioni dei figli.
Stato: accettato
Data: 2026-07-14
Registro immutabile. Esente dal tracciamento della deriva (nessun
covers). Sostituire, non modificare.
Questo completa il follow-up rinviato da ADR 0016: spostare il comportamento parent-child all'espansione a livello di store in fase di recupero. È vincolato dall'ADR 0005 del motore (isolare la nuova capacità dal comportamento predefinito byte-identico) e dall'ADR 0010 del motore (isolamento deterministico indipendente dall'output del modello).
Contesto
L'ADR 0016 ha aggiunto ParentChildChunker. Al momento del chunking un figlio trasportava window_text (il testo
completo della sezione padre) e parent_locator (l'intervallo reale di paragrafi della sezione). Tuttavia la giunzione Chunker
non era collegata all'ingestione narrativa e quei campi non venivano persistiti. Il flusso end-to-end
small-to-big—individuare un figlio a grana fine, fornire il contesto del padre per la generazione—non è quindi mai stato eseguito sul
percorso reale persistito.
Questo registro colma la lacuna: persistere i campi di gerarchia/finestra, collegare il chunker in entrambi i percorsi di ingestione ed espandere all'uscita del recupero preservando il comportamento predefinito byte-identico, la prevenzione delle risposte inventate, la provenienza e l'isolamento a doppia uscita RESTRICTED.
Decisione
Tre modifiche additive, tutte opt-in; il percorso predefinito rimane byte-identico.
Persistenza: colonne additive, i vecchi database restano leggibili
narrative_chunk acquisisce parent_id, heading, window_text e parent_locator, ciascuno
TEXT NOT NULL DEFAULT ''. ChunkStore.init_schema esegue una migrazione additiva:
PRAGMA table_info, seguita da un ALTER TABLE ADD COLUMN per colonna. Le righe esistenti ricevono valori predefiniti
vuoti e restano leggibili; non c'è alcuna ricostruzione distruttiva. StoredChunk rispecchia i quattro campi
con valori predefiniti vuoti.
Una ricostruzione con versione dello schema è stata scartata: le colonne additive sono più semplici e mantengono attive le vecchie librerie.
L'ingestione collega la giunzione Chunker
Entrambi i writer accettano chunker: Chunker | None, dove il valore predefinito None delega al
chunk_document integrato e rimane byte-identico:
ingest_narrative(..., chunker=)per la pipeline batch.NarrativeIndex(chunker=)per l'ingestione lato recupero e l'embedding.
ServiceConfig.chunker effettua la selezione tramite make_chunker, con valore predefinito "none", e il payload del
worker narrativo trasporta la selezione. Con parent_child/small_to_big, i figli arrivano con
window_text/parent_locator e lo store li persiste.
Il recupero espande dopo l'uscita di isolamento della linea A
In narrative_link._to_snippet, un window_text non vuoto diventa una chiave prompt_text separata. Il
_snippet_text dell'agente preferisce già quella chiave come contesto di generazione. text, source_locator
e chunk_id rimangono quelli del figlio individuato. parent_locator è un riferimento aggiuntivo di provenienza
all'intervallo del padre.
L'espansione modifica soltanto il contesto di generazione. Non diventa mai un risultato di ricerca, non cambia il ranking né aggiunge un risultato.
I chunker predefiniti producono valori vuoti, quindi non viene aggiunta alcuna chiave e la mappatura degli snippet rimane
byte-identica. Questo riutilizza la stratificazione prompt_text stabilita per la compressione post-recupero.
Decisione fissata sulla finestra RESTRICTED
window_text segue la doppia uscita RESTRICTED già esistente del figlio; quando un figlio è RESTRICTED,
l'intero snippet—inclusa la sua finestra padre—viene rifiutato.
_to_snippet viene eseguito solo dopo che l'uscita link/ ha rimosso i chunk sensitivity == RESTRICTED. Un
figlio riservato non raggiunge mai l'espansione della finestra e il suo window_text non può mai diventare
prompt_text. Tra le due semantiche candidate—rimuovere solo la finestra o rifiutare l'intero snippet—scegliamo
il rifiuto dell'intero snippet. Il contesto del padre è esposto solo attraverso un figlio che ha superato entrambe le
uscite.
Con l'attuale modello di sensibilità a livello di documento, figlio e padre condividono la stessa sensibilità, quindi un
figlio consentito con una sezione padre riservata non può emergere dall'ingestione. Il meccanismo è comunque
fissato in via difensiva. test_parent_child_isolation.py fornisce un chunk riservato contenente una
finestra e dimostra che l'intero snippet viene rifiutato senza alcuna fuoriuscita.
Alternative considerate (scartate)
- Sostituire lo snippet
textconwindow_text: il contesto espanso si spaccerebbe per evidenza del risultato e comprometterebbe l'onestà della provenienza. Il testo/locator del figlio restano citabili; la finestra è riservata alla sola generazione. - Rimuovere la finestra mantenendo lo snippet di un figlio riservato: la gestione parziale è più fragile del rifiuto dell'intero snippet ed è in conflitto con la semantica di uscita esistente.
- Espandere prima del filtraggio RESTRICTED: questo apre un percorso di fuga diretto. L'espansione avviene rigorosamente in
_to_snippet, a valle della rimozione. - Aggiungere una versione dello schema e ricostruire: l'approccio additivo
ALTER TABLEmantiene leggibili i vecchi database senza cerimoniale di migrazione.
Conseguenze
- Small-to-big funziona end-to-end: un match preciso su un figlio fornisce una sezione padre per la generazione mentre la citazione resta ancorata al vero locator del figlio e il locator del padre rimane un riferimento a ritroso.
- Il comportamento predefinito offline è byte-identico: il chunking predefinito lascia i campi vuoti, non aggiunge chiavi allo snippet e non altera il punteggio né la provenienza. La registrazione dei metadati BM25/vettoriali non legge mai i nuovi campi.
- Anche
SentenceWindowChunkeracquisisce la persistenza attraverso lo stesso percorso. - Gli artefatti modificati includono quattro campi
ChunkStore/StoredChunke la migrazione, le due giunzioni di ingestionechunker=,ServiceConfig.chunkere il passaggio attraverso il worker,narrative_link._to_snippetda finestra aprompt_textetests/conformance/test_parent_child_isolation.pyoltre ai test di persistenza/migrazione.
ADR 0017 — La cronologia della conversazione è contesto solo per la generazione
Aggiungere una cronologia di prima classe senza consentire ai turni precedenti di entrare nel parsing dell'intento, nel recupero, nelle decisioni di sicurezza o nelle evidenze.
Changelog
Modifiche rilevanti e visibili all'utente di RAGSpine per ogni release — e lo stato attuale della famiglia Spine al suo fianco.