Vai al contenuto principale

#net

Un agente AI è un sistema distribuito, non un chatbot: pattern di durable orchestration in .NET

Negli ultimi mesi molti team hanno scoperto a proprie spese che il problema più grande di un agente AI in produzione non è la qualità del prompt, ma l

Altro...

Negli ultimi mesi molti team hanno scoperto a proprie spese che il problema più grande di un agente AI in produzione non è la qualità del prompt, ma la sua affidabilità operativa. Un articolo recente su DZone, “Your AI Agent Is a Distributed System, Not a Chatbot”, mette a fuoco un punto che chi progetta sistemi backend conosce bene da anni: se un componente deve sopravvivere a crash, timeout, retry e attese umane di ore, non puoi trattarlo come una semplice chiamata sincrona a un modello. Devi trattarlo come un sistema distribuito, con tutto ciò che questo comporta: stato persistente, checkpoint, idempotenza e recovery.

In questo articolo riprendiamo quei concetti e li caliamo nella pratica con .NET, mostrando come Azure Durable Functions (o alternative come Temporal) permettano di costruire agenti AI multi-step che non perdono lo stato quando qualcosa va storto — cosa che, in produzione, prima o poi succede sempre.

Perché un chatbot stateless non bastaUn chatbot classico è un ciclo request/response: l’utente scrive, il modello risponde, fine. Un agente AI enterprise, invece, tipicamente deve:

orchestrare più chiamate a modelli e strumenti in sequenza o in parallelo;

interrogare sistemi esterni (CRM, ticketing, knowledge base, database);

attendere l’approvazione di un umano, che può richiedere minuti oppure ore;

gestire fallimenti parziali senza dover ripartire da zero;

garantire che un’azione con effetti collaterali (un rimborso, un invio email, una scrittura su un sistema esterno) non venga eseguita due volte per colpa di un retry.

Nessuno di questi requisiti è nuovo: sono gli stessi problemi che i sistemi distribuiti risolvono da decenni con pattern come saga, checkpointing e idempotency key. La differenza è che oggi il “servizio downstream” spesso è un LLM, e la latenza dominante non è più quella di rete, ma quella dell’attesa umana.

Durable orchestration: lo stato che sopravvive al crashIl pattern architetturale proposto è un runtime di orchestrazione durevole, che mantiene lo stato del workflow indipendentemente dal ciclo di vita del processo che lo esegue. In .NET, l’implementazione più diretta è Azure Durable Functions, che introduce tre concetti chiave:

Orchestrator function: decide “cosa succede dopo”, applica le retry policy e coordina le chiamate, ma non esegue lavoro con effetti collaterali direttamente;

Activity function: esegue il lavoro reale (chiamata al modello, query, side effect) ed è il livello dove si applica l’idempotenza;

Checkpointing automatico: dopo ogni await, il runtime salva lo stato dell’orchestrazione, così un crash del processo non fa perdere il progresso già fatto.

Un’alternativa nota, citata anche nell’articolo originale, è Temporal, che applica lo stesso principio con un modello di programmazione simile ma un runtime a sé stante, spesso preferito in contesti multi-linguaggio o Kubernetes-native.

Il pattern fan-out/fan-in per agenti multipliQuando un task richiede il contributo di più agenti specializzati (per esempio: un agente diagnostico, uno di ricerca sulla knowledge base, uno che consulta lo storico dei casi e uno che verifica le policy aziendali), il pattern corretto è il fan-out/fan-in: l’orchestratore lancia tutte le attività in parallelo e aggrega i risultati solo quando sono tutte terminate.

Ecco un esempio realistico in C# con il modello isolato di Azure Functions, ispirato agli esempi ufficiali Microsoft:

[Function("SupportInvestigationOrchestrator")]
public static async Task<InvestigationResult> Run(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var caseId = context.GetInput();

// Fan-out: 4 agenti specializzati lanciati in parallelo
var diagnosticTask = context.CallActivityAsync<AgentFinding>(
    "RunDiagnosticAgent", caseId);
var knowledgeTask = context.CallActivityAsync<AgentFinding>(
    "RunKnowledgeSearchAgent", caseId);
var historyTask = context.CallActivityAsync<AgentFinding>(
    "RunHistoricalCaseAgent", caseId);
var policyTask = context.CallActivityAsync<AgentFinding>(
    "RunPolicyAgent", caseId);

// Fan-in: si attende che tutti i task completino
AgentFinding[] findings = await Task.WhenAll(
    diagnosticTask, knowledgeTask, historyTask, policyTask);

// Sintesi della raccomandazione finale
var recommendation = await context.CallActivityAsync<Recommendation>(
    "SynthesizeRecommendation", findings);

return new InvestigationResult(caseId, findings, recommendation);

}Il vantaggio rispetto a un semplice Task.WhenAll in un servizio stateless è che, se il processo host crasha mentre due agenti su quattro hanno già risposto, l’orchestrazione riparte dal checkpoint e non richiama gli agenti già completati.

Human-in-the-loop senza tenere aperta la computeIl collo di bottiglia più comune non è il modello, ma l’attesa di un’approvazione umana. Un workflow che tenesse una funzione serverless “in ascolto” per ore sarebbe insostenibile in termini di costo e di timeout. Il pattern corretto è sospendere l’orchestrazione in attesa di un evento esterno:

[Function("SupportInvestigationOrchestrator")]
public static async Task<InvestigationResult> Run(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
// ... fan-out/fan-in come sopra ...

await context.CallActivityAsync("NotifyHumanReviewer", recommendation);

// Il workflow si sospende senza consumare risorse compute
// fino a quando l'evento non arriva, anche dopo diverse ore
var decision = await context.WaitForExternalEvent<HumanReviewDecision>(
    "HumanReviewCompleted");

if (decision.Approved)
{
    await context.CallActivityAsync("ApplyResolution", recommendation);
}

return new InvestigationResult(caseId, findings, recommendation, decision);

}L’evento viene “consegnato” all’orchestrazione in pausa tramite una chiamata separata (per esempio da una Function HTTP-triggered collegata a un pulsante “Approva” nella UI), e il runtime si occupa di riprendere l’esecuzione esattamente dal punto in cui era stata sospesa.

