← Indice documentazione Microprogettazione › Motore cognitivo

Il motore cognitivo

come Metnos pianifica ed esegue, senza chiamare il cloud
Microprogettazione — architettura del motore a quattro strati.

Pubblico: chi vuole capire come Metnos pensa,
perché risponde in fretta e impara dall’uso.
Lettura: 15 minuti.

Indice

  1. In una riga: un solo modello, chiamato una volta sola
  2. L’idea: proporre tutto in un colpo, e ricordare
  3. I quattro strati del motore
  4. La cascata che attraversa un turno
  5. Il piano: che cosa propone il motore
  6. Strato 0 — Fastpath: le scorciatoie auto-apprese
  7. Strato 1 — Autopath: gli autopath appresi dal feedback
  8. Strato 2 — il controllo del piano
  9. Strato 3 — Proposer, Executor, Recovery, Terminator
  10. Come impara e accelera
  11. Cosa cambia per l’utente
  12. Perché funziona meglio dei framework LLM-in-loop
  13. Numeri onesti
  14. Domande aperte

1. In una riga: un solo modello, chiamato una volta sola

Quando arriva una richiesta, Metnos non chiama più il pianificatore cinque o sei volte di seguito. Per prima cosa prova le scorciatoie che già conosce; se non bastano, chiede al modello linguistico locale di proporre l’intero piano in una sola chiamata; poi lo esegue in modo deterministico, e se qualcosa va storto tenta un recupero mirato o spiega onestamente cosa manca. Quattro strati, un solo modello, mai il cloud.

2. L’idea: proporre tutto in un colpo, e ricordare

Il motore attuale parte da due intuizioni semplici.

Prima intuizione: una sola proposta. Invece di interrogare il modello a ogni passo, lo si interroga una volta sola e gli si chiede l’intero piano in un colpo: la lista completa degli step, con i collegamenti fra loro e il messaggio finale. Il modello vede tutto il problema in una volta, costruisce coerenza interna (sa che lo step 4 ha bisogno dell’output dello step 3) e produce un piano molto più stabile. L’esecuzione che segue è pura meccanica deterministica: niente più dadi.

Seconda intuizione: ricordare ciò che funziona. Quando un piano arriva in fondo bene, il sistema lo conserva, indicizzato per «significato» della richiesta. La volta dopo che arriva una richiesta dello stesso tipo, il piano è già pronto: parte in pochi millisecondi, senza disturbare il modello. Il sistema diventa il proprio pianificatore — impara a non chiedere più.

Queste due intuizioni si traducono in quattro strati, provati in cascata. La distinzione che conta è fra ciò che ricorda e ciò che ragiona: i primi due strati (L0 e L1) sono memoria con stato — un archivio che cresce con l’uso e risponde senza disturbare il modello quando riconosce la richiesta. Gli altri due (L2 e L3) sono senza stato: non ricordano nulla da un turno all’altro, ma ricontrollano il piano e, se serve, lo costruiscono daccapo. Il terzo è un controllo, il quarto è il motore vero e proprio che propone, esegue, recupera e — se non c’è via d’uscita — ammette onestamente il limite.

4. I quattro strati del motore

Ogni turno attraversa gli strati dall’alto verso il basso. Appena uno strato risolve, il turno termina: gli strati sotto non vengono nemmeno toccati. I primi due (L0 e L1) sono i due strati con stato, la memoria che impara: sono velocissimi e non usano il modello linguistico. Gli altri due (L2 e L3) sono senza stato — non conservano nulla fra un turno e l’altro — e solo il quarto, solo quando serve, chiama davvero il modello.

Richiesta utente intent_extractor · verbo + oggetto L0 · Fastpathscorciatoia auto-appresa (hash + coseno) miss L1 · Autopathautopath appreso (match semantico + intent) miss L2 · Validatorcontrollo del piano L3 · Engine Proposer1 chiamata LLM Executordeterministico Recovery4 classi errore Terminatorlimite onesto propone · esegue · recupera · ammette il limite selettore: simple | metis | frontier Risposta all'utente hit → risposta
La cascata a quattro strati: ogni strato prova a rispondere; se non riconosce la richiesta passa al successivo. Fastpath e Autopath rispondono in pochi millisecondi senza il modello; il motore (L3) lo chiama una volta sola.
L0

