← Indice documentazione Microprogettazione › fast-path introvertivo

Metnos

fast-path introvertivo — come l'assistente impara a rispondere subito
Microprogettazione v1.0 — 19 maggio 2026
Allineata con ADR 0094 (fast_path letterale), 0149 (canonical_matcher),
0150 (multi-tool memoization).

Pubblico: chi vuole capire perché Metnos a volte risponde in mezzo secondo
e a volte ci mette dieci.
Lettura: 12 minuti.

Indice

  1. L'idea in due righe
  2. Perché serve un fast-path
  3. I tre livelli, dal piu' semplice al piu' ricco
  4. L0 — modelli letterali (fast_path.py)
  5. L1 — un solo executor (canonical_matcher)
  6. L2 — sequenze multi-step (multi_tool_paths)
  7. Una scadenza che misura l'uso, non il tempo
  8. Fast-path composti: due si tengono per mano
  9. Concatenare il fast-path al pianificatore
  10. L'estrattore di argomenti
  11. Configurazione persistente
  12. Promozione a executor sintetico
  13. Domande aperte

1. L'idea in due righe

Quando una richiesta arriva a Metnos, prima di scomodare il pianificatore LLM si cerca di riconoscerla. Se l'abbiamo gia' vista e abbiamo gia' deciso bene come rispondere, la rieseguiamo così com'è e torniamo all'utente in mezzo secondo invece che in dieci. Niente modelli linguistici nel percorso critico, solo memoria.

2. Perché serve un fast-path

Il pianificatore di Metnos è un Gemma 4 26B locale: pensa bene ma ci mette circa dodici secondi a decidere il primo passo di un turno. Per molte richieste questa attesa è spropositata. «Che ora è» non ha bisogno di un LLM: ha bisogno di get_now. Anche «scaricami questa pagina e descrivila in due righe» non ha bisogno del pianificatore se è una richiesta che facciamo spesso e di cui sappiamo già la sequenza: get_urls e poi describe_entries.

Da qui la domanda: come riconosciamo che una richiesta è già nota? E come ci accorgiamo che quasi nota è abbastanza? La risposta è il fast-path introvertivo, organizzato in tre livelli — ciascuno coglie un grado diverso di somiglianza, e tutti convergono allo stesso esito: l'assistente esegue la sequenza giusta senza pensarci due volte.

3. I tre livelli, dal piu' semplice al piu' ricco

LivelloCosa riconosceComeCosto per ogni hit
L0Frasi letterali notissime («che ora è», «dove sono»)Tabella chiusa di modelli normalizzatimicrosecondi
L1Una richiesta semanticamente equivalente a una già vista, con UN solo executorBGE-M3 cosine sul log delle canonical query~30 ms
L2Una pipeline multi-step già eseguita prima, con la stessa intenzioneBGE-M3 cosine sul log delle sequenze + risoluzione di segnaposto~30 ms + N invocazioni

Il flusso è rigorosamente sequenziale: prima si tenta L0, se non matcha si tenta L1, e solo se anche L1 manca si tenta L2. Se tutti e tre mancano, si arriva al pianificatore LLM (L3) come sempre.

Nota. Il pianificatore non sparisce: il fast-path è un percorso aggiuntivo, non sostitutivo. Quando la richiesta è nuova, ambigua, o sopra la soglia di una qualunque delle ricerche, il pianificatore prende le redini come se il fast-path non ci fosse.

4. L0 — modelli letterali

