Un executor è un'unità operativa con argomenti, effetti, capacità e profilo di isolamento dichiarati in un manifest firmato. Il sistema mantiene distinta la provenienza degli executor distribuiti, generati e importati; il catalogo generato fornisce conteggi, domini e stato corrente senza duplicarli in questa pagina.

← Indice documentazione Guida all'architettura › executor

Metnos

executor — capire come è fatto
Guida introduttiva

Chiedi a Metnos con una richiesta come quella di questo esempio: «Cerca nella cartella Documenti tutti i PDF che contengono la parola contratto». Metnos può comporre più executor: uno trova i file, un altro ne legge il contenuto e un terzo filtra i risultati. L'utente descrive il risultato voluto; non deve conoscere i nomi degli executor.

Indice

  1. Cos'è un executor (in trenta secondi)
  2. Anatomia: manifest, firma e implementazione
  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. Quattro esempi concreti
  8. Per andare più a fondo

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

Un executor è una piccola unità operativa specializzata che fa una cosa sola: legge la posta, cerca un file, ottiene l'ora, manda un messaggio. Quelli eseguiti come sottoprocesso hanno una propria cartella; quelli interni al processo conservano lo stesso contratto logico.

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é gli executor sono piccoli

Più compiti riunisce uno strumento, più diventa difficile verificarne contratto, autorizzazioni ed effetti. Separare la cancellazione dei file dall'invio di posta rende ogni passaggio leggibile e controllabile. Quando Metnos compone più executor, il piano e ciascuna esecuzione conservano i propri controlli.

2. Anatomia: manifest, firma e implementazione

Gli executor distribuiti nel catalogo principale usano normalmente una cartella piatta con quattro file. Il contratto, però, non impone il numero quattro: il manifest può elencare più file di codice e gli executor interni al processo conservano il contratto firmato separato dall'implementazione.

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 struttura comune del catalogo distribuito
La struttura usata dagli executor distribuiti: manifest, firma, entry point e stato delle traduzioni. Il manifest resta la fonte autorevole per i file di codice firmati.

Per un executor attivo eseguito come sottoprocesso, manifest, firma e codice formano il nucleo. Il codice fa il lavoro; il manifest permette al pianificatore di valutarne il contratto; la verifica crittografica rileva modifiche successive alla firma.

FileCosa contieneChi lo legge
manifest.tomlNome, descrizione, argomenti, esempi, schema dell'output, capacità dichiarate e policy di esecuzioneil pianificatore (per scegliere), il loader (per caricare)
manifest.toml.sigFirma Ed25519 dei byte del manifest; il manifest contiene anche l'impronta dei file di codiceil loader, durante l'ammissione al catalogo
<entrypoint>.pyIl punto d'ingresso del sottoprocesso; altri file possono essere dichiarati in [code].filesil runtime, quando l'executor viene invocato
manifest.lang_state.jsonImpronte delle descrizioni per linguagli strumenti che mantengono allineate le traduzioni

Il loader non deduce il contratto dalla forma della cartella: legge il manifest, verifica standard, firma e impronta del codice, controlla l'entry point e soltanto allora ammette l'executor. I contratti degli executor interni al processo si trovano in runtime/builtin_executor_contracts/ e subiscono la stessa verifica crittografica.

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.

Quando un dato non deve essere attenuato dalla prosa generata, l'executor può restituire anche una authoritative_presentation con un ambito semantico chiuso: per esempio un conteggio esatto o un insieme di duplicati verificati. Il runtime usa questi frammenti soltanto se coprono tutti i passi produttivi del turno; negli altri casi conserva il normale compositore finale. Il limite di visualizzazione resta così distinto dal lavoro svolto: un frammento può dichiarare completa una scansione solo se l'executor attesta anche la completezza della sorgente. Il frammento sostituisce il normale avviso di troncamento soltanto quando questa composizione integrale riesce per l'intero turno: un passo coperto non può nascondere il limite di una pipeline che, nel suo insieme, non è coperta.