Fastpath

scorciatoie auto-apprese · con stato

File: runtime/engine/fastpath.py · archivio fastpaths.sqlite.

Cosa fa: è il primo dei due strati con stato. Riconosce le richieste già risolte con successo dal piano pieno: la scorciatoia si auto-produce a ogni turno riuscito (valvole: rimozione dalla console di amministrazione e invecchiamento). Il riconoscimento avviene in due tempi: prima un confronto esatto per impronta (hash, sotto i 5 ms, zero modello), poi un confronto per significato con gli embedding BGE-M3 (coseno, sotto i 150 ms). Se trova un riscontro, esegue subito il piano salvato.

Quando vince: sempre, se c’è un match. Una scorciatoia auto-appresa ha la precedenza su tutto il resto.

L1

Autopath

autopath appresi · con stato

File: runtime/engine/autopath.py · archivio autopath.sqlite.

Cosa fa: è il secondo strato con stato, la memoria che cresce da sola. Conserva i piani che hanno funzionato, indicizzati per significato della richiesta. Cerca prima per somiglianza semantica (cluster di richieste affini) e poi per corrispondenza esatta dell’intento. Se riconosce la richiesta, esegue il piano già collaudato senza chiamare il modello.

Quando interviene: dopo Fastpath, solo se l’intento è completo (verbo + oggetto riconosciuti). Registra inoltre ogni turno per imparare in futuro.

L2

Validator

controllo del piano · senza stato

File: runtime/engine/validator.py.

Cosa fa: è un controllo deterministico del piano prima di eseguirlo. Non conserva nulla fra un turno e l’altro: è il primo dei due strati senza stato. Verifica che i tool citati esistano, che gli argomenti abbiano forma valida, che i riferimenti fra step puntino a qualcosa di reale. Se trova errori, chiede al motore di riproporre il piano una volta. Così un errore banale viene corretto senza nemmeno avviare l’esecuzione.

Quando interviene: fra la proposta e l’esecuzione. Attivo per impostazione predefinita.

L3

Engine

proposta · esecuzione · recupero · senza stato

File: runtime/engine/{proposer,executor,recovery,terminator}.py.

Cosa fa: è il cuore del motore, quattro componenti senza stato che lavorano in sequenza. Il Proposer chiede al modello l’intero piano in una sola chiamata. L’Executor lo realizza passo per passo, in modo completamente deterministico. La Recovery entra in gioco se uno step fallisce: classifica l’errore e tenta un’alternativa. Il Terminator è l’ultima istanza onesta: se non c’è via d’uscita, spiega all’utente cosa manca.

Quando interviene: solo se né Fastpath né Autopath hanno riconosciuto la richiesta.

Due strati con memoria, due senza. La spina dorsale del motore è questa asimmetria: solo L0 (fastpath) e L1 (autopath) hanno uno stato che persiste e cresce con l’uso — sono la memoria del sistema. Il fastpath riconosce la stessa richiesta già risolta; l’autopath riconosce un piano generalizzato a un cluster di richieste affini, promosso dai riscontri positivi. Il Validator (L2) e il motore (L3) sono invece privi di stato: ricontrollano e ricostruiscono il piano da zero ogni volta, senza ricordare i turni passati. Imparare è compito della memoria; ragionare è compito del motore.
Un solo punto di ingresso. Tutto questo è orchestrato da runtime/engine/dispatch.py, che espone una sola funzione di turno e annota quale strato ha risposto (fastpath, autopath, engine, recovery o terminator). Il dispatcher non sa nulla dei domini applicativi: orchestra soltanto gli strati. Aggiungere un nuovo motore o una nuova strategia di recupero non richiede di toccare gli altri strati.