Idempotenza: il vero rischio nascostoUn runtime durevole garantisce tipicamente semantica at-least-once: dopo un crash, un’attività può essere rieseguita. Questo va benissimo per operazioni pure (una query di lettura), ma è pericoloso per operazioni con side effect (un rimborso, un invio email, una scrittura irreversibile). Il momento critico è il cosiddetto crash window: l’intervallo tra il completamento effettivo di un’azione e il salvataggio del suo checkpoint.

La soluzione è associare a ogni operazione critica una idempotency key deterministica, così che una riesecuzione accidentale venga riconosciuta e ignorata a livello applicativo:

[Function("ApplyGoodwillRefund")]
public static async Task ApplyGoodwillRefund(
[ActivityTrigger] RefundRequest request)
{
string idempotencyKey = $"{request.CaseId}:goodwill-refund";

if (await _paymentService.WasAlreadyProcessedAsync(idempotencyKey))
{
    return; // operazione già eseguita, nessun effetto collaterale duplicato
}

await _paymentService.ProcessRefundAsync(request, idempotencyKey);

}Questo pattern, ben noto a chi lavora con gateway di pagamento, va applicato sistematicamente a ogni activity che tocchi sistemi esterni non idempotenti per natura.

Successo parziale, non tutto-o-nienteSe uno dei quattro agenti fallisce (per esempio, il servizio di knowledge search va in timeout), il sistema non dovrebbe far fallire l’intera indagine. Meglio trattare i risultati come esiti strutturati, distinguendo tra “successo”, “fallito” e “degradato”, e permettere che la sintesi finale proceda comunque, segnalando esplicitamente quali fonti mancano:

public record AgentFinding(
string AgentName,
AgentStatus Status, // Success, Failed, Degraded
string? Result,
string? FailureReason);Questo approccio “graceful degradation” è preferibile a un fallimento totale che obbliga a rieseguire da capo un’indagine costosa in termini di tempo e token consumati.

Osservabilità come requisito di prodottoIn un sistema dove un’indagine può durare ore e coinvolgere quattro o più agenti, la tracciabilità non è un dettaglio tecnico: è parte dell’esperienza utente e, in molti contesti regolamentati, un requisito di audit. Vale la pena tracciare sistematicamente:

workflow instance ID, correlation ID e case ID;

lo stage corrente e la durata di ogni singolo agente;

il numero di retry e il motivo di ogni fallimento;

la latenza di revisione umana (spesso il fattore dominante);

la tracciabilità delle evidenze usate per la raccomandazione finale, per scopi di audit.

ConclusioneIl messaggio di fondo è semplice ma spesso trascurato: il workflow conta più del prompt. Un agente AI ben progettato non è quello con il prompt più raffinato, ma quello costruito su un runtime capace di sopravvivere a crash, gestire attese umane di ore senza sprecare risorse, ed evitare effetti collaterali duplicati. Per chi lavora nello stack .NET, Azure Durable Functions offre oggi gli strumenti necessari per applicare questi pattern senza dover reinventare un motore di orchestrazione da zero; per contesti poliglotta o già containerizzati, Temporal resta un’alternativa solida con la stessa filosofia.

Fonte originale: “Your AI Agent Is a Distributed System, Not a Chatbot” su DZone.

#ai #ia #microsoft #c #net #azure #dev

0 0 1

Crittografia post-quantistica: perché il threat modeling non può più aspettare (con esempi in .NET 10)

C’è una minaccia alla crittografia moderna che non richiede un computer quantistico funzionante oggi per essere reale oggi: si chiama “harvest now, de

Altro...

C’è una minaccia alla crittografia moderna che non richiede un computer quantistico funzionante oggi per essere reale oggi: si chiama “harvest now, decrypt later” (raccogli ora, decifra dopo). Un attaccante con risorse sufficienti — uno stato-nazione, tipicamente — può intercettare e archiviare traffico cifrato con RSA o ECC adesso, per poi decifrarlo tra qualche anno, quando computer quantistici sufficientemente potenti saranno disponibili. Per dati con un ciclo di vita lungo — segreti industriali, cartelle cliniche, comunicazioni diplomatiche, chiavi di firma di lungo periodo — la finestra di esposizione è già aperta, anche se il computer quantistico “rompi-RSA” non esiste ancora.

È in questo contesto che Microsoft ha recentemente pubblicato una guida al threat modeling applicato specificamente alla migrazione verso la crittografia post-quantistica (PQC), invitando organizzazioni e team di sviluppo a non aspettare la disponibilità degli algoritmi per cominciare a muoversi.

Perché serve il threat modeling, e non solo un elenco di algoritmiIl problema centrale che Microsoft evidenzia non è tecnico in senso stretto, è organizzativo: la maggior parte delle aziende non ha un inventario completo di dove e come viene usata la crittografia nei propri sistemi. Certificati TLS, librerie di firma incorporate in applicazioni legacy, hardware embedded con chiavi hard-coded, protocolli proprietari che usano RSA “perché si è sempre fatto così” — sono tutte dipendenze crittografiche nascoste che un aggiornamento generico non intercetta.

Il threat modeling applicato alla PQC serve esattamente a questo: mappare asset, flussi di dati, confini di fiducia (trust boundary) e controlli di sicurezza esistenti per far emergere queste dipendenze prima che diventino un problema urgente sotto pressione regolatoria o, peggio, sotto attacco. Concretamente, significa rispondere a domande come: quali sistemi cifrano dati che devono restare confidenziali per più di 5-10 anni? Quali certificati di firma del codice hanno una validità che si estende oltre il 2030? Quali protocolli interni non supportano l’agilità crittografica, cioè non permettono di sostituire un algoritmo senza riscrivere l’applicazione?

Gli algoritmi: da NIST a nomi che iniziano a essere familiariDopo anni di standardizzazione, il NIST ha pubblicato tre standard che è ormai il momento di conoscere, perché stanno entrando nei prodotti che usiamo quotidianamente:

ML-KEM (Module-Lattice Key Encapsulation Mechanism, FIPS 203) — sostituisce lo scambio di chiavi basato su RSA o curve ellittiche (ECDH). È l’algoritmo che entra in gioco nell’handshake TLS per stabilire un segreto condiviso.

ML-DSA (Module-Lattice Digital Signature Algorithm, FIPS 204) — l’algoritmo di firma digitale post-quantistico, pensato come sostituto di RSA e ECDSA per firmare certificati, codice e messaggi.

