Quale “libro” per il nostro agente?
C’è una differenza fondamentale tra dare a un agente un manuale e dargli un vocabolario.
Un manuale si legge dall’inizio alla fine. Contiene tutto. È lineare, in prosa, organizzato per come l’ha scritto l’autore. Un agente che legge AGENTS.md prima di ogni sessione di lavoro ottiene una panoramica completa, ma quella conoscenza non è strutturata per rispondere a domande specifiche nel momento in cui servono.
Un vocabolario è diverso. Non si legge, si interroga. Hai un problema preciso (“Come presento il team in questa pagina?”) e il vocabolario ti dà la risposta pertinente, con i vincoli, con gli esempi, senza che tu debba ricordare dove era scritto nel manuale.
Questa è la distinzione che ha guidato la progettazione di catalog.yaml in Websmith, il nostro CMS agentico. Il file non è documentazione: è la grammatica attraverso cui l’agente parla con il design system.
Perché le regole in prosa non bastano
Nel primo articolo di questa serie ho descritto come AGENTS.md funzioni come contratto operativo condiviso tra tutti gli agenti che lavorano sul repository. È un approccio che funziona bene per le regole di processo: come aggiornare le sorgenti, come propagare le traduzioni, quando fermarsi e chiedere invece di procedere. Ma per i componenti visivi, le regole in prosa hanno un limite strutturale.
Consideriamo questa situazione reale. Un collaboratore chiede: “Aggiungi una sezione che presenta le persone del team virtuale ARchetipo”. L’agente sa che esistono componenti. Ne conosce i nomi perché li ha visti in altri file markdown. Ma quale usa?
- team-avatar? Esiste, il nome sembra giusto.
- team-member-list? Anche questo riguarda il team.
- griglia-celle con item di tipo team-avatar? Forse.
- collage-box? Tecnicamente mostra elementi in griglia.
Senza contesto semantico, l’agente sceglie in base alla probabilità, che è un modo elegante per dire che tira a indovinare. E quando indovina componenti visivi, il risultato è output che visivamente sembra plausibile ma è architetturalmente sbagliato: team-member-list è per la pagina Chi Siamo dei membri reali dell’azienda, non per le personas virtuali di un progetto. Oppure il risultato che si ottiene non è coerente con il resto del sito prodotto in precedenza.
La struttura del catalogo
Nella soluzione a cui sto lavorando, il CMS agentico appunto, abbiamo pensato alla creazione di un catalogo di componenti (descritti in catalog.yaml) che risolve il problema di quale componente utilizzare tramite una struttura interrogabile per ogni componente. Non si tratta solo di elencare i campi accettati: questo lo fa già la documentazione nei file HTML WYSIWYG. Il catalogo aggiunge la dimensione semantica: perché esiste questo componente, quando usarlo, quando non usarlo.
Ogni voce ha questa forma:
- name: team-avatar category: team owned_by: markdown purpose: > Avatar card for a person or virtual role: photo, name, role label, short bio and skill tags. Cards are designed for uniform height in a grid. when_to_use: > Use inside griglia-celle to present team members or project virtual personas. Each card = one person. do_not_use_when: > Presenting real company team members on the Chi Siamo page — use team-member-list instead, which has a different layout. fields: required: [name, image, role, body, skills] optional: []
La coppia when_to_use / do_not_use_when è la parte più utile. Non descrive cosa fa il componente, ma descrive in quale contesto ha senso. Con questa informazione, l’agente può rispondere alla domanda sulle personas di un progetto cercando componenti con when_to_use che menzioni “virtual personas” o “Project”, trovare team-avatar, leggere che va dentro griglia-celle, e costruire la sezione corretta.
Come nasce una pagina: decisioni e responsabilità
Dovendo identificare uno degli elementi più utile del catalogo, potremmo scegliere il campo owned_by. Per capire cosa fa, bisogna prima capire come nasce una pagina.
Il renderer prende due ingredienti:
- il template HTML: una pagina già completa, con menu in alto, un hero vuoto, uno spazio centrale, un footer in fondo;
- il file Markdown: i contenuti da inserire.
E li combina. La domanda è: per ogni pezzo della pagina, cosa deve scrivere l’agente nel file Markdown? owned_by risponde a questa domanda, e i casi possibili sono tre.
Caso 1: menu e footer
L’agente non scrive niente. Il menu è identico su tutte le pagine. È già nel template, finito. L’agente non deve fare nulla: né scriverlo, né toccarlo.
owned_by: template = “non ti riguarda, è già a posto”
Caso 2: lo hero
L’agente scrive solo i testi. Anche lo hero è già nel template come struttura (la sezione colorata in alto esiste già, con un titolo segnaposto). Ma i testi cambiano da pagina a pagina. Quindi l’agente deve fornirli al renderer, e li mette nel frontmatter.
--- title: La mia pagina hero: title: Software Development potenziato dall'AI subtitle: Da 15+ anni aiutiamo i team... ---
Il renderer prende questi testi e li infila nello hero che già esiste nel template. L’agente decide le parole, non la forma né la posizione.
owned_by: frontmatter = “scrivi solo i testi, in questo punto preciso”
Caso 3: i contenuti della pagina
L’agente scrive tutto. La parte centrale della pagina invece è vuota nel template. Qui l’agente decide liberamente: quali sezioni, in che ordine, quante. Le scrive nel corpo del markdown.
<!-- component: highlights-box-sx --> title: I nostri servizi ... <!-- /component --> owned_by: markdown = “campo libero: scegli tu cosa, dove e quanto”
I tre casi sono sintetizzati nella tabella 1.