5. La cascata che attraversa un turno

Ecco il cammino completo, dal messaggio dell’utente alla risposta finale. Si legge dall’alto verso il basso: ogni livello si tenta solo se quello sopra non ha già risolto.

 UTENTE: "cerca le mail spam e mettile in cestino"
 |
 v
 +-------------------+
 | fast_path (pre) | regex su pattern banalissimi ("che ora e'", "dove sono")
 | (zero LLM) | --> match? rispondi in ~50ms e termina
 +-------------------+
 | (no match)
 v
 +-------------------+
 | intent_extractor | LLM fast tier ~370ms
 | verbo + oggetto + | --> (verbo="move", oggetto="messages",
 | parole chiave | keywords=["spam","cestino"])
 +-------------------+
 |
 v
+--------------------------------------------------------------+
| MOTORE (engine/dispatch.run_turn) |
| |
| +----------------+ |
| | L0 Fastpath | hash (<5ms) + coseno BGE-M3 (<150ms) |
| | lookup | --> scorciatoia auto-appresa ? esegui |
| +----------------+ |
| | (miss) |
| v |
| +----------------+ |
| | L1 Autopath | match semantico + intent_hash |
| | lookup | --> autopath appreso ? esegui (no LLM) |
| +----------------+ |
| | (miss) |
| v |
| +----------------+ |
| | L3 Proposer | LLM wise 1-shot |
| | propose | --> piano JSON {steps, fillers, |
| | (Qwen 35B-A3B) | final_message} |
| +----------------+ |
| | |
| v |
| +----------------+ |
| | L2 Validator | typecheck piano; errore? --> riproponi |
| +----------------+ |
| | |
| v |
| +----------------+ |
| | Executor | per ogni step: |
| | (deterministico)| - resolve from_step + FILLER + RUNTIME |
| +----------------+ - invoke_executor + vaglio + accumula |
| | |
| v |
| +----------------+ |
| | render final | template "Trovate {N} mail, spostate" |
| +----------------+ |
+--------------------------------------------------------------+
 |
 v (ok? si' --> Autopath registra + risponde a utente)
 |
 | (errore? si' --> entra Recovery)
 v
 +-------------------+
 | Recovery | classifica errore: wrong_tool / wrong_args /
 | classify + retry | missing_input
 | | --> riproponi escludendo il tool fallito
 | | --> esegui di nuovo
 +-------------------+
 |
 v (ok? si' --> risposta)
 |
 | (out_of_scope o recovery fallita? --> Terminator)
 v
 +-------------------+
 | Terminator | spiega onestamente: causa + azione suggerita
 | honest dead-end | --> registra la lacuna in terminator_log.sqlite
 | | --> "Non posso risolvere: X. Per procedere: Y."
 +-------------------+
 |
 v
 UTENTE: messaggio finale (risposta o richiesta di azione)
Una nota sull’ordine. Il Validator è lo strato 2 per numerazione, ma agisce dopo la proposta: il suo posto naturale è fra il Proposer e l’Executor, perché controlla proprio il piano appena proposto. La numerazione riflette la priorità di progetto, non l’ordine temporale esatto.

6. Il piano: che cosa propone il motore

Il Proposer non produce testo libero: produce un oggetto strutturato che chiamiamo framework (il piano). Ha tre parti:

{
 "steps": [
 {"tool": "find_messages",
 "args": {"folder": "INBOX", "query": "is:unread"}},
 {"tool": "classify_entries",
 "args": {"from_step": 1, "dimension": "spam"}},
 {"tool": "filter_entries",
 "args": {"from_step": 2, "where_field": "spam", "where_value": "spam"}},
 {"tool": "move_messages",
 "args": {"from_step": 3, "dst_folder": "${FILLER:cestino_folder}"}}
 ],
 "fillers": {
 "cestino_folder": {
 "prompt": "Come si chiama la cartella cestino per questo account?",
 "default": "Trash",
 "tier": "fast"
 }
 },
 "final_message": "Spostate ${step4.ok_count} mail in cestino."
}

Dentro gli argomenti compaiono quattro tipi di segnaposto, che l’Executor risolve in modo deterministico al momento giusto:

SegnapostoSignificato
from_step: NPrendi le entries prodotte dallo step N (numerazione da 1) e passale a questo step.
${stepN.field}Estrai un campo dal risultato dello step N (supporta percorsi annidati e proiezioni). Usato soprattutto nel messaggio finale.
${FILLER:nome}Slot riempito al volo da una piccola chiamata al modello fast tier (con cache), o dal valore di default.
${RUNTIME:chiave}Valore di contesto del turno: actor (chi sta parlando), lang, channel.
Perché la proposta è affidabile. La chiamata al modello può essere vincolata da una grammatica deterministica (GBNF): il decoder rifiuta in tempo reale ogni token che violerebbe lo schema, quindi il piano non può uscire malformato. Inoltre il modello non vede tutti i tool del sistema, ma solo i più pertinenti alla richiesta (una dozzina, selezionati per affinità): il prompt resta corto e la proposta più rapida e più precisa.

7. Strato 0 — Fastpath: le scorciatoie auto-apprese

Il Fastpath è la corsia preferenziale. Ogni turno completato con successo dal piano pieno crea da solo la scorciatoia — le catene sono executor già vagliati e testati, nessuna approvazione —: da quel momento quella richiesta (e le sue varianti vicine) saltano tutto il resto e vengono servite direttamente. Le scorciatoie invecchiano da sole (mai riusate, stantie, tetto totale), muoiono quando un executor le rimpiazza o sparisce, e si rimuovono a mano dalla console admin.

Il riconoscimento avviene in due tempi. Prima un confronto esatto per impronta della frase (deterministico, sotto i 5 ms, senza alcun modello). Se non basta, un confronto per significato: la richiesta viene trasformata in un vettore con BGE-M3 e confrontata per coseno con le scorciatoie salvate (sotto i 150 ms). Una scorciatoia auto-appresa vince sempre, anche su un autopath appreso automaticamente.

8. Strato 1 — Autopath: gli autopath appresi dal feedback

L’Autopath è la memoria che cresce da sola, senza che nessuno scriva regole a mano. Conserva tre tabelle nel proprio archivio sqlite:

TabellaCosa contieneQuando viene scritta
autopaths Piani che hanno funzionato, indicizzati per significato della richiesta e raggruppati per cluster semantico. Per ognuno: il piano, i contatori d’uso, un punteggio composito, lo stato (campione/sfidante). Promossi automaticamente quando un piano si dimostra valido sullo stesso tipo di richiesta.
anti_autopaths Piani che hanno fallito ripetutamente. Per ognuno: il motivo e una scadenza (circa 30 giorni). Quando un piano fallisce ripetutamente sullo stesso intento. Alla scadenza, il sistema riprova.
observations Registro di ogni turno: intento, piano eseguito, latenza, vettore semantico. In sola aggiunta. Sempre, a fine turno. È la fonte di verità per promuovere o ritirare gli autopath.

La ricerca procede in due tempi: prima per somiglianza semantica (la richiesta cade nel cluster di richieste affini già viste e si serve il piano «campione» di quel cluster), poi, se serve, per corrispondenza esatta dell’intento. Quando più piani competono per lo stesso cluster, vige un meccanismo campione/sfidante: lo sfidante deve dimostrarsi migliore prima di prendere il posto del campione.

L’effetto pratico. La prima volta che si chiede «cerca le mail spam e mettile in cestino», il modello impiega qualche secondo per generare il piano. La seconda volta, l’Autopath ha già quel piano in memoria: la pipeline parte in pochi millisecondi. Da quel momento, quella richiesta non passa più per il modello linguistico.

9. Strato 2 — il controllo del piano

Prima di eseguire un piano appena proposto, il Validator lo passa al setaccio, in modo puramente deterministico e senza modello:

Se trova anche un solo errore, non avvia l’esecuzione: chiede al Proposer di riproporre il piano una volta, escludendo quello sbagliato. Così un refuso o un argomento storto vengono corretti a costo zero, senza sprecare una chiamata di recupero più pesante. È attivo per impostazione predefinita.

10. Strato 3 — Proposer, Executor, Recovery, Terminator

Proposer — la proposta in un colpo

Il Proposer è l’unico componente che parla davvero con il modello linguistico. Riceve la richiesta e l’intento estratto e produce, in una sola chiamata, il piano intero. Niente ragionamento passo-passo, niente tool-call iterativi: un solo oggetto strutturato.

Esistono varianti selezionabili (impostazione METNOS_ENGINE):

Executor — l’esecuzione senza dubbi

L’Executor è il componente più rigoroso: zero modello linguistico nel loop principale, solo Python deterministico. Per ogni step: risolve i riferimenti agli step precedenti (from_step), riempie gli slot ${FILLER:…} (una piccola chiamata fast con cache) e i valori di contesto ${RUNTIME:…}, valida gli argomenti, chiama l’executor del tool, passa il risultato al Vaglio (il giudice di sicurezza) e accumula. Alla fine compone il messaggio finale dal modello con i campi ${stepN.field}.

Una virtù nascosta: la riproducibilità. Lo stesso piano eseguito sulla stessa istantanea di dati produce sempre lo stesso esito. Questo significa che si può rieseguire un turno passato e che i test possono fare verifiche puntuali — cosa impossibile con un pianificatore stocastico.

Recovery — il recupero mirato

Se uno step fallisce, entra la Recovery. Il suo primo lavoro è capire che tipo di errore è, leggendo la classe d’errore strutturata che l’executor restituisce (niente analisi del testo multilingua):

ClasseQuandoStrategia
wrong_tool Il tool scelto non era adatto: ha fallito o è semanticamente sbagliato. Riproponi un piano diverso, escludendo quel tool.
wrong_args Il tool era giusto ma gli argomenti erano malformati: pipeline a vuoto, limite di passi raggiunto, un riferimento che punta nel nulla. Riproponi con argomenti canonici e riferimenti espliciti.
missing_input Manca un presupposto: un indice non costruito, una cartella inesistente, un filtro che produce zero risultati. Riproponi un piano alternativo; se serve l’utente, cedi al Terminator.
out_of_scope Errore non recuperabile: serve un’azione fisica dell’utente (per esempio condividere la posizione) o manca del tutto una capacità. La Recovery non interviene: cede subito al Terminator.

La Recovery tenta una sola alternativa, escludendo sia il piano fallito sia il tool che ha causato il problema. Niente loop infiniti: se l’alternativa non riesce, la parola passa al Terminator.

Terminator — il vicolo cieco onesto

Il Terminator è il pezzo più sottile del sistema. Non è un «gestore di errori»: è un riconoscimento onesto del limite. Quando arriva il suo turno, vuol dire che tutto il resto ha tentato e fallito. Il Terminator non finge un successo: spiega all’utente la causa e suggerisce un’azione concreta, e registra la lacuna in un archivio (terminator_log.sqlite) con un contatore di quante volte si è ripresentata.

I messaggi di causa e azione non sono stringhe fisse nel codice: passano dal dizionario multilingua, così l’utente li legge nella propria lingua. Il turno si chiude sempre con una risposta leggibile, mai con un silenzio o un errore grezzo.

La lacuna è evolutiva. Quando l’utente risolve la mancanza (condivide la posizione, abilita un autopath, costruisce un indice), la stessa richiesta torna alla cascata normale e tipicamente diventa un autopath appreso dopo qualche conferma. Ogni vicolo cieco riconosciuto è un’occasione di crescita: il numero di occorrenze è visibile nel pannello di amministrazione e aiuta a decidere cosa colmare strutturalmente.

11. Come impara e accelera

Sotto a ogni risposta in chat, l’utente vede dei piccoli pulsanti di riscontro:

Non è obbligatorio premere niente: se una pipeline arriva in fondo senza errori, l’Autopath la considera comunque un’osservazione positiva. Il feedback esplicito accelera e rifinisce, ma il sistema impara anche dal silenzio. Più lo si usa, più spesso le richieste vengono servite dalla memoria invece che dal modello — e quindi più in fretta.

12. Cosa cambia per l’utente

1. Le prime richieste sono più veloci

Anche al primo colpo, il motore non interroga il pianificatore passo-passo ma fa una sola proposta. La sensazione è quella di un sistema che «ci pensa un attimo» invece di «si blocca per un minuto».

2. Le richieste ripetute sono istantanee

Dopo qualche turno della stessa famiglia di richieste, l'autopath è appreso e i turni successivi partono dalla memoria. La risposta arriva in una frazione di secondo: è la differenza fra «assistente» e «reazione immediata».

3. I fallimenti diventano informativi

Quando qualcosa non si può fare, il Terminator dice esattamente cosa serve. Niente messaggi vaghi del tipo «errore generico», niente loop di tentativi inutili: una frase chiara, un suggerimento operativo e, se è il caso, un dialogo per risolvere la mancanza sul momento.

13. Perché funziona meglio dei framework LLM-in-loop

I framework per «workflow di agenti LLM» (LangGraph e affini) coprono in parte lo stesso problema: ridurre la varianza del modello nel loop dando struttura ai task. Ma hanno limiti che questo motore non ha:

AspettoFramework LLM-in-loopMotore di Metnos
Origine dei workflow scritti a mano, manutenzione manuale seed opzionale, ma crescono da soli dal feedback ✓ ✗ ↺
Apprendimento no sì: memoria che si popola dai turni andati bene
Anti-errore no tabella anti_autopaths che esclude i cammini falliti
Recupero strutturato retry generici 4 classi d’errore ortogonali + riproposta mirata
Vicolo cieco onesto retry infinito o crash Terminator: classifica + suggerisce un’azione
Tracciabilità nessuna, o log testuali registro observations + pannello di amministrazione
Coerenza dei nomi nomi liberi vocabolario chiuso (verbo + oggetto + qualificatore) garantito

L’analogia che chiarisce tutto

I framework LLM-in-loop tradizionali sono come artisti con tela bianca: ogni turno reinventano la composizione. Bella creatività, ma il rischio di sbagliare strumento è reale e ogni opera costa.

Il motore di Metnos è come un artigiano con quaderno di schizzi: il modello schizza il piano una volta (creatività concentrata e vincolata), e da quel momento l’artigiano riapre il quaderno alla pagina giusta. La creatività del modello c’è ancora, ma è limitata al momento dell’invenzione: l’esecuzione è riproduzione fedele dello schizzo. Così si evitano le allucinazioni in esecuzione, si guadagna velocità e si arriva a costo zero quando il quaderno è già ricco.

14. Numeri onesti

Su un campione di richieste reali (mail, file, web, posizione, ora, dialoghi multi-turno, scheduling, contatti), confrontando il pianificatore iterativo sul cloud con il motore locale a singola proposta:

ModalitàCoperturaLatenza mediaModelloCosto
Pianificatore iterativo (cloud, passo-passo) equivalente ~76 s cloud frontier a pagamento
Motore locale (singola proposta + memoria) equivalente ~12 s a freddo, <1 s da memoria Qwen 3.6 35B-A3B locale 0

Diverse volte più veloce, copertura equivalente, costo zero (il modello gira in locale su hardware a memoria unificata). E la latenza scende ancora man mano che la memoria si popola: le richieste ricorrenti partono dalla cache, sotto il secondo.

Onestà sul limite. Alcune richieste non si possono soddisfare in nessuna modalità: senza posizione condivisa non si può dire dove sei, senza un autopath abilitato non si può cercare in quel servizio. In questi casi il Terminator raccoglie il fallimento e spiega all’utente cosa serve. Nessuno «vince» in modo fasullo: il fallimento è classificato e l’utente sa cosa fare.

15. Domande aperte