SLH-DSA (Stateless Hash-Based Digital Signature Algorithm, FIPS 205) — un algoritmo di firma alternativo, basato su funzioni hash anziché su reticoli, pensato come backup conservativo nel caso in cui in futuro emergano debolezze crittanalitiche nei problemi su reticolo.

Sul fronte delle raccomandazioni pratiche, Microsoft insiste su un punto spesso sottovalutato: la migrazione a TLS 1.3 è il prerequisito, non un dettaglio. TLS 1.2 non supporta i meccanismi ibridi necessari per introdurre ML-KEM accanto agli algoritmi classici, e restare su TLS 1.2 significa restare bloccati fuori dalla transizione PQC anche quando gli algoritmi saranno pronti. In parallelo, si consiglia di alzare l’asticella anche sulla crittografia simmetrica e sulle funzioni hash, adottando AES-256 e SHA-384 come standard, per mantenere margini di sicurezza adeguati anche in scenari quantistici (dove alcuni attacchi, come Grover, dimezzano di fatto la sicurezza effettiva degli algoritmi simmetrici).

La timeline che Microsoft si è data per la transizione dei propri prodotti e servizi critici è il 2029 — una data che vale la pena tenere a mente come riferimento di settore, anche per pianificare le proprie migrazioni interne con lo stesso ordine di grandezza.

Cosa c’è già oggi: le API disponibiliLa parte più interessante per chi scrive codice è che la PQC non è più solo teoria da paper accademico: le API sono già disponibili e generalmente disponibili (GA) sulle piattaforme Microsoft.

Windows: CNGSu Windows, il supporto arriva a livello di CNG (Cryptography API: Next Generation), con identificatori di algoritmo dedicati per ML-KEM e ML-DSA utilizzabili tramite le funzioni BCryptGenerateKeyPair e le relative API di key exchange e firma. Il supporto nativo è arrivato a partire da Windows 11 con gli aggiornamenti di novembre 2025: chi gestisce flotte Windows dovrebbe verificare di essere allineato con le patch più recenti prima di pianificare qualunque test.

.NET 10: MLKem e MLDsa in System.Security.CryptographyPer chi sviluppa in C#, la notizia più concreta è l’arrivo delle classi MLKem e MLDsa direttamente in System.Security.Cryptography con .NET 10. Ecco un esempio minimo di key encapsulation:

using System.Security.Cryptography;

if (!MLKem.IsSupported)
{
Console.WriteLine("ML-KEM non è supportato su questa piattaforma");
return;
}

MLKemAlgorithm alg = MLKemAlgorithm.MLKem768;

using (MLKem privateKey = MLKem.GenerateKey(alg))
using (MLKem publicKey = MLKem.ImportEncapsulationKey(
alg, privateKey.ExportEncapsulationKey()))
{
publicKey.Encapsulate(out byte[] ciphertext, out byte[] sharedSecret1);
byte[] sharedSecret2 = privateKey.Decapsulate(ciphertext);

bool match = sharedSecret1.AsSpan().SequenceEqual(sharedSecret2);
Console.WriteLine($"I segreti condivisi coincidono: {match}");

}E un esempio di firma con ML-DSA:

MLDsaAlgorithm alg = MLDsaAlgorithm.MLDsa65;

using (MLDsa key = MLDsa.GenerateKey(alg))
{
byte[] data = "Messaggio da firmare"u8.ToArray();
byte[] signature = new byte[alg.SignatureSizeInBytes];

key.SignData(data, signature);
bool verified = key.VerifyData(data, signature);

Console.WriteLine($"Firma verificata: {verified}");

}Entrambe le famiglie di algoritmi sono disponibili in più varianti — MLKem512, MLKem768, MLKem1024 per il key encapsulation, e MLDsa44, MLDsa65, MLDsa87 per la firma — con un compromesso crescente tra sicurezza e dimensione delle chiavi. Ad esempio, per MLDsa65 la chiave pubblica occupa 1952 byte, quella privata 4032 byte e la firma 3309 byte: numeri sensibilmente più grandi rispetto a ECDSA, un dettaglio da tenere presente quando si progettano protocolli o formati di messaggio con vincoli di banda o storage.

Un paio di note pratiche per chi vuole sperimentare da subito: su Linux serve OpenSSL 3.5 o superiore, mentre chi è ancora su .NET Standard 2.0 può accedere alle stesse API tramite il pacchetto NuGet Microsoft.Bcl.Cryptography. Per il supporto lato TLS 1.3, certificati basati su ML-DSA e SLH-DSA funzionano già in scenari di autenticazione quando sia il sistema operativo sia la controparte della connessione supportano i nuovi algoritmi — un altro motivo per cui la coordinazione tra client e server nella transizione non è opzionale.

Da dove cominciare, in praticaPer un team che affronta questo tema per la prima volta, un percorso ragionevole è: primo, effettuare un inventario reale di certificati, librerie crittografiche e protocolli in uso, distinguendo per criticità e durata di vita dei dati protetti; secondo, verificare che l’infrastruttura TLS sia già su 1.3 ovunque possibile, perché è il prerequisito tecnico per tutto il resto; terzo, iniziare a sperimentare le nuove API — su .NET 10 il costo di ingresso è basso, si tratta di poche righe di codice — in ambienti non di produzione, per capire l’impatto su dimensioni di chiavi, firme e tempi di calcolo prima che diventi un requisito con scadenza stretta.

La crittografia post-quantistica non è ancora un’emergenza operativa per la maggior parte delle organizzazioni, ma il messaggio di Microsoft è chiaro: il momento di mappare le proprie dipendenze crittografiche e cominciare a testare gli algoritmi è adesso, non quando arriverà l’obbligo normativo o il primo incidente.

Fonte originale: Microsoft Urges Threat Modeling to Prepare for Post-Quantum Cryptography Migration, Petri IT Knowledgebase

#sicurezza #microsoft #windows #c #net

0 0 1

Agent Plugins 1.0: lo standard che rende portabili skill e server MCP tra VS Code, Copilot CLI e SDK

Il problema che Agent Plugins 1.0 prova a risolvereChi lavora quotidianamente con GitHub Copilot conosce bene la frammentazione degli ultimi due anni:

Altro...

Il problema che Agent Plugins 1.0 prova a risolvereChi lavora quotidianamente con GitHub Copilot conosce bene la frammentazione degli ultimi due anni: skill scritte per VS Code che non funzionano su Copilot CLI, configurazioni MCP duplicate tra client diversi, agenti custom da ricostruire da zero ogni volta che si cambia strumento. Con Agent Plugins 1.0, annunciato a metà agosto 2026, GitHub prova a chiudere questa frammentazione introducendo uno standard aperto — non un formato proprietario — per pacchettizzare skill e server MCP in un plugin installabile una sola volta e utilizzabile su più superfici: VS Code, Copilot CLI, Copilot SDK, l’app Copilot e il coding agent cloud.

È un cambio di prospettiva interessante anche per chi non usa Copilot come strumento principale: la specifica Agent Plugins non è un’invenzione isolata, ma converge con formati simili già visti in altri ecosistemi (Claude, OpenPlugin), e questo la rende rilevante per chiunque costruisca strumenti per agenti AI, non solo per i team Microsoft/GitHub.

Anatomia di un Agent PluginUn plugin conforme allo standard 1.0 è una directory con una struttura riconoscibile. Il file centrale è plugin.json, che dichiara esplicitamente lo schema a cui aderisce:

{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "my-dev-tools",
"description": "Utility per lo sviluppo React",
"version": "1.2.0"
}I soli campi obbligatori sono $schema e name; tutto il resto (version, description, author, homepage, repository, license, keywords) è opzionale ma consigliato per la pubblicazione su un marketplace.

Attorno al manifest, la struttura tipica di un plugin più completo è questa:

my-testing-plugin/
plugin.json # Metadati del plugin
skills/
test-runner/
SKILL.md # Istruzioni della skill
run-tests.sh # Script di supporto
agents/
test-reviewer.agent.md # Agente custom (client-specific)
hooks/
hooks.json # Configurazione hook (client-specific)
scripts/
validate-tests.sh
.mcp.json # Definizione dei server MCPLa distinzione più importante per chi progetta un plugin è tra componenti portabili e componenti client-specific:

Portabili (parte dello standard 1.0): server MCP (integrazioni con tool esterni) e skill, cioè istruzioni, script e risorse caricate on-demand dall’agente.

Client-specific (solo VS Code, ad esempio): agenti custom con personalità e configurazione tool dedicata, hook che eseguono comandi shell in punti precisi del ciclo di vita dell’agente, e slash command richiamabili in chat con /.

Questa separazione è la vera innovazione pratica: uno stesso plugin può portare le sue skill e i suoi server MCP ovunque, mentre le personalizzazioni specifiche di un client restano lì dove hanno senso, senza rompere la compatibilità altrove.

Configurare i server MCP dentro un pluginI server MCP si dichiarano in .mcp.json (o mcp.json), con variabili d’ambiente che il runtime dell’agente risolve automaticamente al momento del caricamento:

{
"mcpServers": {
"plugin-database": {
"command": "${PLUGIN_ROOT}/servers/db-server",
"args": ["--config", "${PLUGIN_ROOT}/config.json"],
"env": {
"DB_PATH": "${PLUGIN_ROOT}/data"
}
}
}
}La variabile ${PLUGIN_ROOT} punta sempre alla directory del plugin installato, indipendentemente da dove l’utente finale lo abbia scaricato — un dettaglio che elimina un’intera classe di bug legati a percorsi assoluti hardcoded nei plugin distribuiti da terze parti.

Hook: automazioni sul ciclo di vita dell’agentePer i client che supportano questa estensione (il formato è compatibile con la sintassi già nota da Claude), gli hook permettono di agganciare comandi shell a eventi specifici, ad esempio dopo l’uso di un tool:

{
"hooks": {
"PostToolUse": [
{
"type": "command",
"command": "${PLUGIN_ROOT}/scripts/format.sh"
}
]
}
}Un caso d’uso concreto: formattare automaticamente il codice generato dall’agente subito dopo ogni modifica a un file, senza dover ricordare di lanciare il linter manualmente.

Migrare un plugin Copilot esistenteChi ha già plugin Copilot “vecchio formato” non deve riscrivere nulla da zero. GitHub descrive un percorso di migrazione minimo:

Aggiungere il campo $schema al plugin.json esistente, puntandolo allo schema Agent Plugins 1.0.

Mantenere le skill dove sono già, sotto skills/.

Spostare i file specifici di Copilot (agenti, comandi, regole, hook, canvas) dentro una directory dedicata com.github.copilot/, così da separarli chiaramente dalla parte portabile.

I plugin GitHub Copilot esistenti restano comunque supportati senza obbligo di migrazione: è un aggiornamento incrementale, non un breaking change forzato.

Governance aziendale: managed-settings.jsonPer i team enterprise, probabilmente l’aspetto più rilevante non è la portabilità in sé ma il controllo centralizzato che ne deriva. Il file managed-settings.json permette di definire una baseline aziendale su cosa è installabile:

{
"extraKnownMarketplaces": {
"company-tools": {
"source": { "source": "github", "repo": "vostra-org/plugin-marketplace" }
}
},
"enabledPlugins": {
"code-formatter@company-tools": true
}
}Tre leve principali:

enabledPlugins — installa automaticamente o blocca esplicitamente plugin specifici per tutti gli utenti gestiti.

extraKnownMarketplaces — aggiunge marketplace interni oltre a quelli pubblici di default.

strictKnownMarketplaces — se attivato, limita l’installazione ai soli marketplace esplicitamente autorizzati, impedendo l’uso di sorgenti non gestite.

Le impostazioni enterprise agiscono come baseline additiva: i team possono comunque aggiungere plugin approvati sopra la configurazione centrale, senza però poter aggirare i blocchi imposti a livello organizzativo. Per chi gestisce flotte di sviluppatori con policy di sicurezza stringenti, è la differenza tra “confidare che ognuno installi solo cose sicure” e avere un controllo verificabile.

Dove si installano i pluginIl canale di scoperta di default è l’Awesome Copilot marketplace, disponibile fin da subito in VS Code, Copilot CLI e nell’app Copilot. Per plugin locali o in sviluppo, VS Code offre una registrazione esplicita via impostazioni:

"chat.pluginLocations": {
"/percorso/al/mio-plugin": true,
"/percorso/a/un-altro-plugin": false
}Un dettaglio utile in fase di debug: se un plugin non viene rilevato, vale la pena controllare che chat.plugins.enabled sia attivo, che il nome nel manifest sia in kebab-case (obbligatorio per lo standard 1.0), e — per problemi di installazione persistenti — ripulire la cache locale in ~/.config/Code/agentPlugins/ su Linux.

ConclusioneAgent Plugins 1.0 non introduce funzionalità radicalmente nuove rispetto a quello che skill e server MCP già facevano singolarmente: il suo valore è nella standardizzazione del confezionamento e nella governance che ne deriva. Per un team che sviluppa strumenti interni per i propri agenti AI — che siano skill per il debug, integrazioni con sistemi proprietari via MCP, o automazioni sul ciclo di vita dell’agente — poter scrivere il plugin una sola volta e vederlo funzionare su CLI, editor e SDK senza riscritture è un risparmio di manutenzione concreto, non solo un dettaglio architetturale. Vale la pena tenerlo d’occhio anche per chi non usa Copilot: essendo uno standard aperto, è probabile che altri agent tool ne adottino la compatibilità nei prossimi mesi.

Fonte: Agent Plugins 1.0 in VS Code, Copilot CLI, and the Copilot app — GitHub Changelog

#vscode #howto #tutorial #net #copilot

0 0 1

Unity abbandona Mono per CoreCLR: cosa cambia con .NET 10 e C# 14

Da Mono a CoreCLR: la fine di un’era per UnitySe sviluppate in C# ma non toccate Unity, la notizia potrebbe sembrarvi di nicchia. Non lo è. Con la rel

Altro...

Da Mono a CoreCLR: la fine di un’era per UnitySe sviluppate in C# ma non toccate Unity, la notizia potrebbe sembrarvi di nicchia. Non lo è. Con la release di Unity 6.8, prevista entro la fine del 2026, il motore di gioco più diffuso al mondo abbandona definitivamente il proprio fork custom di Mono in favore di CoreCLR, il runtime “vero” del .NET moderno, con supporto a .NET 10 e C# 14. Per anni Unity è rimasto ostaggio di un’architettura di scripting congelata a metà del decennio scorso, mentre il resto dell’ecosistema .NET correva avanti tra top-level statements, pattern matching avanzato, source generator e miglioramenti di performance del JIT. Il cambio di runtime non è solo un aggiornamento di versione: è un caso di studio interessante per chiunque lavori con .NET, anche fuori dal game dev, perché racconta come si affronta la migrazione di un’applicazione legacy da AppDomain a un modello di isolamento moderno.

Domain Reload: il problema che ogni sviluppatore Unity conosce a memoriaChi lavora su progetti Unity di dimensioni consistenti conosce fin troppo bene il Domain Reload: si salva uno script, si torna nell’editor, e parte una barra di progresso che su progetti grandi può durare decine di secondi. Il motivo è architetturale: ogni ricompilazione degli script comporta lo scaricamento completo dell’AppDomain e il suo ricaricamento da zero in memoria, un’operazione “a tappeto” pensata per un modello di isolamento vecchio di vent’anni.

Con CoreCLR, Unity abbandona il concetto di AppDomain in favore degli AssemblyLoadContext (ALC), il meccanismo di isolamento e caricamento assembly introdotto nel .NET moderno fin dai tempi di .NET Core. Invece di ricaricare tutto lo stato applicativo, il runtime può scaricare e ricaricare selettivamente solo gli assembly effettivamente modificati.

AssemblyLoadContext: un concetto utile ben oltre UnityPer chi non l’avesse mai usato fuori da un contesto plugin-system, un AssemblyLoadContext è essenzialmente un contenitore isolato in cui caricare assembly .NET, che può essere scaricato indipendentemente dal resto dell’applicazione. È lo stesso meccanismo che sta dietro a scenari come plugin dinamici, hot reload di parti di un’applicazione server, o sandboxing di codice di terze parti. Un esempio minimale, applicabile a qualunque applicazione .NET moderna:

using System.Reflection;
using System.Runtime.Loader;

var alc = new AssemblyLoadContext("PluginContext", isCollectible: true);

Assembly plugin = alc.LoadFromAssemblyPath(@"C:\plugins\MyPlugin.dll");
// ... usa il plugin tramite reflection o interfacce condivise ...

alc.Unload(); // richiede la garbage collection per liberare davvero la memoria
GC.Collect();
GC.WaitForPendingFinalizers();
Il dettaglio da tenere presente, sia in Unity sia in un’applicazione .NET qualsiasi, è che uno scaricamento “leaked” è possibile: se un riferimento a un tipo caricato nell’ALC sopravvive fuori dal suo scope (una closure, un evento sottoscritto, un campo statico), l’assembly non può essere effettivamente liberato dal garbage collector. Non a caso, nel forum ufficiale Unity gli sviluppatori hanno confermato che il motore segnalerà esplicitamente all’utente quando un AssemblyLoadContext non riesce a essere scaricato correttamente, un problema di leak molto simile a quello che chi scrive plugin system in .NET conosce già.

Cosa cambia in pratica, oltre al Domain ReloadLa migrazione a CoreCLR porta con sé una serie di conseguenze pratiche per chi sviluppa con Unity:

ECS più integrato: gli Instance ID passano da 32 a 64 bit, permettendo a GameObject “classici” ed entità ECS (Data-Oriented Technology Stack) di condividere lo stesso spazio di identificatori. Un nuovo metodo GetEntityId() collega direttamente gli oggetti di scena al mondo ECS, riducendo la frizione tra i due paradigmi.

Serializzazione nativa dei dizionari: Dictionary<TKey, TValue> sarà finalmente serializzabile nativamente dall’Inspector, eliminando i workaround basati su liste parallele di chiavi e valori o su ISerializationCallbackReceiver. In parallelo, Unity rimuove il datato e insicuro BinaryFormatter.

IL2CPP resta, ma diventa opzionale: per le piattaforme non coperte da NativeAOT (che CoreCLR usa per la compilazione ahead-of-time), IL2CPP continuerà a essere il backend di scripting necessario. Su piattaforme compatibili, però, sarà possibile scegliere CoreCLR come backend alternativo già a partire da una preview tecnica prevista intorno a Unity 6.7.