Dove va dichiarato owned_by
Un chiarimento importante: owned_by non compare mai nei file Markdown di contenuto. Vive solo nella libreria dei componenti, cioè in catalog.yaml: una voce per ogni componente, scritta una volta sola.
templates/html/components/catalog.yaml ← qui, una volta per componente contents/ita/pagine/index.md ← qui NON compare mai
Ecco una voce reale dal catalogo:
- name: menu-bar category: navigation owned_by: template # ← eccolo purpose: Main navigation bar with logo, nav links and language switcher. when_to_use: Never declared in Markdown. Lives in the page template.
e per un componente di contenuto:
- name: team-avatar category: team owned_by: markdown # ← eccolo purpose: Avatar card for a person... fields: required: [name, image, role, body, skills]
È metadato descrittivo, non configurazione: né il renderer né il file di contenuti lo leggono. Lo legge l’agente, prima di scrivere.
Qualche esempio
Scenario: chiedi a un agente “Aggiungi la barra di navigazione alla pagina prodotti”. L’agente, prima di toccare il file .md, consulta il catalogo:
- cerca menu-bar nel catalogo
- trova owned_by: template
- conclusione: “non devo scrivere nulla nel Markdown; il menu è già nel template” → si ferma e lo dice, invece di aggiungere <!– component: menu-bar –> nel file
Stesso scenario con “Aggiungi una card per Marta al team”:
- Cerca team-avatar → trova owned_by: markdown
- Conclusione: “OK, questo lo scrivo io nel corpo del file .md” → procede
Il file di contenuti resta pulito: contiene solo <!– component: team-avatar –> e i suoi campi. owned_by è la scheda informativa che dice all’agente se può scrivere quel componente e dove: sta nel catalogo perché è una proprietà del componente, non della singola pagina.
Prima del catalogo, questa distinzione viveva implicitamente in AGENTS.md come regola di prosa:
“Template contains: WYSIWYG page structure, menu/footer where they are fixed.”
È una regola che un agente legge, comprende, e poi applica correttamente il 60-70% percento delle volte. Il restante 30% emerge in situazioni di contorno: pagine nuove, componenti mai usati prima, sessioni dove il contesto rilevante è finito fuori dalla finestra del contesto.
Con owned_by nel catalogo, quella conoscenza è interrogabile ogni volta che l’agente sta per piazzare un componente. Non deve ricordare la regola, deve solo leggere il campo.
Il problema delle gerarchie di componenti
Un aspetto del design system che il catalogo ha reso esplicito è la gerarchia tra componenti container e componenti foglia.
griglia-celle è un container. Non ha contenuto proprio: ospita altri componenti come item. Ma quali componenti hanno senso dentro una griglia? Non tutti. highlights-box-sx gestisce il proprio layout interno e non va “wrappato” in griglia-celle. E lo stesso vale per collage-box. Ma fase-processo e team-avatar sono progettati specificamente per stare in una griglia.
Il ruolo di ogni componente nel layout
L’approccio che abbiamo scelto è derivare la compatibilità da una proprietà di design del singolo componente, il campo layout_role. Ogni componente dichiara il proprio ruolo nel layout della pagina, con tre valori possibili:
- name: griglia-celle category: layout owned_by: markdown layout_role: container # ospita altri componenti come item - name: team-avatar category: team owned_by: markdown layout_role: embedded # progettato per stare dentro un container - name: highlights-box-sx category: content owned_by: markdown layout_role: self # gestisce il proprio layout, sta da solo
La regola di composizione diventa una sola, e vale per sempre: un container accetta come item solo componenti embedded; un componente self non va mai wrappato.
Come evolve il design system
Il vantaggio si vede quando il design system evolve. Chi crea un componente nuovo deve rispondere a una sola domanda — “È una cella da griglia, una sezione autonoma, o un contenitore?” — che è una domanda a cui deve comunque rispondere per progettarlo. Risposta data, layout_role assegnato, e la compatibilità con tutti i container presenti e futuri segue da sola. Nessuna lista da tenere sincronizzata, nessuna voce di container da ricordarsi di aggiornare.
Prima del catalogo, questa conoscenza viveva negli esempi in AGENTS.md e nella memoria contestuale della sessione. Il campo layout_role la rende esplicita, persistente e, soprattutto, locale al componente che la possiede.
Cosa è cambiato in pratica
Il catalogo è in uso da circa due mesi. La differenza più concreta non è che gli agenti non fanno mai errori sui componenti, ma è che gli errori che fanno sono diversi.
Prima del catalogo si trattava soprattutto di errori di categoria: usare team-member-list invece di team-avatar, wrappare componenti che gestiscono il proprio layout, inserire direttive hero nel corpo del documento.
Dopo il catalogo si tratta in massima parte di errori di dettaglio. Un campo obbligatorio mancante, un’immagine con path invece di solo il nome del file, un numero di colonne fuori range. Errori che il validator rileva e che richiedono una correzione di una riga, non una riscrittura della sezione.
C’è anche una categoria di errori che è quasi scomparsa: i componenti inventati. Prima capitava occasionalmente che un agente, non trovando un componente adatto, ne creasse uno con sintassi verosimile ma inesistente. Il renderer falliva, il problema era difficile da diagnosticare perché l’output del renderer non diceva “Componente inesistente” ma diceva qualcosa di più criptico sulla struttura del DOM.
La regola nel catalogo che ha eliminato questo comportamento è nel commento in cima al file:
# If no component seems right, stop and report instead of inventing a substitute.
Non è una regola nuova, visto che era già in AGENTS.md. Ma averla nel catalogo, nel file che l’agente consulta quando sta scegliendo un componente, la rende operativa nel momento giusto.
Cosa non funziona ancora
Il catalogo risolve la scoperta dei componenti. Non risolve l’evoluzione del design system.
Quando un componente cambia — nuovi campi opzionali, un campo rinominato, un vincolo che non vale più — il catalogo va aggiornato a mano e bisogna sincronizzare i file HTML dei componenti o il codice del renderer. La coerenza tra le tre fonti (HTML WYSIWYG, renderer Python, catalog.yaml) è responsabilità che presto verrà sviluppata in un agente.
Questo è accettabile finché il design system è stabile. Diventa un problema quando il sistema evolve velocemente o quando componenti vecchi restano nel catalogo ma non dovrebbero più essere usati.
La gestione della deprecazione è il gap più visibile. Oggi il renderer mantiene la compatibilità con sintassi legacy di componenti vecchi: ci sono pattern di parsing per nomi e strutture che non andrebbero più usati, ma che esistono per non rompere pagine già generate. Il catalogo non ha ancora un campo deprecated: true con un campo use_instead che dica all’agente “Questo componente esiste ancora ma non aggiungerlo mai in pagine nuove”.
È una lacuna che pianifichiamo di colmare. La struttura YAML la rende semplice da risolvere: si tratta di decidere il modo in cui il campo dovrebbe influenzare il comportamento dell’agente.
Quello che il catalogo non può fare
Un catalogo di componenti risponde alla domanda “Quale componente uso qui?”. Non risponde alla domanda “Come si struttura questa pagina?”.
La differenza è importante. Sapere che fase-processo va dentro griglia-celle è utile. Ma decidere come ordinare le sezioni di una pagina prodotto — prima gli highlights, poi il processo, poi il team, poi i contatti — richiede conoscenza dell’architettura informativa, non del design system.
Quella conoscenza oggi vive in COMPOSITION_GUIDE.md: pattern di composizione per tipo di pagina, ordine raccomandato delle sezioni, quali componenti funzionano bene insieme a livello di pagina. È documentazione in prosa, non un catalogo strutturato.
La domanda che teniamo aperta è se e quanto di quella conoscenza si presta alla stessa formalizzazione del catalogo componenti. Un file page-types.yaml con pattern di composizione per pagine prodotto, pagine team, landing page potrebbe fare per le architetture di pagina quello che il catalogo fa per i singoli componenti.
Non l’abbiamo costruito ancora. La ragione è pragmatica: il design system dei componenti cambia raramente, i pattern di composizione cambiano spesso. Formalizzare conoscenza che evolve frequentemente crea un costo di manutenzione che potrebbe superare il beneficio.
Ma è un esperimento che vale la pena fare.
Il prossimo articolo di questa serie lo affronta da un’angolazione diversa: non come formalizzare le intenzioni di composizione, ma come insegnare a un agente a distinguere tra intenzioni editoriali di diverso peso, e perché questa distinzione determina quanto un agente può agire in autonomia prima di dover chiedere.
Nota
Il codice di Websmith al momento non è pubblico. Il file catalog.yaml come descritto in questo articolo riflette l’implementazione attuale del design system.
Domande e commenti possono essere inviati via email a info@techreloaded.it