Gli identificativi possono inoltre richiedere un contesto di origine dichiarato nel manifest. Una lista può proiettare entries[*].uid tramite from_entries_key; proprietà scalari come account e cartella di origine possono dichiarare un from_entries_required condizionale. La proiezione accetta questi valori soltanto se tutte le righe concordano. Una chiamata diretta deve fornire esplicitamente lo stesso contesto applicabile; se manca, il punto unico di invocazione la rifiuta prima di qualunque effetto.

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.

[[capabilities]]
name = "provider:access"
hint = ["google-workspace"]
when = { arg = "client", values = ["google_workspace"] }

Per un backend remoto la clausola when restringe la dichiarazione alla singola invocazione. Solo quando il valore finale di client corrisponde, lo stesso binding abilita rete, home delle credenziali in lettura-scrittura e collocazione sul server. Un valore arbitrario in client non concede nulla.

Una sola politica di esecuzione

Ogni invocazione passa da uno scheduler centrale del runtime. Se la sezione [execution] manca, è incompleta o non è valida, il loader sceglie sempre la modalità seriale. Il parallelismo è quindi una proprietà opt-in del contratto firmato, ammessa soltanto dopo test ripetuti di equivalenza fra esecuzione seriale e concorrente.

ClasseSignificato portabileComportamento
0Nessun threadResta sul thread del chiamante.
1ModerataUsa una quota contenuta del pool centrale.
2AltaRichiede più concorrenza, entro i limiti della risorsa.
3MassimaÈ comunque limitata da hardware, backend e tetti globali.

All'avvio il runtime osserva le CPU visibili e il limite operativo max_workers, e stabilisce un solo tetto per l'istanza. La classe firmata è una riduzione di quel tetto, non un numero fisso di thread: il singolo executor può soltanto ridurre l'assegnazione in base alla quantità di lavoro o al profilo di I/O. Applica nello stesso punto code limitate, backpressure, pool per risorsa e metriche, senza cambiare argomenti, risultati, ordine causale, permessi o criteri di successo. Un executor non di sola lettura può dichiarare una classe positiva, ma deve anche fornire un'identità di concorrenza verificabile; invocazioni sulla stessa identità restano seriali.

Gli executor che usano un LLM seguono la stessa regola. All'avvio, framework e hardware determinano il tetto della risorsa LLM: un backend a singolo slot degrada a classe 0, mentre un backend con batching può ammettere più richieste. Non esistono pool concorrenti indipendenti nascosti nei singoli executor.

Le ricerche ricorsive sul filesystem usano un visitor comune: le directory formano una coda dinamica, i worker liberi prendono il prossimo ramo e i risultati vengono riordinati prima di applicare un limite. Per esempio, la ricerca dei duplicati confronta prima dimensione e campioni, poi calcola SHA-256 completo sui soli candidati; il limite di visualizzazione non riduce l'insieme confrontato.

Perché il contratto usa TOML

Il manifest deve poter essere letto, commentato e aggiornato durante una revisione. TOML conserva questa leggibilità senza rinunciare a una struttura che il loader può convalidare in modo rigoroso.

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

Il manifest dichiara le capacità massime richieste dall'executor. Prima dell'invocazione il runtime controlla gli argomenti concreti, risolve le sole risorse necessarie e applica i controlli di identità, Vaglio e policy. La sandbox è un ulteriore livello di contenimento; non concede autorizzazioni e non sostituisce quei controlli.

LivelloComportamento corrente sul server
Contrattocapabilities, collocazione, piattaforme e politica di esecuzione provengono dal manifest firmato.
Controlli applicativiGli argomenti possono restringere un ambito firmato, mai ampliarlo; identità, consenso e destinazione vengono verificati prima del sottoprocesso.
Bubblewrap attivoCodice e runtime sono in sola lettura, /tmp è privato, le risorse dati sono montate con l'accesso necessario e la rete viene separata quando nessuna capacità la richiede.
Bubblewrap assente o disabilitatoRestano i controlli applicativi, ma il comando gira direttamente: non va descritto come isolamento del sistema operativo.

La rete è attualmente binaria: quando serve, il processo eredita la rete dell'host; non viene applicata una lista di domini. Alcune radici di sistema, fra cui /etc, sono visibili in sola lettura. Per montaggi, eccezioni, declassamenti e differenze fra Linux, Windows e macOS, vedi la guida alla sandbox.

5. La vita di un executor