MiMalloc come allocatore di memoria: l’integrazione dell’allocatore ad alte prestazioni di Microsoft riduce i problemi di lock contention nei carichi multithread, con benefici diretti per chi usa il C# Job System in scenari data-oriented.

Perché la cosa interessa anche chi non fa game devIl percorso di Unity da Mono a CoreCLR è, in piccolo, lo stesso tipo di migrazione che molte software house con applicazioni .NET Framework legacy dovranno affrontare prima o poi: passare da un modello di isolamento basato su AppDomain (o peggio, da un fork custom del runtime) a un’architettura moderna basata su ALC, con tutti i vantaggi in termini di performance, ma anche le insidie legate alla gestione del ciclo di vita degli assembly caricati dinamicamente. Se lavorate su plugin system, hosting di codice di terze parti o architetture a moduli scaricabili a runtime, vale la pena studiare da vicino come Unity gestisce concretamente i leak di ALC: è un problema che, presto o tardi, si incontra in qualunque applicazione .NET che provi a fare hot-reload o isolamento dinamico.

ConclusioneCon Unity 6.8, il motore smette di essere un’isola separata dall’ecosistema .NET e si allinea finalmente al runtime moderno, portando in dote C# 14, prestazioni migliori e un developer loop molto più rapido. Per chi sviluppa giochi è una notizia enorme; per chi sviluppa in .NET in generale è un promemoria utile su come affrontare, con gli strumenti giusti, la migrazione da architetture di isolamento legacy a AssemblyLoadContext.

Fonte: DZone, con dettagli tecnici dal thread ufficiale Path to CoreCLR, 2026: Upgrade Guide su Unity Discussions.

#c #programmazione #net #dev

0 0 1

C# 15 introduce le union: basta OneOf e Results per modellare i tipi (.NET 11 Preview 6)

Da anni gli sviluppatori C# aspettano un modo nativo per dire “questo valore è esattamente uno tra questi tipi, e nient’altro”, con il compilatore in

Altro...

Da anni gli sviluppatori C# aspettano un modo nativo per dire “questo valore è esattamente uno tra questi tipi, e nient’altro”, con il compilatore in grado di garantirlo. In assenza di un supporto di linguaggio, la community si è arrangiata con pattern come Results<T> di ASP.NET Core Minimal API o con librerie come OneOf. Funzionano, ma con un limite strutturale: se aggiungi un quarto caso possibile a una funzione che prima ne restituiva tre, nessuno ti avvisa che uno switch a valle non gestisce il nuovo tipo. Manca l’esaustività, che è poi il punto centrale delle discriminated union.

Con .NET 11 Preview 6 (rilasciata il 14 luglio 2026) e C# 15, questa lacuna si chiude: arriva la parola chiave union, insieme a un intero pacchetto di funzionalità correlate (conversioni implicite, pattern matching esaustivo, gestione della nullabilità) che rendono i union type un cittadino di prima classe del linguaggio.

La sintassi: dichiarare un union typeLa forma più semplice è la union declaration, una sintassi compatta che genera automaticamente uno struct:

public union Pet(Cat, Dog, Bird);Questa singola riga dice al compilatore che un Pet è esattamente un Cat, un Dog o un Bird, e nient’altro. Sotto il cofano, il compilatore genera uno struct che implementa l’interfaccia System.Runtime.CompilerServices.IUnion e conserva il valore effettivo in una proprietà Value di tipo object?.

I union generici sono immediatamente utili per modellare risultati di operazioni che possono fallire, un caso d’uso ricorrentissimo in codice di produzione:

public union Result<TSuccess, TError>(TSuccess, TError);

Result<User, Error> Register(string username)
{
if (string.IsNullOrEmpty(username))
return new Error("Username cannot be empty");

return new User(username);

}Da notare l’ultima riga: nessun wrapping esplicito, nessuna chiamata a un factory method. Il valore User o Error viene convertito implicitamente in Result<User, Error> grazie alle union conversion generate automaticamente per ogni case type.

Pattern matching esaustivo, finalmenteIl vero salto di qualità è nel matching. Se dimentichi un caso in uno switch su un union type, il compilatore te lo dice:

string Describe(Pet pet) => pet switch
{
Dog d => d.Name,
Cat c => c.Name,
// CS8509: switch expression does not handle all
// possible values of its input type (Bird)
};Aggiungendo il caso mancante, l’avviso scompare, e non serve più il classico _ => throw new InvalidOperationException("unreachable") per zittire il compilatore:

string Describe(Pet pet) => pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};Il punto interessante è che questa esaustività è dinamica: se in futuro aggiungi un nuovo case type al union (per esempio un quarto animale), ogni switch esistente che non lo gestisce si illumina di un warning. Per chi ha inseguito bug di produzione causati da un case dimenticato in un evento di dominio, è un cambiamento non da poco.

I pattern si applicano direttamente al union, senza bisogno di accedere manualmente a .Value: il compilatore effettua l’unwrapping in automatico, anche negli if pattern classici:

if (pet is Dog d)
{
Console.WriteLine(d.Name);
}Gestione del nullIl valore di default di un union type ha Value == null, ed è possibile intercettarlo esplicitamente con il pattern null:

string Describe(Pet pet) => pet switch
{
null => "nessun animale",
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};La necessità o meno del ramo null dipende dal fatto che il parametro o il campo union-typed sia nullable: l’analisi di nullabilità del compilatore guida correttamente questi casi, in coerenza con le annotazioni enable già in uso nella maggior parte delle codebase moderne.

Un caso pratico: modellare eventi di dominioI union type diventano davvero interessanti quando li si combina con i record per il domain modeling, per esempio in un sistema di elaborazione ordini con eventi distinti:

public record OrderPlaced(Guid OrderId, decimal Total);
public record OrderShipped(Guid OrderId, string TrackingNumber);
public record OrderCancelled(Guid OrderId, string Reason);
public union OrderEvent(OrderPlaced, OrderShipped, OrderCancelled);

