Allineata al codice. Il pool conta 79 executor fra scritti a mano in /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).
← Indice documentazione Microprogettazione › executor

Metnos

executor — capire come è fatto
Introduzione didattica
Per chi vuole capire, non per chi deve implementare.

Lettura: dieci minuti.

Indice

  1. Cos'è un executor (in trenta secondi)
  2. Anatomia: cinque cose nella cartella
  3. Il manifesto: il biglietto da visita
  4. Il recinto: cosa può fare e cosa no
  5. La vita di un executor
  6. Le quattro origini: a mano, generato, importato, di sistema
  7. Tre esempi concreti
  8. Per andare più a fondo

1. Cos'è un executor (in trenta secondi)

Un 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.

argomenti {tz: "Europe/Rome"} get_now una cosa sola: restituisce data e ora manifesto firma risultato 2026-05-06 16:45 cosa chiedo l'attrezzo che esegue cosa ricevo
Un executor è una scatola con un compito preciso, un contratto in entrata e uno in uscita.

Le tre cose che lo definiscono sono:

Perché così piccoli? Più un attrezzo fa cose, più è difficile fidarsi di lui. Se un executor cancella i file e manda mail, non lo si può usare in un workflow automatico senza paura. Tagliando per "una cosa sola", Metnos può comporli a piacere senza temere effetti a sorpresa.

2. Anatomia: cinque cose nella cartella

Un executor è una cartella sul disco. Dentro ci sono al massimo cinque file, ognuno con un mestiere. Niente di più.

get_processes/ manifest.toml il biglietto da visita: nome, cosa fa, argomenti, esempi, schema dell'output manifest.toml.sig la firma crittografica del biglietto da visita: dimostra chi l'ha scritto get_processes.py il codice vero e proprio: una funzione invoke(args) che esegue il compito manifest.lang_state.json stato di traduzione (multilingua): solo se ci sono descrizioni in più lingue cinque file, niente di più
La cartella di un executor: tre file obbligatori (manifesto, firma, codice), uno facoltativo per le traduzioni, tutti piatti dentro la cartella.

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.

FileCosa contieneChi lo legge
manifest.tomlNome, descrizione, argomenti, esempi, schema dell'output, profilo del recintoil pianificatore (per scegliere), il loader (per caricare)
manifest.toml.sigFirma Ed25519 del manifesto in byteil loader, all'avvio del catalogo
<nome>.pyUna funzione invoke(args) che fa il lavoroil runtime, quando l'attrezzo viene chiamato
manifest.lang_state.jsonHash delle descrizioni per lingua (per il sistema di traduzione)il daemon di traduzione notturno
Niente sotto-cartelle, niente pacchetti Python con import incrociati, niente requirements.txt. Un executor sta in una cartella piatta. La libreria che usa è quella standard di Python o quella già installata nel sistema. Più isolato è, più facile è firmarlo, caricarlo, eseguirlo, spostarlo su un'altra macchina. Vedi sandbox per il dettaglio.

3. Il manifesto: il biglietto da visita

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.

name = "get_now" version = "0.1.0" affinity = ["ora", "data", "now", "time"] [description] it = "Restituisce data e ora correnti." [args.properties.tz] type = "string" default = "UTC" [output] schema_inline = "{ ok: bool,... }" [code] files = ["get_now.py"] digest = "sha256:e6e609f6..." [[capabilities]] name = "time:read" identità parole-chiave aiuta il pianificatore a trovarlo cosa fa una frase chiara, tradotta nelle lingue argomenti tipo, default, descrizione forma del risultato cosa esce dall'attrezzo, campo per campo codice + impronta il digest cambia se il codice viene toccato permessi cosa può toccare
Un manifesto reale, semplificato. Ogni blocco serve a una funzione chiara: identità, parole-chiave per essere trovato, descrizione, argomenti, forma del risultato, codice con impronta, permessi.

Cosa c'è di importante

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.