Il campo lifecycle separa i candidati dagli executor disponibili al pianificatore. Non descrive una sequenza obbligatoria per ogni origine: gli executor distribuiti e revisionati possono nascere direttamente come active, mentre i candidati di Synt attraversano i passaggi di preparazione e ammissione.

StatoSignificatoÈ in pool?
proposedMetadati di triage senza file di codice; se dichiara codice, il loader lo rifiuta.No; visibile soltanto alle superfici di lavoro e audit.
synthesizedCandidato con codice. Quando la verifica delle firme è attiva deve già superare firma, impronta ed entry point, ma non è ancora ammesso al composer.No; disponibile al percorso Synt.
activeContratto ammesso e visibile al pianificatore.Sì, salvo disabilitazione o dormienza per prerequisiti mancanti.
deprecatedEscluso dalle nuove composizioni; resta indicizzato in forma compatta per diagnosi e sostituzione.No.
archivedEscluso dal catalogo operativo; lo stato resta nel registro di durata.No.

L'invecchiamento automatico per inattività riguarda soltanto gli executor generati da Synt: dopo 30 giorni senza uso diventano deprecated e, dopo altri 14 giorni in quello stato, archived. Gli executor curati a mano, quelli importati come skill e i nomi protetti non vengono ritirati per il solo fatto di essere usati di rado. Le soglie sono configurabili e il ripristino richiede un'azione esplicita.

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

Tutti gli executor espongono lo stesso contratto logico, ma nascono in quattro modi diversi. La distinzione non è cosmetica: cambia chi li scrive, dove vivono e quale provenienza viene registrata.

Scritti a mano

L'autore li compone con pazienza. Sono il nucleo stabile, il seed da cui parte tutto.

Cartella: executors/ nell'installazione.

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/

Esempio: un candidato ristretto preparato, su richiesta governata, quando il catalogo non copre una capacità necessaria.

Tenuti separati: non possono mai fare ombra ai seed scritti a mano.

Importati da skill esterna

Una skill pubblica descrive l'uso di un servizio terzo. Il parser e la mappatura deterministica nel vocabolario chiuso la convertono 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 eseguiti nel processo. L'implementazione vive nel runtime e il contratto firmato resta separato.

Contratti: runtime/builtin_executor_contracts/

Esempi: admin, create_tasks, list_skills, describe_images.

Servizi del sistema, non attrezzi normali.

Precedenza del catalogo curato

Un executor generato o importato non può usare il nome di un executor scritto a mano. L'ammissione rifiuta la collisione; se questa viene rilevata durante il caricamento, il catalogo conserva l'executor curato e sposta la cartella sintetizzata in un'area temporanea recuperabile. Una generazione o una skill di terzi non può quindi sostituire silenziosamente il nucleo del catalogo.

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 executors/: gli executor importati vivono nella directory dati separata, sotto sorveglianza, e passano comunque dal vaglio prima di ogni chiamata.

Un dettaglio operativo: un executor che richiede credenziali resta dormant e viene escluso dal pool finché i prerequisiti non sono disponibili. Il flusso di configurazione può raccogliere i dati con un dialogo e conservarli cifrati; non è corretto promettere che qualunque prima chiamata possa sempre proseguire da sola. Vedi la guida allo skill importer per i casi ammessi.

7. Quattro esempi concreti

Vediamo quattro 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 sei. 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. delta individua invece elementi nuovi o avanzati rispetto a una lista di riferimento e viene usato nei monitor incrementali.

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 un familiare"

Spedisce uno o più messaggi via Telegram o email. 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 le capacità mail:send e channel:out, e la destinazione viene risolta rispetto all'utente e al canale associati. L'invio è tracciato, ma non è annullabile: il manifest dichiara infatti revertible=false. I controlli devono quindi precedere la consegna. 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
quali modelli (LLM, embedding, VLM) muovono gli executor e come si cambiano da un TOMLvirtualizzazione dei modelli
l'osservabilità (cosa è successo, perché, quando)observability

Non occorre leggere queste guide in ordine. Parti dalla domanda concreta, segui i collegamenti pertinenti e fermati quando hai raggiunto il livello di dettaglio che ti serve.


Metnos — executor, introduzione didattica