string Summarize(OrderEvent evt) => evt switch
{
OrderPlaced p => $"Ordine {p.OrderId} piazzato per {p.Total:C}",
OrderShipped s => $"Ordine {s.OrderId} spedito, tracking: {s.TrackingNumber}",
OrderCancelled c => $"Ordine {c.OrderId} annullato: {c.Reason}",
};Quando tra tre mesi qualcuno aggiungerà OrderRefunded al union, ogni handler che non lo gestisce lancerà un warning in fase di compilazione, invece di fallire silenziosamente in produzione. È il compilatore che fa da revisore di esaustività al posto tuo.

I union possono anche esporre metodi e proprietà calcolate (ma non campi d’istanza), utile per incapsulare logica di conversione:

public union Length(Meters, Feet)
{
public double TotalMeters => this switch
{
Meters m => m.Value,
Feet f => f.Value * 0.3048,
_ => throw new InvalidOperationException(),
};
}Union personalizzati con l’attributo [Union]La parola chiave union genera sempre uno struct. Se per motivi di identità, ereditarietà o layout di memoria serve un tipo reference, è possibile costruire un union personalizzato applicando direttamente l’attributo System.Runtime.CompilerServices.UnionAttribute e implementando l’interfaccia IUnion:

[System.Runtime.CompilerServices.Union]
public class Shape : System.Runtime.CompilerServices.IUnion
{
private readonly object? _value;
public Shape(Circle value) { _value = value; }
public Shape(Rectangle value) { _value = value; }
public object? Value => _value;
}Nella grande maggioranza dei casi, però, la sintassi union compatta copre già ogni esigenza pratica: l’approccio con attributo è pensato per scenari con requisiti specifici che lo struct generato non può soddisfare.

Come provarlo oggiI union type sono attualmente in preview. Per sperimentarli serve l’SDK .NET 11 Preview e questa configurazione nel file di progetto:

ConclusioniF# ha le discriminated union fin dalle origini, e la richiesta di un equivalente in C# (csharplang issue #113) è aperta da anni. Con l’arrivo dei union type, non ogni problema di modellazione richiede più una gerarchia di classi: per rappresentare “uno tra un insieme chiuso di tipi”, ora esiste un costrutto di linguaggio dedicato, con conversioni implicite, pattern matching esaustivo e un’ottima integrazione con i record esistenti. Per chi lavora ogni giorno con API che possono restituire risultati eterogenei, o con event sourcing e domain modeling, è una delle novità più concrete della prossima versione di C#.

Fonte: .NET Blog, C# language reference proposal: Unions e Maarten Balliauw.

#c #guide #tutorial #net

0 0 0

MCP 2026-07-28: il Model Context Protocol diventa stateless, ecco cosa cambia per chi sviluppa agenti AI

Il 28 luglio 2026 entra in vigore la nuova revisione della specifica Model Context Protocol (MCP), lo standard aperto introdotto da Anthropic che perm

Altro...

Il 28 luglio 2026 entra in vigore la nuova revisione della specifica Model Context Protocol (MCP), lo standard aperto introdotto da Anthropic che permette ai modelli AI di collegarsi in modo sicuro a strumenti e sorgenti dati esterne. Non si tratta di un aggiornamento cosmetico: la revisione 2026-07-28 ridisegna il nucleo del protocollo, passando da un modello con stato a uno completamente stateless, e introduce cambiamenti che toccano direttamente chi progetta, ospita o consuma server MCP in produzione.

Per chi lavora con agenti AI e integrazioni enterprise, questa è una di quelle revisioni che vale la pena capire in dettaglio prima che arrivi la versione finale, perché tocca scalabilità, autenticazione e compatibilità con le versioni precedenti.

Dal protocollo con stato al core statelessNelle versioni precedenti di MCP, un client doveva eseguire un handshake initialize prima di poter fare qualsiasi cosa: il server rispondeva con un identificatore di sessione da includere in ogni richiesta successiva. Chi ha provato a mettere in produzione un server MCP dietro un load balancer conosce il problema: il client restava “incollato” a una specifica istanza, e scalare orizzontalmente richiedeva sticky session o uno store di sessione condiviso.

La revisione 2026-07-28 elimina sia l’handshake initialize sia il concetto stesso di sessione a livello di protocollo. Ogni richiesta è ora autosufficiente: versione del protocollo, informazioni sul client e capability dichiarate viaggiano in ogni singola chiamata invece di essere negoziate una volta sola all’apertura della connessione. Un nuovo metodo, server/discover, permette al client di recuperare le capability del server solo quando gli servono davvero.

La conseguenza pratica è che, non esistendo più un identificatore di sessione, qualsiasi richiesta può essere instradata verso qualunque istanza del server. Diventa possibile piazzare un server MCP dietro un banale load balancer round-robin, senza sticky session né store condiviso. Se un’applicazione ha comunque bisogno di mantenere uno stato tra una chiamata e l’altra, la soluzione prevista è restituire un handle esplicito (ad esempio un ID di un “carrello” o di una sessione applicativa) da uno strumento, e far sì che il client lo passi indietro come un normale argomento nelle chiamate successive. È lo stesso pattern usato da anni nelle API REST stateless: lo stato non sparisce, si sposta semplicemente dal trasporto al payload applicativo.

Multi Round-Trip Requests: confermare senza tenere aperta una connessioneUn protocollo stateless ha comunque bisogno di un modo per gestire i casi in cui un server deve chiedere qualcosa al client a metà di una chiamata, per esempio una conferma dell’utente prima di eseguire un’operazione sensibile. Nelle versioni precedenti questo richiedeva tenere aperto uno stream Server-Sent Events per tutta la durata dell’interazione. La nuova revisione sostituisce questo meccanismo con le Multi Round-Trip Requests (MRTR).

Quando un server ha bisogno di input dall’utente durante l’esecuzione di uno strumento, restituisce un oggetto InputRequiredResult contenente le domande da porre e un blob requestState. Il client raccoglie le risposte e riemette la chiamata originale includendo sia le risposte sia lo stato “echoed” ricevuto in precedenza. Poiché tutto ciò che serve al server è contenuto nel payload, qualunque istanza del server può gestire il retry, anche se è diversa da quella che ha generato la richiesta iniziale. È un dettaglio che semplifica enormemente il deployment di server MCP dietro infrastrutture di bilanciamento del carico stateless.