Perché TOML e non JSON? Il TOML è pensato per essere letto e scritto a mano, anche con commenti. Un manifesto è un documento di lavoro: l'autore lo scrive, qualcuno lo rivede, ogni tanto va aggiornato. JSON sarebbe più rigido e meno leggibile.

4. Il recinto: cosa può fare e cosa no

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.

off limits per tutti (chiavi, /etc/shadow,...) area di sola lettura concessa dal manifesto workspace: legge e scrive qui codice invoke(args) più ti allontani dal centro, meno cose puoi fare
Il recinto come anelli concentrici. Al centro il codice. Subito intorno la zona di lavoro (legge e scrive). Più fuori, cose che può solo leggere. All'esterno, ciò che è off limits per tutti, anche per chi avesse i permessi giusti.

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.

CosaDove sta scritta
Cosa l'executor vorrebbe poter farenel manifesto, blocco capabilities
Cosa davvero può fare nel turno correntenel recinto applicato dal sandbox al momento dell'invocazione
Cosa nessuno può fare, mainelle "regole del nucleo", non negoziabili
Esempio pratico. Se un executor che dovrebbe leggere PDF di fatture cercasse di aprire /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.

5. La vita di un executor

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.

proposto solo manifesto sintetizzato codice + test verdi attivo in pool, firmato in quarantena recuperabile entro 24h archiviato fuori dal pool recupero solo i sintetizzati passano da qui passa quando il loader li approva
Il ciclo di vita di un executor. La maggior parte dei seed (scritti a mano) parte direttamente da attivo; quelli generati al volo dal Synt passano prima da proposto e sintetizzato. Da attivo si esce per quarantena (ad esempio se un altro executor lo rimpiazza); dopo 24 ore senza recupero, archiviazione definitiva.
StatoSignificatoÈ in pool?
propostoesiste solo il manifesto, manca il codiceno
sintetizzatocodice presente e test verdi, in attesa di firmano
attivofirmato e visibile al pianificatore
in quarantenarimpiazzato da un altro o sospetto, ma recuperabilevisibile in audit, escluso dalle nuove scelte
archiviatofuori dal sistema; resta solo per l'audit storicono
Perché la quarantena e non l'eliminazione diretta? Perché capita che un executor nuovo che doveva sostituirne uno vecchio si riveli imperfetto. Tenendo il vecchio per ventiquattr'ore in panchina, si può tornare indietro senza fatica. È un meccanismo di sicurezza analogo al cestino del file manager.

6. Le quattro origini: a mano, generato, importato, di sistema

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.

Scritti a mano

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.

Generati al volo

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.

Importati da skill esterna

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.

Di sistema (builtin)

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 regola che salva da mille grattacapi. Un executor non scritto a mano — che sia generato o importato — non può mai prendere lo stesso nome di uno scritto a mano. Se ci prova, il loader lo rifiuta e lo sposta in una cartella di scarto. Così il nucleo curato a mano non viene mai inghiottito da una composizione automatica né da una skill di terzi.

Perché l'origine importata esiste

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.

7. Tre esempi concreti

Vediamo tre executor realmente in uso, raccontati da fuori. Niente codice sorgente: solo cosa chiedi e cosa ottieni.

7.1 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.

7.2 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.

7.3 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.

7.4 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.

8. Per andare più a fondo

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 giustoagent_runtime
il recinto in dettaglio (forbidden paths, deroghe, «riordina le foto»)sandbox
come il Synt compone executor nuovisynt
come si importa una skill esterna come executorskill importer
il controllo che precede l'esecuzione di azioni rischiosevaglio
come l'utente vede e approva le azioniapproval_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.

Il consiglio. Non leggere questi documenti in ordine. Parti dalla domanda concreta che ti interessa, segui i collegamenti, e fermati quando hai capito quello che ti serviva. Il corpus è pensato per la consultazione, non per la lettura lineare.

Metnos — executor, introduzione didattica