Il livello più basso vive in runtime/fast_path.py e contiene una manciata di pattern testuali codificati a mano. Sono le richieste che ricorrono a tutti, dette in mille modi intercambiabili: l'ora, la data, la propria posizione, l'annulla. Per ognuna abbiamo elencato le varianti italiane e inglesi che ci aspettiamo realisticamente («che ora è», «che ore sono», «dimmi l'ora», «what time is it», «current time»...) e l'executor canonico da chiamare.

Quando arriva una richiesta, la normalizziamo (minuscolo, apostrofi diritti, punteggiatura via) e cerchiamo una corrispondenza esatta nel dizionario. Se c'è, l'executor parte subito; la risposta esce formattata da un piccolo template italiano/inglese. Zero modello linguistico, zero rete, zero attesa percepita.

È una tabella chiusa: la si estende solo aggiungendo modelli nuovi, mai con espressioni regolari complicate. La regola d'oro: se nel dubbio non sai se va, non aggiungerlo. Conservativo per costruzione.

5. L1 — un solo executor (canonical_matcher)

Il secondo livello si occupa delle richieste con UN solo executor di prima scelta: «trova le foto del battesimo», «mostrami gli appuntamenti di oggi», «leggi questa pagina». Sono casi in cui non possiamo enumerare a mano tutte le varianti possibili, perché nel mezzo l'utente cambia argomenti, ordine, lessico. Il pianificatore in pratica chiamerebbe sempre lo stesso executor: vogliamo arrivare alla stessa conclusione senza il giro lungo.

Per riuscirci, ogni volta che il pianificatore decide il primo passo di un turno, Metnos registra nella tabella canonical_query_log del mnestoma una riga con tre cose: la richiesta in forma lemma (versione canonica, minuscola, condensata), l'executor scelto, e i valori degli argomenti che ha effettivamente usato. Da quel momento quella riga è un candidato.

Quando una nuova richiesta arriva, la trasformiamo nel suo vettore semantico con BGE-M3 (lo stesso modello multilingua che usiamo per le affinità) e calcoliamo il coseno contro tutti i candidati con almeno tre osservazioni. Se il candidato più vicino supera la soglia 0,95 di coseno, lo accettiamo: la richiesta nuova viene servita con l'executor della riga matchata, e gli argomenti vengono estratti dall'estrattore deterministico (vedi §10) oppure riusati dai valori memorizzati al primo passaggio.

Tre osservazioni minime, non una. Perché una sola osservazione potrebbe essere rumore: l'utente ha scritto una frase strana, ha cliccato per errore, ha chiesto qualcosa che non tornerà più. Tre passaggi consolidano il pattern. La soglia è configurabile in ~/.config/metnos/runtime.toml.

6. L2 — sequenze multi-step (multi_tool_paths)

Alcune richieste richiedono più di un executor in sequenza, con il risultato del primo che alimenta il secondo. «Scaricami quella pagina e dimmi cosa dice» è due step: get_urls seguito da describe_entries. Anche qui il pattern si ripete: per pipeline frequenti il pianificatore finisce sempre per emettere la stessa sequenza, ma con dodici secondi di latenza ciascuna volta.

Il terzo livello memorizza le sequenze. Al termine di ogni turno andato a buon fine con due o più step, Metnos registra in multi_tool_paths.sqlite la coppia (canonical query, sequenza di executor + segnaposto degli argomenti). Dopo tre passaggi identici, la pipeline diventa candidata. Da quel momento, una richiesta nuova abbastanza simile (coseno ≥ 0,88) viene riconosciuta e rieseguita per intero: nessun pianificatore, nessuna interruzione.

La soglia 0,88 è più permissiva di 0,95 perché le pipeline multi-step hanno più varianti lessicali della stessa intenzione («scarica e descrivi», «leggi e riassumi», «fammi un sunto» sono la stessa cosa dal punto di vista dell'esecuzione). La protezione contro falsi positivi non è la soglia, ma la risoluzione dei segnaposto: se la nuova richiesta non porta i dati richiesti (per esempio non c'è un URL nel testo ma la pipeline ne richiede uno), il match fallisce e si torna al pianificatore. Niente danni.

Anche L2 ha bisogno di un punto di partenza. Al primo giorno la tabella è vuota: il sistema accumula osservazioni mentre l'utente fa le sue cose normali, e dopo una settimana di uso quotidiano comincia a riconoscere le pipeline ricorrenti. Per questo L2 nasce con la levetta spenta di default (enabled = false): si attiva quando le righe sono sufficienti per essere utili.

7. Una scadenza che misura l'uso, non il tempo

Cosa succede a una memoizzazione che non viene usata per mesi? Se Roberto parte due mesi in vacanza, le sue pipeline non vanno cancellate: gli serviranno di nuovo al rientro. Per questo le righe di multi_tool_paths non scadono a calendario, ma in giorni di attività effettiva.

La tabella system_active_days registra una riga per ogni giorno in cui Metnos ha visto almeno un turno. A ciascuna riga è associato un day_rank, un contatore monotono crescente: il primo giorno utile è 1, il secondo 2, e così via, saltando i giorni vuoti. Ogni memoizzazione conserva il day_rank dell'ultima volta che è servita. Quando il differenziale fra il giorno corrente e quel valore supera N (30 per default), la riga viene rimossa.

La conseguenza interessante: due mesi di assenza non contano. Il contatore avanza solo nei giorni di uso. Una scadenza misurata in «giorni di vita reale dell'assistente», non in «giorni del calendario gregoriano».

8. Fast-path composti: due si tengono per mano

C'è un effetto a cascata interessante. Quando un turno contiene step già serviti dal fast-path (perché alcuni sotto-pezzi della richiesta sono noti), Metnos non li scarta dalla registrazione di L2. La sequenza completa — inclusi i passi gestiti da L0 o L1 — viene memorizzata per quella che è: una pipeline che ha funzionato. Dopo tre passaggi, anche quella combinazione diventa un fast-path multi-step.

Significa che il sistema impara a comporre: due fast-path adiacenti diventano un fast-path più lungo, ricorsivamente. La decisione storica è già approvata per ciascun passo, comporne più insieme non aggiunge rischio, solo determinismo.

9. Concatenare il fast-path al pianificatore

Una variante opzionale (oggi disattivata di default) permette al fast-path L2 di passare il testimone al pianificatore dopo avere eseguito la sua sequenza. È il caso in cui la richiesta ha più verbi: «scaricami questa pagina e poi mandami il sunto per email». Il prefisso «scarica e descrivi» è un L2 conosciuto; ma «mandami per email» è intenzione residua.

Se la levetta chain_to_planner è accesa e il rilevatore di continuazione (multi-verbo, congiunzioni) trova ulteriori azioni nella richiesta, Metnos esegue il prefisso col fast-path, lo iscrive nella memoria di lavoro del turno, e cede al pianificatore per gli step rimanenti. Il pianificatore vede una storia già popolata e decide solo cosa manca, partendo dallo step successivo.

10. L'estrattore di argomenti

Riconoscere la richiesta è metà del lavoro. L'altra metà è ricostruire i valori concreti degli argomenti: quali path, quali URL, quale data, quale soglia. Per questo Metnos ha un piccolo estrattore deterministico (args_extractor.py) che lavora a regole, in tre passi:

  1. Memoria. Se la riga matchata nel canonical_query_log ha valori osservati al primo passaggio (colonna args_observed), li riusiamo tali e quali.
  2. Regole. Altrimenti applichiamo espressioni regolari chiuse per i tipi noti: URL (https://...), path (~/... o /..., con la scorciatoia «home» che diventa ~/), email, numeri, estensioni di file («file PDF» diventa *.pdf), date (oggi/ieri/domani/dopodomani in italiano e inglese, mappate a formato ISO), finestre temporali («questa settimana», «ultime 24 ore», «last 7 days»).
  3. LLM, solo se serve. Se restano argomenti obbligatori non coperti, e la levetta canonical_query_args_llm è accesa, si chiama il Gemma fast tier con uno schema vincolato e un prompt minimo. Il risultato viene messo in cache su disco per non ripetere la chiamata. Spento di default: l'esperienza dice che le regole bastano per il 90% delle richieste reali.

11. Configurazione persistente

Tutti i parametri del fast-path vivono in un file TOML in ~/.config/metnos/runtime.toml, generato al primo avvio dal setup. Una variabile d'ambiente METNOS_* ha sempre la precedenza, per il debug e i bench; il file TOML stabilisce il valore persistente fra un riavvio e l'altro, il default codificato nel modulo è l'ultima rete di sicurezza.

[fast_path]
canonical_query_enabled    = true
canonical_query_min_uses   = 3
canonical_query_threshold  = 0.95
canonical_query_args_llm   = false

[multi_tool_fast_path]
enabled            = false
chain_to_planner   = false
threshold          = 0.88
min_uses           = 3
ttl_active_days    = 30
k_synth            = 50

12. Promozione a executor sintetico

C'è un ultimo passaggio che chiude il cerchio. Quando una pipeline L2 viene usata abbastanza spesso da meritare un trattamento diverso (50 utilizzi, soglia configurabile), un job notturno (multi_tool_promote) la promuove a candidato per la sintesi vera e propria. Crea un proto-mnest nel mnestoma, con la firma desiderata (sequenza di executor, segnaposto, query canonica), e da quel momento il motore di sintesi può produrre UN nuovo executor unificato che racchiude tutta la pipeline. La promozione è idempotente: una riga già promossa viene ignorata nelle esecuzioni successive.

Il bridge fa due tier conviventi: L2 cattura il volume basso (sequenze viste 3-50 volte, replay deterministico tramite N invocazioni di executor), mentre L3 prende il sopravvento quando il pattern è stabilmente ricorrente e vale l'investimento di sintetizzare un executor monolitico che fa tutto in un colpo. Non si escludono: si completano.

13. Domande aperte

Alcuni punti sono volutamente lasciati al futuro, perché diventano interessanti solo con un po' di volume.

© 2026 Roberto Brunialti · documentazione Metnos · ADR di riferimento: 0094 / 0149 / 0150