/opt/metnos/executors/ e generati al volo dal Synt in
~/.local/share/metnos/executors/ (separati per costruzione).
Manifesto firmato Ed25519, recinto di esecuzione costruito sul manifesto stesso, ciclo di vita gestito dal loader.
Vedi anche agent_runtime
(loop pianificatore), sandbox
(recinto) e synt
(generazione automatica).
executor — capire come è fattoUn executor è una piccola utilità specializzata che fa una cosa sola: legge la posta, cerca un file, scatta una foto dell'orologio, manda un messaggio. Vive da solo, in una sua cartella, e parla con Metnos attraverso un contratto preciso.
Pensa a una cassetta degli attrezzi. Ogni attrezzo è semplice e riconoscibile: il cacciavite stringe le viti, il martello batte i chiodi. Nessuno chiederebbe al cacciavite di battere un chiodo. In Metnos è lo stesso: ogni executor è un attrezzo con un compito chiaro. Quando l'utente chiede qualcosa, il pianificatore sceglie l'attrezzo giusto e lo usa.
Le tre cose che lo definiscono sono:
Un executor è una cartella sul disco. Dentro ci sono al massimo cinque file, ognuno con un mestiere. Niente di più.
I tre file obbligatori sono il cuore. Il codice fa il lavoro. Il manifesto descrive il codice in modo che il pianificatore possa decidere se usarlo. La firma dimostra che il manifesto non è stato manomesso da quando l'autore l'ha scritto.
| File | Cosa contiene | Chi lo legge |
|---|---|---|
manifest.toml | Nome, descrizione, argomenti, esempi, schema dell'output, profilo del recinto | il pianificatore (per scegliere), il loader (per caricare) |
manifest.toml.sig | Firma Ed25519 del manifesto in byte | il loader, all'avvio del catalogo |
<nome>.py | Una funzione invoke(args) che fa il lavoro | il runtime, quando l'attrezzo viene chiamato |
manifest.lang_state.json | Hash delle descrizioni per lingua (per il sistema di traduzione) | il daemon di traduzione notturno |
Il manifesto è un file in formato TOML. Lo apri con un editor di testo e ci capisci qualcosa anche senza essere un programmatore. Dichiara tutto quello che il pianificatore deve sapere: come si chiama l'executor, cosa fa, che argomenti accetta, come è fatto il risultato, qualche esempio per orientarsi.
Tre cose meritano un secondo sguardo, perché sono quelle su cui si regge l'intero sistema.
L'impronta del codice (digest): è un'impronta crittografica
calcolata sui byte del file .py. Se qualcuno modifica anche solo una virgola
nel codice senza ricalcolare l'impronta, il loader rifiuta l'executor. Il manifesto e
il codice sono legati come un certificato e il documento che certifica.
La forma del risultato (output.schema_inline): dichiara
campo per campo cosa restituisce l'executor. Serve a chi compone catene di executor
(il pianificatore non vola alla cieca: legge lo schema e sa cosa aspettarsi al passo
successivo) e a chi genera codice in automatico.
I permessi (capabilities): non è il manifesto a stabilire
cosa l'executor può fare. Il manifesto dichiara di cosa avrebbe bisogno per
funzionare; poi il sistema decide se concedere quei permessi e con che vincoli.
Vedi sandbox.
Un executor non gira a piede libero sul sistema. Vive dentro un recinto, costruito proprio sui permessi che il suo manifesto dichiara. Il recinto stabilisce: cosa può leggere sul disco, cosa può scrivere, con quali macchine può parlare in rete, quali file di sistema sono comunque off limits anche se l'executor li chiedesse.
La regola d'oro è semplice: il recinto è sempre più stretto del manifesto.
Se un manifesto chiede di leggere /home, il recinto può comunque tagliare la
richiesta a /home/user/notes. Se chiede di parlare con qualunque dominio,
il recinto può limitare a imap.example.com. La differenza è importante:
il manifesto è quello che l'autore sperava, il recinto è quello che il sistema
ha deciso dopo aver visto chi lo invoca, in che contesto, con che livello di
fiducia.
| Cosa | Dove sta scritta |
|---|---|
| Cosa l'executor vorrebbe poter fare | nel manifesto, blocco capabilities |
| Cosa davvero può fare nel turno corrente | nel recinto applicato dal sandbox al momento dell'invocazione |
| Cosa nessuno può fare, mai | nelle "regole del nucleo", non negoziabili |
/etc/shadow (il file delle password di sistema), succederebbe questo:
il manifesto non chiede quel permesso, il sandbox lo intercetta e lo rifiuta, l'executor riceve
un errore di "permesso negato". Anche se per un attimo il codice fosse compromesso, non c'è
modo di uscire dal recinto.
Un executor non nasce attivo e non rimane attivo per sempre. Attraversa una serie di stati che il sistema gestisce automaticamente. Capire questi stati aiuta a leggere il catalogo: se un attrezzo non viene scelto più, magari è perché è passato in quarantena, non perché sia rotto.
| Stato | Significato | È in pool? |
|---|---|---|
| proposto | esiste solo il manifesto, manca il codice | no |
| sintetizzato | codice presente e test verdi, in attesa di firma | no |
| attivo | firmato e visibile al pianificatore | sì |
| in quarantena | rimpiazzato da un altro o sospetto, ma recuperabile | visibile in audit, escluso dalle nuove scelte |
| archiviato | fuori dal sistema; resta solo per l'audit storico | no |
Tutti gli executor hanno la stessa anatomia esterna, ma nascono in quattro modi diversi. La distinzione non è cosmetica: cambia chi li scrive, dove vivono sul disco, e da dove provengono le idee che li abitano.
L'autore li compone con pazienza. Sono il nucleo stabile, il seed da cui parte tutto.
Cartella: /opt/metnos/executors/
Esempi: get_now, find_files, read_messages, send_messages.
Stretti, robusti, rivisti più volte.
Quando il catalogo non copre una richiesta, il Synt compone un nuovo executor con cinque passi (nome, contratto, prove, descrizione, codice).
Cartella: ~/.local/share/metnos/executors/
Esempi: specializzazioni o generalizzazioni di seed esistenti.
Tenuti separati: non possono mai fare ombra ai seed scritti a mano.
Una skill pubblica (es. da agentskills.io) descrive in italiano l'uso di un servizio terzo. Un traduttore deterministico la converte in uno o più executor Metnos.
Cartella: ~/.local/share/metnos/executors/skills/
Esempi: read_events, set_events, delete_events (da una skill calendario).
Stessi controlli di un generato: niente trattamento di favore per il fatto di venire da fuori.
Servizi interni del runtime. Non hanno cartella propria, vivono dentro Metnos.
Cartella: runtime/system/
Esempi: undo_last_turn, scheduler, admin.
Servizi del sistema, non attrezzi normali.
Una libreria di terzi ha tipicamente una sua documentazione testuale che
spiega come si usa: «per leggere il calendario chiama gws calendar list;
per creare un evento usa --summary e --start». Lo standard
agentskills.io ha codificato questa documentazione in un formato
preciso (un file Markdown con intestazione strutturata). L'importatore di
Metnos legge quel formato, lo traduce nel vocabolario chiuso del sistema, e
genera la cartella dell'executor come se fosse scritta a mano.
Il vantaggio: ogni servizio già documentato come skill (calendario di Google,
posta, drive di archiviazione,...) si può portare in Metnos senza riscriverlo
da capo. Lo svantaggio: bisogna fidarsi di chi ha scritto la skill (e dei suoi
script di supporto). Per questo l'importatore non installa nulla in
/opt/metnos/executors/: gli executor importati vivono in una cartella
separata, sotto sorveglianza, e passano comunque dal vaglio
prima di ogni chiamata.
Un dettaglio operativo: gli executor importati che hanno bisogno di credenziali (una chiave di una API esterna, il file di sessione di un account) non se le inventano. La prima volta che li chiami, ti aprono una finestra di dialogo, ti chiedono il dato mancante, lo cifrano in una cassaforte locale, e proseguono. Le chiamate successive trovano la credenziale già al suo posto. Niente password nel codice, niente file di configurazione da modificare a mano.
Vediamo tre executor realmente in uso, raccontati da fuori. Niente codice sorgente: solo cosa chiedi e cosa ottieni.
get_now — "che ora è?"L'attrezzo più semplice del catalogo. Non ha argomenti obbligatori. Ritorna un dizionario con la data e l'ora correnti.
chiamata: get_now(timezone="Europe/Rome")
risposta: { ok: true,
content: "2026-05-06T16:45:23+02:00",
metadata: { timezone: "Europe/Rome", iso8601: "...", epoch:... } }
Niente rete, nessun file letto, nessuna scrittura. Permessi: time:read.
È uno di quegli attrezzi che sembrano superflui finché non si capisce perché servono:
il pianificatore non deve mai inventarsi la data dalla memoria di addestramento.
Quando deve calcolare "le mail di ieri", chiama prima get_now, poi calcola
"ieri" sottraendo. Così "ieri" è sempre quello vero, non quello del giorno in cui il modello è stato addestrato.
find_files — "trovami le foto"Cerca file per nome o per modello (le classiche "estensioni"). Restituisce la lista con i metadati di base: percorso, nome, dimensione, data ultima modifica, tipo.
chiamata: find_files(base_path="/home/user/images", pattern="*.jpg")
risposta: { ok: true,
entries: [
{path: "/home/.../foto1.jpg", size: 2458123,...},
{path: "/home/.../foto2.jpg", size: 1923456,...},...
],
metadata: { count: 247,... } }
Il pianificatore lo usa quando deve passare la lista a un altro attrezzo: per esempio
filtrare le foto più recenti, calcolare la dimensione totale, comprimere quelle più vecchie.
Notare che la filtrazione non sta in find_files: l'attrezzo torna i
file e basta. Se vuoi un sottoinsieme, glielo chiedi col modello, oppure passi il
risultato a un altro attrezzo che filtra. Una cosa sola alla volta.
filter_lists — "trovami eventi che si sovrappongono"Un attrezzo che mette in relazione due liste invece di una. Lo usi quando la domanda dell’utente incrocia due insiemi: «quali appuntamenti HLT si sovrappongono a quelli MNM nei prossimi tre mesi?», oppure «quali file sono presenti sia in questa cartella sia nell’altra?».
chiamata: filter_lists(op="overlap",
from_step=2, # lista A (eventi HLT)
with_step=3) # lista B (eventi MNM)
risposta: { ok: true,
op: "overlap",
entries: [...gli eventi di A che si sovrappongono ad almeno uno di B... ],
metadata: { count_a: 4, count_b: 5, count_out: 0 } }
Le operazioni disponibili sono cinque. intersect tiene
solo le entries presenti in entrambe le liste (il confronto avviene su
una chiave indicata, ad esempio il percorso del file).
union unisce le due liste eliminando i duplicati.
difference tiene le entries di A che non compaiono in B.
symdiff è la differenza simmetrica (quel che c’è
solo in A o solo in B). overlap è un’operazione
temporale: tiene gli eventi di A che cadono in un intervallo
sovrapposto a qualche evento di B, riconoscendo da solo i campi di
inizio e fine.
Le tre primitive che lavorano sulle liste hanno ruoli complementari e
ben separati: filter_entries riduce una lista applicando
un predicato a un campo (where_starts_with,
where_contains, where_glob,
where_regex); filter_lists combina due liste
con le operazioni di insieme appena viste; compute_entries
calcola un singolo numero a partire da una lista (somma, media,
minimo, massimo, conteggio). Le tre primitive coprono insieme la
quasi totalità delle manipolazioni che servono, senza dover
introdurre verbi nuovi.
send_messages — "manda un messaggio a Lucia"Spedisce uno o più messaggi via Telegram (oggi) o via email (in arrivo). Argomento principale: una lista di messaggi, ognuno con destinatario e testo.
chiamata: send_messages(messages=[
{to_user: "lucia", body: "Sono uscito, torno alle 7."}
])
risposta: { ok: true, ok_count: 1, fail_count: 0,
results: [{to_user: "lucia", channel: "telegram", message_id: "abc123"}] }
Un attrezzo "trasformativo": modifica il mondo, manda davvero un messaggio. Per questo
gli executor che cambiano qualcosa al mondo vengono trattati con più cautela: il loro
manifesto dichiara permessi più ampi (net:write), passano da un controllo
prima dell'esecuzione, e il loro effetto resta tracciato in modo da poterlo annullare.
Vedi vaglio per i controlli pre-esecuzione e
approval_ux per come si chiede conferma all'utente.
Questo documento è un'introduzione. Se vuoi capire i meccanismi che stanno sotto — come si firma un manifesto, come si applica il recinto, come il Synt genera il codice, come il pianificatore sceglie un attrezzo — i documenti seguenti vanno letti uno alla volta.
| Per capire… | Leggi |
|---|---|
| il pianificatore che sceglie l'executor giusto | agent_runtime |
| il recinto in dettaglio (forbidden paths, deroghe, «riordina le foto») | sandbox |
| come il Synt compone executor nuovi | synt |
| come si importa una skill esterna come executor | skill importer |
| il controllo che precede l'esecuzione di azioni rischiose | vaglio |
| come l'utente vede e approva le azioni | approval_ux |
| la memoria che le esecuzioni lasciano dietro di sé | mnest e mnestoma |
| il dialogo con il mondo (Telegram, web, voce) | channel |
| l'osservabilità (cosa è successo, perché, quando) | observability |
Per le decisioni di progetto (perché questa scelta e non quella) è tenuto un registro
in /opt/metnos/decisions/. Esempi: perché gli executor generati stanno in una cartella diversa
da quelli scritti a mano; come si chiede una password senza farla finire
in chiaro nei log; come si fa l'indicizzazione di trentamila foto senza
bloccare tutto il resto.
Metnos — executor, introduzione didattica