Header instradabili e caching esplicitoDue modifiche più contenute a livello di trasporto rendono il traffico MCP molto più facile da osservare e instradare in produzione. Il trasporto Streamable HTTP richiede ora un header Mcp-Method su ogni richiesta, mentre l’header Mcp-Name è obbligatorio solo per tipi di richiesta specifici, come tools/call, resources/read e prompts/get. Un gateway o un load balancer può leggere questi header per instradare il traffico senza dover analizzare il corpo della richiesta, un vantaggio non trascurabile per chi gestisce infrastrutture MCP a scala aziendale. I server rifiutano le richieste in cui header e corpo non sono coerenti tra loro.

In secondo luogo, i risultati di liste e letture di risorse ora includono i campi ttlMs e cacheScope, modellati sul comportamento dell’header HTTP Cache-Control. I client sanno esattamente per quanto tempo una risposta a tools/list resta valida e se può essere condivisa tra utenti diversi. Non è più necessario mantenere uno stream a lungo termine solo per scoprire che una lista è cambiata: un pattern di polling con cache esplicita è sufficiente.

Autenticazione più rigorosa, allineata a OAuth 2.0La revisione stringe le regole di autorizzazione per allinearle più da vicino a OAuth 2.0 e OpenID Connect. I client devono ora validare il parametro iss nelle risposte di autorizzazione secondo la RFC 9207, una mitigazione contro i “mix-up attack”, una classe di attacchi più rilevante nel pattern tipico di MCP in cui un singolo client dialoga con molti server diversi.

I client devono inoltre dichiarare il proprio application_type durante la Dynamic Client Registration, così gli authorization server non assumono più di default che i client desktop o CLI siano applicazioni “web”, evitando il rifiuto ingiustificato dei redirect URI su localhost. Le credenziali vengono vincolate all’authorization server che le ha emesse, e la specifica chiarisce come richiedere i refresh token durante l’autenticazione step-up.

Funzionalità deprecate: cosa cambia per chi ha già integrato MCPTre funzionalità core vengono formalmente deprecate: Roots, Sampling e Logging. Roots permetteva a un client di dichiarare i confini del filesystem a un server; Sampling permetteva a un server di chiedere al modello del client di generare testo per suo conto; Logging forniva notifiche a livello di protocollo dal server al client.

La deprecazione non significa rimozione immediata: questi metodi, tipi e capability flag continuano a funzionare in questa release e in ogni versione della specifica pubblicata entro un anno da essa. Da qui in poi, però, sono su un orologio di rimozione. Le alternative consigliate sono parametri degli strumenti o URI di risorse al posto di Roots, integrazione diretta con le API dei provider LLM al posto di Sampling, e OpenTelemetry per l’osservabilità strutturata al posto del Logging di protocollo. Chi mantiene librerie o server MCP dovrebbe iniziare a pianificare la migrazione già da ora.

Schema degli strumenti, codici di errore ed estensioniGli schema di input e output degli strumenti utilizzano ora JSON Schema 2020-12 completo. Gli schema di input mantengono il vincolo della radice type: "object" ma ora permettono composizione con oneOf, anyOf e allOf, oltre a condizionali e riferimenti. Gli schema di output non hanno più restrizioni, e structuredContent può essere qualsiasi valore JSON, non solo un oggetto.

Il codice di errore per una risorsa mancante cambia dal valore custom MCP -32002 allo standard JSON-RPC -32602 (Invalid Params). Chi ha client che fanno pattern matching sul valore letterale -32002 deve aggiornare quel codice prima del 28 luglio.

Le estensioni, già presenti nella release precedente ma senza un processo formale, ricevono ora una struttura definita: sono identificate da ID reverse-DNS, negoziate tramite una mappa extensions, e vivono in repository indipendenti con maintainer dedicati, versionando separatamente dalla specifica principale. Due estensioni ufficiali arrivano con questa release: MCP Apps, che permette ai server di fornire interfacce HTML interattive renderizzate dagli host in un iframe sandboxed, e Tasks, promossa da funzionalità sperimentale a estensione ufficiale, che fornisce un modello stateless per lavori a lunga esecuzione: un server può rispondere a tools/call con un task handle, e il client lo gestisce con tasks/get, tasks/update e tasks/cancel.

SDK beta disponibili da subitoSono già disponibili release beta degli SDK Python, TypeScript, Go e C#. Python v2 rinomina FastMCP in MCPServer ma mantiene l’API a decoratori. TypeScript v2 divide l’SDK monolitico in pacchetti focalizzati come @modelcontextprotocol/server e @modelcontextprotocol/client, ed è ora ESM-only. Go integra il supporto alla revisione 2026-07-28 in v1.7.0-pre.1 sullo stesso module path. C# rilascia la versione 2.0.0-preview.1 dei pacchetti ModelContextProtocol.

Per chi ha già server e client in produzione, la buona notizia è che nulla si rompe automaticamente il giorno del rilascio: i nuovi client che parlano la revisione 2026-07-28 tornano automaticamente all’handshake initialize quando raggiungono un server che implementa una versione precedente o uguale al 2025-11-25. Chi però pubblica librerie che dipendono dal pacchetto Python mcp dovrebbe aggiungere un limite superiore esplicito, per esempio mcp>=1.27,<2, in modo che il passaggio alla v2 stabile non colga di sorpresa gli utenti del proprio pacchetto.

ConclusioneLa revisione 2026-07-28 di MCP è un cambiamento strutturale, non un semplice aggiornamento incrementale. Il core stateless elimina la necessità di session affinity e semplifica scaling orizzontale e load balancing di server MCP in produzione. I nuovi pattern come le MRTR e gli header instradabili rendono il protocollo più facile da operare su larga scala. L’irrigidimento delle regole di autorizzazione allinea MCP alle pratiche standard di OAuth e OpenID Connect, riducendo la superficie di attacco tipica delle integrazioni multi-server. Le funzionalità deprecate restano funzionanti per ora, ma vanno sostituite secondo un piano preciso prima che scada la finestra di un anno.

Chi sviluppa o ospita server MCP dovrebbe testare gli SDK beta contro i propri carichi di lavoro prima della data finale del 28 luglio, verificando in particolare la gestione dei codici di errore, la compatibilità dei client con l’handshake legacy e l’impatto delle nuove regole di autorizzazione sulle integrazioni desktop e CLI esistenti.

Fonte: 4sysops.com

#ai #ia #net #dev

0 0 0