-----------------------------------

Acquista i software ArcGIS tramite Studio A&T srl, rivenditore autorizzato dei prodotti Esri.

I migliori software GIS, il miglior supporto tecnico!

I migliori software GIS, il miglior supporto tecnico!
Azienda operante nel settore GIS dal 2001, specializzata nell’utilizzo della tecnologia ArcGIS e aderente ai programmi Esri Italia Business Network ed Esri Partner Network

-----------------------------------



martedì 20 ottobre 2026

Deep Dive WebGL e RenderNode

WebGL e GIS 3D: Three.js, RenderNode e ArcGIS Maps SDK 5.1 con Vite e Node.js

Deep dive per sviluppatori. Come inserire un personaggio glTF animato nella pipeline grafica di una scena geografica, gestire un contesto WebGL condiviso, interpolare una traiettoria sul terreno e distribuire il risultato come applicazione JavaScript statica. Il caso di studio è il progetto open source externalRendererSkeleton.

Demo: Apri l’applicazione su GitHub Pages. Repository: codice sorgente. Le sezioni di codice riprendono parti del progetto e, dove esplicitamente indicato, mostrano versioni abbreviate per spiegare un meccanismo.

In sintesi. Il browser esegue SceneView, RenderNode e Three.js; Node.js serve per gli strumenti di sviluppo e la build. Vite produce il bundle statico. Il problema centrale non è caricare un GLB, ma sincronizzare camera, spazio di riferimento, framebuffer, stato WebGL e tempo fisico dell’animazione.

1. Dal vecchio externalRenderer al RenderNode moderno

Il nome del progetto rimanda al vecchio externalRenderers, ma l’implementazione corrente utilizza RenderNode con ArcGIS Maps SDK for JavaScript 5.1. L’originaria applicazione ArcGIS 4.1 e il vecchio modello Marine non appartengono più all’albero sorgente corrente: la loro storia è consultabile nei commit precedenti. Il modello della versione attuale è Soldier.glb.

Con externalRenderers la logica di rendering esterno veniva agganciata alla vista tramite un’interfaccia dedicata. Con RenderNode il contributo custom viene modellato come nodo della pipeline della scena 3D: il nodo dichiara gli input consumati, l’output prodotto e si integra nel ciclo di compositing. Questo cambia il ragionamento architetturale: non basta avere un renderer Three.js che disegna; bisogna rispettare il contratto grafico della scena che lo ospita.

Il codice sperimentale o soggetto a evoluzione del SDK richiede una verifica sulla versione effettiva in uso prima degli aggiornamenti. In particolare, i nomi degli stage del render graph e il loro contratto devono essere considerati parte dell’integrazione, non dettagli stabili e universali.

2. La toolchain: Node.js 22+, Vite 7, ES modules e dipendenze

Il repository usa moduli ECMAScript nativi ("type": "module") e un progetto Vite collocato direttamente nella radice. Le dipendenze dichiarate nel package.json sono @arcgis/core ^5.1.0, @arcgis/map-components ^5.1.0, three ^0.180.0, con vite ^7.1.0 tra le dipendenze di sviluppo. Il README richiede Node.js 22 o superiore.

{
  "type": "module",
  "scripts": {
    "dev": "vite --host 0.0.0.0",
    "build": "vite build",
    "test": "node --test"
  },
  "dependencies": {
    "@arcgis/core": "^5.1.0",
    "@arcgis/map-components": "^5.1.0",
    "three": "^0.180.0"
  },
  "devDependencies": { "vite": "^7.1.0" }
}
git clone https://github.com/nicogis/externalRendererSkeleton.git
cd externalRendererSkeleton
npm install
npm test
npm run dev
npm run build

Il server di Vite abilita la sperimentazione locale e l’Hot Module Replacement; la build di produzione emette file statici in dist/. Node.js non disegna il modello 3D e non esegue il WebGL del client: a runtime sono il browser, la GPU e il SDK cartografico a lavorare.

Nel progetto la configurazione base: "./" consente di mantenere i riferimenti relativi agli asset, un aspetto importante quando la stessa build viene pubblicata sotto una sottocartella su GitHub Pages o IIS. Un plugin Vite personalizzato serve models/Soldier.glb durante lo sviluppo e lo emette in dist/models/Soldier.glb durante la build.

3. Separazione dei moduli: il rendering non deve possedere tutta l’applicazione

src/main.js → bootstrap UI + ArcGIS Scene
src/renderer.js → RenderNode, camera, GL, modello
src/character.js → AnimationMixer e gait
src/locomotion.js → tempo, velocità, distanza
src/path.js → geometria e interpolazione metrica
src/routeEditor.js → sketch, DEM e route display
src/activity.js → telemetria derivata
vite.config.js → sviluppo, packaging asset e build

Questa separazione è fondamentale. Il movimento lungo la traiettoria non dipende dalla GPU; il calcolo delle calorie non appartiene al renderer; la geometria metrica è verificabile in test isolati. In un’applicazione 3D reale, ridurre gli accoppiamenti rende più facile diagnosticare errori che altrimenti sembrano tutti semplicemente “il modello non si vede”.

4. RenderNode: input, output e framebuffer condiviso

Il cuore di src/renderer.js è la creazione di una sottoclasse RenderNode. La configurazione attuale prende e restituisce lo stage transparent-color. Non significa che Three.js possieda un canvas separato: il codice riceve l’accesso al contesto WebGL già utilizzato da ArcGIS.

const Node = RenderNode.createSubclass({
  consumes: { required: ["transparent-color"] },
  produces: "transparent-color",

  initialize() {
    this.scene3 = new THREE.Scene();
    this.camera3 = new THREE.Camera();
    this.camera3.matrixAutoUpdate = false;
    this.camera3.matrixWorldAutoUpdate = false;
  }
});

Nel primo passaggio utile del rendering, l’integrazione inizializza il renderer usando canvas e contesto ricevuti dal nodo:

this.renderer3 = new THREE.WebGLRenderer({
  canvas: this.gl.canvas,
  context: this.gl,
  antialias: false
});
this.renderer3.autoClear = false;

Poiché WebGL è una macchina a stati, una chiamata Three.js può modificare binding e opzioni che ArcGIS usa nei passaggi successivi. Il problema non si limita allo shader attivo: comprende framebuffer, viewport, buffer, depth/stencil test, blending e altre configurazioni. Per questo l’ordine di ripristino è una parte sostanziale dell’implementazione.

// Parte cruciale della sequenza nel renderer reale
this.renderer3.resetState();
this.bindRenderTarget();

const viewport = this.gl.getParameter(this.gl.VIEWPORT);
this.renderer3.setViewport(...viewport);
this.renderer3.render(this.scene3, this.camera3);
this.resetWebGLState();
Perché quest’ordine? resetState() può intervenire sui binding GL condivisi. Richiamare bindRenderTarget() dopo il reset permette ad ArcGIS di ripristinare il framebuffer di destinazione per lo stage corrente. Nel repository esiste inoltre un adattamento di setRenderTarget() per ripristinare il target ArcGIS quando Three.js torna al target nullo: estrarre soltanto poche righe non equivale quindi a una soluzione plug-and-play.

Il progetto evita inoltre renderer.setSize() sul canvas di proprietà ArcGIS: quella chiamata può modificare le dimensioni reali del canvas, compromettendo le attachment del framebuffer. Il renderer Three.js non deve ridefinire arbitrariamente la superficie grafica gestita da SceneView.

5. Camera ArcGIS e Three.js: sincronizzare due convenzioni matriciali

Una mesh correttamente caricata e inviata alla GPU può risultare invisibile se Three.js usa matrici differenti da SceneView. La camera del SDK mette a disposizione le matrici già calcolate per la scena; l’integrazione le trasferisce nella camera Three.js. L’origine locale riduce la magnitudine delle coordinate in render space, ma richiede la corrispondente compensazione nella matrice di vista.

this.camera3.projectionMatrix.fromArray(this.camera.projectionMatrix);
this.camera3.projectionMatrixInverse
  .copy(this.camera3.projectionMatrix).invert();

const viewMatrix = new THREE.Matrix4()
  .fromArray(this.camera.viewMatrix);
const origin = new THREE.Matrix4()
  .makeTranslation(...this.localOrigin);

this.camera3.matrixWorldInverse.multiplyMatrices(viewMatrix, origin);
this.camera3.matrixWorld
  .copy(this.camera3.matrixWorldInverse).invert();

In termini geometrici, la posizione usata dalla mesh è espressa rispetto a un’origine locale. La matrice di vista deve dunque convertire correttamente quelle coordinate locali nello spazio camera. Se si sottrae l’origine alla posizione del personaggio ma non la si reinserisce nella trasformazione della camera, la proiezione risulta incoerente.

Disabilitare matrixAutoUpdate e matrixWorldAutoUpdate evita che Three.js ricostruisca le matrici della camera a partire da proprietà non sincronizzate. Una prova diagnostica utile è proiettare la posizione del modello nello spazio NDC: coordinate fuori dall’intervallo visibile indicano problemi di camera, placement o frustum prima ancora del caricamento dell’asset.

6. Coordinate GIS, origine locale e riferimento East-North-Up

La scena usa coordinate geografiche/proiettate, mentre i vertici del personaggio appartengono allo spazio locale del modello. In renderer.js si trova webgl.toRenderCoordinates per convertire una posizione geografica nello spazio di rendering e webgl.renderCoordinateTransformAt per costruire una base locale orientata est, nord e alto (ENU).

const anchor = webgl.toRenderCoordinates(
  view, raisedPosition, 0, SR,
  new Float64Array(3), 0, 1
);
this.root.position.set(
  anchor[0] - this.localOrigin[0],
  anchor[1] - this.localOrigin[1],
  anchor[2] - this.localOrigin[2]
);

const enu = webgl.renderCoordinateTransformAt(
  view, raisedPosition, SR, new Float64Array(16)
);
const east  = new THREE.Vector3(enu[0], enu[1], enu[2]).normalize();
const north = new THREE.Vector3(enu[4], enu[5], enu[6]).normalize();
const up    = new THREE.Vector3(enu[8], enu[9], enu[10]).normalize();

L’asse verticale di una scena globale non coincide necessariamente con il +Z globale del render space. Il codice ricostruisce quindi una terna locale e usa la tangente del percorso per ricavare la direzione di avanzamento, che viene proiettata sul piano tangente al terreno. Da questa terna si produce una matrice di rotazione e poi un quaternione per il nodo principale.

const forward = new THREE.Vector3()
  .addScaledVector(east, tangent[0])
  .addScaledVector(north, tangent[1]);
forward.addScaledVector(up, -forward.dot(up)).normalize();
const right = new THREE.Vector3().crossVectors(forward, up).normalize();
forward.crossVectors(up, right).normalize();
const rotationBasis = new THREE.Matrix4().makeBasis(right, forward, up);
const desired = new THREE.Quaternion()
  .setFromRotationMatrix(rotationBasis);

Nella versione corrente viene applicata anche una correzione aggiuntiva di heading, inizialmente pari a 180°, per compensare la direzione nominale del modello. Il quaternione viene interpolato tramite slerp, evitando rotazioni istantanee quando la polilinea cambia direzione. Questo offset è una calibrazione dell’asset, non una regola cartografica generale.

Nota sulle misure: WKID 3857 è una proiezione Web Mercator: le differenze planimetriche tra coordinate non coincidono ovunque con le distanze geodetiche al suolo. Il caso di studio utilizza una metrica nello spazio proiettato coerente con i propri calcoli; per applicazioni fitness o misurazioni geografiche accurate è opportuno passare a distanze geodetiche o a un sistema locale appropriato, distinguendo anche la quota verticale.

7. Caricare Soldier.glb senza distruggere lo scheletro di animazione

Il modello viene importato con GLTFLoader. Il caricamento asincrono restituisce una scena glTF e le clip eventualmente presenti. Il progetto conserva la gerarchia interna dell’asset, la inserisce in un gruppo separato, ne calcola il bounding box e la scala rispetto a un’altezza visiva di riferimento di 2,5 metri.

const gltf = await new GLTFLoader().loadAsync(url);
this.characterHolder = new THREE.Group();
this.root.add(this.characterHolder);
this.characterHolder.add(gltf.scene);

this.animator = new CharacterAnimator(
  gltf.scene, gltf.animations,
  { forward: [0, 0, 1], up: [0, 1, 0] }
);

const bounds = new THREE.Box3().setFromObject(gltf.scene);
const modelSize = bounds.getSize(new THREE.Vector3());
const scale = 2.5 / modelSize.z;
this.characterHolder.scale.setScalar(scale);
gltf.scene.position.z -= bounds.min.z;

Il gruppo characterHolder separa scala e allineamento dei piedi dall’orientamento geografico del contenitore root. È una distinzione necessaria quando un asset nasce con assi, forward vector e proporzioni differenti da quelli della scena GIS. Il codice usa CharacterAnimator per gestire AnimationMixer, selezione delle clip che contengono nomi come idle, walk o run, transizioni con fade e variazione della velocità temporale della clip.

const referenceSpeed = selected === "run"
  ? this.runMetersPerSecond
  : this.clipMetersPerSecond;
this.active.timeScale = speedMetersPerSecond / referenceSpeed;
this.mixer.update(Math.min(Math.max(0, deltaSeconds), 0.1));

Il timeScale allinea in modo approssimativo il ritmo dell’animazione alla velocità simulata. Non elimina automaticamente foot sliding e discrepanze cinematiche: servono una calibrazione del ciclo di passo, l’eventuale gestione del root motion e verifiche sulla geometria reale del terreno.

8. Disegnare un percorso, densificarlo e campionare il DEM

L’applicazione non impone una route predefinita: l’utente disegna una polilinea sulla scena. routeEditor.js utilizza SketchViewModel, GraphicsLayer e Polyline. Per evitare segmenti troppo lunghi rispetto al terreno, la polilinea viene densificata prima di interrogare il ground DEM.

const xy = densifyPath(geometry.paths[0]);
const interpolated = new Polyline({
  paths: [xy],
  spatialReference: geometry.spatialReference
});
const result = await view.map.ground.queryElevation(
  interpolated,
  { demResolution: "auto" }
);
const elevated = result.geometry.paths[0]
  .map(p => [p[0], p[1], p[2]]);

La funzione corrente densifyPath usa un passo indicativo di 8 unità coordinate e un limite di 1.800 vertici per evitare campionamenti eccessivi. La geometria restituita viene validata: numero dei vertici coerente e coordinate finite. Una query asincrona porta anche problemi di concorrenza: il modulo usa un contatore di generazione per ignorare il risultato di richieste ormai superate dopo che l’utente ha annullato o sostituito il percorso.

Il tracciato è manuale, non generato tramite un routing engine e non vincolato automaticamente alla rete stradale. Questo va esplicitato: il Soldier percorre la polilinea disegnata e campionata, non un itinerario con snapping e costi di rete.

9. Interpolazione metrica indipendente dai frame

Un’animazione che avanza di una quantità fissa ogni frame cambia velocità con gli FPS. MetricPath prepara una sequenza cumulativa delle lunghezze dei segmenti e offre i metodi sample(distance) e tangent(distance). Locomotion aggiorna la distanza usando la velocità espressa in unità al secondo e il tempo reale di simulazione.

// Estratto da Locomotion.update(deltaSeconds)
const dt = Math.min(deltaSeconds, 0.1);
if (this.running) {
  if (this.speed > 0) {
    this.elapsed += dt;
    this.metMinutes += metForSpeed(this.speed) * dt / 60;
  }
  this.distance = Math.min(
    this.path.totalLength,
    this.distance + this.speed * dt
  );
  if (this.distance >= this.path.totalLength) {
    this.running = false;
    this.finished = true;
  }
}
return {
  position: this.path.sample(this.distance),
  tangent: this.path.tangent(this.distance),
  distance: this.distance,
  finished: this.finished
};

Il delta temporale viene limitato a 0,1 secondi. Questa scelta protegge dalle grandi discontinuità temporali dopo sospensioni o blocchi del tab, ma implica che durante gli scatti più lunghi il tempo di simulazione non recupera integralmente il tempo trascorso. È una scelta di stabilità visiva da distinguere da una simulazione che debba mantenere rigorosamente il tempo di orologio.

La tangente viene calcolata confrontando punti campionati a distanza ravvicinata lungo la polilinea. L’orientamento non dipende quindi dal solo segmento istantaneo, riducendo l’instabilità angolare nei punti di discontinuità. Il moto viene poi trasformato dalla base planimetrica al sistema locale geografico della mesh.

10. Visualizzare il percorso completato senza ricreare Graphics ogni frame

La UI offre tre modalità: Completed / remaining, Reveal as you move e Thin line. Il modulo non rimuove e ricrea continuamente i Graphic: conserva due oggetti per percorso completato e restante, aggiornandone la geometria e la visibilità quando serve. In una SceneView 3D questa scelta riduce il rischio di riavviare ripetutamente l’assemblaggio GPU degli stessi elementi.

// Strategia adottata in routeEditor.js, estratto
const remaining = new Graphic({ visible: false });
const completed = new Graphic({ visible: false });
progressLayer.addMany([remaining, completed]);

// Durante l’avanzamento: modificare gli stessi Graphics
remaining.visible = Boolean(rest);
if (rest) remaining.geometry = shape(rest);
completed.visible = Boolean(done);
if (done) completed.geometry = shape(done);

Inoltre l’aggiornamento del percorso viene evitato quando il progresso differisce meno di una piccola soglia (0,15 unità coordinate nella versione esaminata). Questo meccanismo di throttling, combinato con il riuso delle istanze, è utile per distinguere la frequenza di rendering da quella di aggiornamento delle geometrie cartografiche.

11. Activity Monitor: telemetria simulata, non sensori

Il dashboard visualizza progressione, tempo in movimento (pause escluse), distanza, velocità, passo al chilometro, ascesa, discesa e una stima di energia associata al livello MET. Le metriche non derivano da GPS, accelerometri o cardiofrequenzimetri reali. Il peso è regolabile e il campo battito cardiaco non è disponibile senza un sensore.

// Formula presente in activity.js
const integrated = metMinutes ?? metForSpeed(speed) * elapsed / 60;
const kcal = integrated * 3.5 * weight / 200;

// Dislivelli dai segmenti percorsi
const change = (p1[2] - p0[2]) * portion;
if (change > 0) ascent += change;
else descent -= change;

La formula MET è una stima illustrativa basata su ipotesi semplificate, non una misura fisiologica e non un valore clinico. La funzionalità dimostra invece un principio generale interessante: una simulazione grafica può produrre uno stato quantitativo coerente e leggibile da UI, mantenendo i calcoli in moduli puri e sottoponibili a test.

12. Diagnostica GPU: isolare RenderNode, framebuffer, proiezione e asset

Il sorgente conserva diverse modalità di diagnostica attivabili con parametri URL. Sono particolarmente importanti perché gli errori di integrazione grafica raramente generano eccezioni JavaScript esplicative: spesso si traducono soltanto in un oggetto invisibile o in un framebuffer vuoto.

ParametroCosa isola
?noNodeSceneView senza il RenderNode custom
?noThreeRenderNode attivo senza chiamate WebGL Three.js
?probeScrittura diretta su framebuffer mediante WebGL
?threeProbeRendering di una mesh diagnostica in spazio schermo
?geoProbeProiezione da posizione geografica a NDC
?worldProbeMesh davanti alla camera, indipendente dal posizionamento GIS
?placementProbeVerifica posizionamento della mesh nel mondo
?cubeOnlySostituzione del Soldier con un cubo diagnostico

La sequenza diagnostica ragionevole è: confermare prima che SceneView funzioni, poi che il nodo venga eseguito, poi che il framebuffer riceva output, poi che Three.js sappia disegnare un oggetto semplice e infine che la proiezione geografica del modello cada nel frustum. Soltanto dopo conviene indagare le gerarchie glTF e la sincronizzazione delle clip.

Strumenti complementari: pannello Performance e Memory del browser, numero di draw calls riportato da Three.js, controlli su gl.getError(), raccolta delle matrici della camera e WebGL frame capture quando disponibile. Un quadro completo richiede benchmark ripetibili su hardware e scenari definiti: il repository non espone benchmark comparativi che giustifichino numeri di FPS dichiarati come prestazioni garantite.

13. Condivisione di GPU state e ciclo di vita delle risorse

Quando si condivide un contesto tra due librerie, la gestione delle risorse non può essere affrontata come se ciascuna fosse proprietaria dell’intera superficie. Vanno considerate le risorse create dall’applicazione Three.js, l’eventuale distruzione del nodo e la non interferenza con gli oggetti gestiti dal SDK. Il nodo implementa un metodo destroy() che libera risorse diagnostiche, ferma l’animator e richiama renderer3.dispose().

In uno sviluppo destinato alla produzione conviene verificare sistematicamente ogni geometry, material, texture e risorsa glTF creata e non più usata, oltre agli eventi UI e ai listener del layer. In particolare non bisogna assumere che il solo distacco di un Object3D liberi ogni allocazione GPU associata: la strategia di disposal deve seguire le regole dell’engine impiegato.

14. Build e distribuzione: GitHub Pages e IIS

Il workflow .github/workflows/ci.yml lancia test e build, e pubblica l’output compilato dist/ su GitHub Pages dopo gli aggiornamenti a master (o dispatch manuale). Le pull request eseguono test e build senza pubblicare. È una distinzione utile fra validazione del codice e rilascio pubblico.

Su IIS si pubblica il contenuto di dist/ nella virtual directory desiderata, non la radice dei sorgenti Vite. Il progetto copia public/web.config come dist/web.config per il corretto tipo MIME dei file GLB. I riferimenti relativi ai modelli sono costruiti rispetto a document.baseURI, così da funzionare anche sotto un path applicativo.

// Risoluzione dell’asset nel codice corrente
const modelUrl = new URL(
  "models/Soldier.glb",
  document.baseURI
).href;

Verifiche pratiche dopo il rilascio: risposta HTTP 200 per models/Soldier.glb, tipo MIME appropriato, assenza di richieste a URL radice errati, disponibilità degli asset ArcGIS e nessun errore di caricamento in Console/Network. Per il build locale: npm test e npm run build.

15. Limiti attuali e direzioni di ricerca

Il progetto è un caso di studio efficace, ma non è un navigation engine, un’applicazione di fitness scientificamente validata o un framework di animazione caratteri general-purpose. Restano problemi tecnici aperti o migliorabili: foot clearance sul terreno, sincronizzazione fisica del passo, comportamento della velocità sotto frame drop, stime geodetiche nel caso di percorsi estesi, e compatibilità futura delle API di rendering custom.

Una possibile evoluzione è misurare l’effetto delle opzioni grafiche su draw calls e GPU frame time, ottimizzare l’animazione per più personaggi e introdurre controlli automated sul caricamento GLB e sulle conversioni di coordinate. WebGPU è un tema distinto: non si può presupporre che una SceneView ArcGIS basata su WebGL condivida automaticamente contesto, target o risorse con un renderer WebGPU. Una migrazione richiederebbe una diversa architettura d’integrazione.

16. Conclusioni

L’insegnamento principale di externalRendererSkeleton è che una scena 3D geografica non è semplicemente una canvas in cui inserire una mesh. È un sistema composto da pipeline di rendering, camera, transform space, terreno, asset animati e logica temporale. Separare questi livelli, renderli osservabili e riprodurre i problemi con probe semplici consente di costruire soluzioni solide anche quando ArcGIS e Three.js condividono il medesimo contesto WebGL.

Il caso dimostra anche il valore della toolchain moderna: ES modules, Vite, test eseguibili in Node.js, build riproducibile e pubblicazione statica. Il risultato è un laboratorio concreto per sviluppatori GIS che vogliono integrare rendering 3D personalizzato nella scena ArcGIS senza perdere controllo sul comportamento grafico.

Risorse e codice

NicoGIS Tech Weekly · 20 ottobre 2026. Analisi tecnica riferita alla versione del repository esaminata per la stesura. Le API, le dipendenze e i sorgenti possono evolvere. Le misure di attività incluse nell’esempio sono simulate.

lunedì 19 ottobre 2026

NicoGIS Tech Weekly — .NET 11 RC2, GeoAI Retrieval, Agent Skills e Durable AI Agents

Settimana del 12–18 ottobre 2026 · Pubblicazione: 19 ottobre 2026. Una release candidate, un cambio di rappresentazione nel runtime, una nuova frontiera per il retrieval geografico e un problema ancora irrisolto nei sistemi agentici: l'affidabilità delle operazioni esterne. Il punto comune non è il numero di nuove API, ma il luogo nel quale viene trasferita la complessità: dal compilatore al runtime, dalle coordinate agli embedding, dal programmatore alle convenzioni che guidano gli agenti, dall'esecuzione volatile alla persistenza dei workflow.

.NET 11 RC2: ottimizzare le astrazioni, non soltanto i cicli

Il 13 ottobre 2026 Microsoft ha rilasciato .NET 11 Release Candidate 2, con licenza go-live. Non è la versione finale; la compatibilità di applicazioni e dipendenze va verificata sui workload reali. Tra i cambiamenti specifici di RC2 figurano CborReader incrementale, API sperimentali di crittografia post-quantum, una nuova impostazione per la concorrenza iniziale HTTP/2 e modifiche comportamentali al file locking su iOS/tvOS. Fonti: annuncio .NET 11 RC2 (13 ottobre) · release notes delle librerie RC2.

Runtime async: il confine Task<T> non deve necessariamente diventare un oggetto

Il lavoro descritto da Stephen Toub in Performance Improvements in .NET 11 (15 settembre) è il contesto tecnico più importante per leggere la release. Il lowering classico di C# produce per ogni metodo async una state machine con MoveNext e un builder; un metodo A che attende B che attende C porta con sé confini logici spesso materializzati in task intermedi. In runtime async, invece, il compilatore emette IL più compatto e il runtime/JIT può usare una variante AsyncCall della chiamata per passare direttamente il risultato o collegare le continuation, evitando la materializzazione dei task intermedi quando non sono osservabili. Un'API che espone un Task continua a rispettare il suo contratto. Fonte: analisi originale del runtime e benchmark di Toub.

// Applicazione .NET 11: feature opt-in (non default)
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net11.0</TargetFramework>
    <Features>$(Features);runtime-async=on</Features>
  </PropertyGroup>
</Project>

La feature è opt-in per l'applicazione, mentre gran parte del framework incluso in .NET 11 è già compilato con questo meccanismo. Sono supportati Task, Task<T>, ValueTask e ValueTask<T>; async void, iteratori asincroni e alcuni task-like personalizzati mantengono il lowering tradizionale. Il guadagno dipende dalla forma della call chain e dalla quota di confini realmente eliminabili. Non equivale a una garanzia di zero allocazioni, né rende ValueTask obsoleto. Fonte: limiti e compatibilità di runtime async.

Analisi architetturale. Si riduce la penalità storica del codice fortemente composto in metodi asincroni brevi. Le astrazioni di dominio, i decorator, la telemetria e i livelli di servizio possono rimanere leggibili senza assumere che ogni await comporti necessariamente una nuova allocazione. Ma non è lecito estrapolare il vantaggio dalle catene sintetiche a un servizio dominato da database, rete o lock.

JIT, GC e JSON: tre ottimizzazioni con meccanismi distinti

Il JIT continua il lavoro di deabstraction: quando riesce a determinare il target effettivo di una chiamata virtuale, può devirtualizzare e rendere possibile l'inlining, aprendo la strada a constant propagation ed eliminazione di controlli superflui. Il beneficio non è “un'interfaccia più veloce” in astratto, ma una porzione più ampia di codice disponibile per l'ottimizzatore. Fonte: sezione JIT e devirtualization.

Sul fronte GC, il post documenta interventi alla gestione di metadata e handle durante le collection generazionali e ottimizzazioni Arm64: nei casi che sfruttano molti ConditionalWeakTable e dependent handles, l'età generazionale associata ai metadata condiziona quanta struttura il GC deve riesaminare. I microbenchmark pubblicati mostrano differenze importanti in configurazioni artificiali con centinaia di migliaia di handle; non sono un moltiplicatore di throughput applicativo. Fonte: sezione GC e dependent handles.

System.Text.Json sfrutta nuove scansioni basate su SearchValues per individuare byte da escapare o whitespace da saltare; una modifica al writer consente inoltre al JIT di dimostrare in anticipo che una sequenza escapata entra nel buffer, evitando bounds check ripetuti. Nel benchmark di Microsoft su una stringa volutamente satura di caratteri da escapare, la serializzazione passa da 30,83 μs (.NET 10) a 7,875 μs (.NET 11). È un caso di test mirato, non una previsione generale per i JSON REST. Fonte e codice riproducibile: JSON writer e reader in .NET 11.

HTTP/2: proteggere il burst prima del primo SETTINGS

Una novità di RC2 riguarda SocketsHttpHandler.InitialHttp2MaxConcurrentStreams. Prima che arrivi il frame SETTINGS del server, un client HTTP/2 non conosce ancora il limite effettivo di stream concorrenti della nuova connessione. La proprietà permette di impostare una soglia iniziale più bassa del default documentato di 100; dopo il frame SETTINGS prevale il valore annunciato dal peer. Un limite inferiore precedentemente osservato dallo stesso host può ridurre ulteriormente l'avvio. Fonte: API e semantica nelle release notes RC2.

using System.Net;
using System.Net.Http;

var handler = new SocketsHttpHandler
{
    InitialHttp2MaxConcurrentStreams = 32
};

using var client = new HttpClient(handler);
using var request = new HttpRequestMessage(HttpMethod.Get, "https://example.org/")
{
    Version = HttpVersion.Version20,
    VersionPolicy = HttpVersionPolicy.RequestVersionExact
};
using var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();

Valutazione. È un controllo di admission per il burst di avvio, non una sostituzione di rate limiting, backpressure e limiti di connessione. Per servizi con centinaia di richieste simultanee su connessioni appena aperte, questa impostazione merita un test che includa handshake, RTT, frame SETTINGS e numero di connessioni effettive; misurare soltanto il p50 su connessioni calde non basta.

Cryptography: “post-quantum” non significa interoperabilità automatica

Le nuove API sperimentali CompositeMLKem e Hpke richiedono controlli di supporto sulla piattaforma e opt-in agli avvisi sperimentali (SYSLIB5006 e SYSLIB5009). CompositeMLKem combina contributi classici e post-quantum; Hpke in RC2 implementa suite classiche DHKEM, non ancora suite HPKE ML-KEM o hybrid ML-KEM. Il supporto ML-KEM per destinatari CMS è documentato in RC2 per piattaforme non Windows. Fonte: precisazioni crittografiche della RC2.

Analisi. Nella progettazione di protocolli federati e applicazioni che scambiano chiavi, l'adozione dipende da profili standardizzati, suite negoziate, librerie delle controparti e policy di certificazione. Un'API sperimentale nel BCL non autorizza a modificare unilateralmente i wire format di OIDC, TLS o CMS.

Toolchain watch. Il 15 ottobre Microsoft ha presentato in preview dotnetup per macOS/Linux: installazione user-local di SDK/runtime side-by-side e rispetto dei vincoli global.json. È un miglioramento dell'ergonomia degli ambienti, non una sostituzione del pinning degli SDK nelle pipeline CI. Fonte: Introducing dotnetup (15 ottobre).

GeoAI: somiglianza semantica e vicinanza geografica non sono la stessa metrica

La documentazione ArcGIS Pro sugli embedding chiarisce un dettaglio infrastrutturale cruciale: la feature class risultante contiene un campo Embedding di tipo BLOB, con valori IEEE-754 Float32 little-endian contigui. Un vettore di dimensione D occupa esattamente 4 × D byte; quando transita in un Feature Service JSON viene veicolato in Base64. Non è un array di double e non contiene header o informazioni di modello: dimensione, versione e semantica del modello vanno gestite esternamente. Fonti: funzionamento del tool Generate Embeddings · formato BLOB e convenzioni di trasporto.

Il motore del retrieval: query polygon, aggregazione e coseno

Il tool Find Similar Features Using Embeddings prende una feature class di embedding e feature di query poligonali. Identifica gli embedding che intersecano i poligoni, ne calcola il vettore medio e confronta tale query aggregata con i candidati utilizzando la cosine similarity; la soglia filtra i risultati e l'output è una feature class. La ricerca opera sui vettori associati alle celle/feature, non sui pixel dell'immagine visualizzata nella mappa. Per la ricerca estesa Esri propone il tool di geoprocessing invece dell'esplorazione interattiva. Fonte: How Find Similar Features Using Embeddings works.

Query polygon
    | spatial overlap
    v
{e1, e2, ..., ek}    ->    q = mean(e1, ..., ek)
                                     |
                                     v
                         cosine(q, candidate_i)
                                     |
                        threshold + feature output
                                     |
                                     v
                            GIS result layer

Questo meccanismo stabilisce una relazione di similarità nel latent space del modello, non una relazione di prossimità euclidea o topologica. Due aree lontane possono essere semanticamente simili; due aree contigue possono essere molto diverse per uso del suolo, tipologia edilizia o contesto ambientale. Aumentare la soglia del coseno non equivale a restringere un buffer geografico.

Analisi: spatial-semantic retrieval a due stadi. Per casi d’uso con vincoli spaziali obbligatori, conviene eseguire prima un filtro geografico esatto (intersezione, buffer, confine amministrativo, area di competenza) e poi il ranking vettoriale all’interno dei candidati ammissibili. Una combinazione pesata tra distanza geografica e coseno è possibile, ma è una politica applicativa da calibrare: non è la semantica predefinita del tool Esri.

Filtro di ammissibilità:    A = {i : intersects(geometry_i, AOI)}
Ranking all'interno di A:  s_i = cosine(q, embedding_i)
Risultati:                  topK({i in A : s_i >= soglia})

Alternativa analitica (non API Esri):
score_i = alpha * cosine_i + (1 - alpha) * exp(-distance_km_i / tau)
con alpha in [0, 1], tau > 0 e distanze in km

Il ranking misto necessita di normalizzazione, scelta del parametro di decadimento spaziale e validazione su query reali. In particolare, la media di embedding all'interno di un poligono attenua configurazioni multimodali: un'area contenente sia infrastrutture industriali sia vegetazione può generare una query che non rappresenta bene nessuna delle due classi. Un test con più query poligonali o cluster distinti può essere preferibile alla media cieca.

GeoAI interpretabile: 256 dimensioni non sono 256 variabili leggibili

Nel workflow Esri pubblicato il 6 ottobre, gli USA Geodemographic Embeddings prodotti dal GDFM sono organizzati in quattro blocchi da 64 dimensioni: housing/household, environment, American Community Survey e age/race census. AutoML con SHAP misura l'importanza media assoluta delle singole dimensioni rispetto a uno specifico task predittivo; sommare le importanze di un blocco produce una attribuzione aggregata descrittiva, non una stima causale e nemmeno un vero SHAP di gruppo. Fonte: Interpret embeddings using AutoML and SHAP (6 ottobre).

Ad esempio, un valore elevato di embedding_137 non permette di concludere che la dimensione codifichi direttamente il reddito: è un fattore latente all'interno della vista ACS. Analogamente, la somma di mean(abs(SHAP)) non coincide necessariamente con il valore di mean(abs(sum(SHAP))). Per attribuire valore predittivo incrementale a un segmento occorrono studi di ablation, retraining o permutazioni congiunte e split spaziali adeguati. Fonte: avvertenze interpretative Esri.

Interoperabilità C# e costo dei dati

Un decoder indipendente da architettura può ricostruire il formato descritto da Esri senza assumere l'endianness della macchina. Il codice seguente è un esempio .NET senza dipendenze Esri; expectedDimensions deve venire dai metadata del modello effettivamente usato.

using System;
using System.Buffers.Binary;

static float[] ReadEmbedding(ReadOnlySpan<byte> blob, int expectedDimensions)
{
    if (expectedDimensions <= 0 || blob.Length != checked(expectedDimensions * 4))
        throw new ArgumentException("Unexpected embedding payload size");

    var result = new float[expectedDimensions];
    for (int i = 0; i < result.Length; i++)
    {
        int bits = BinaryPrimitives.ReadInt32LittleEndian(blob.Slice(i * 4, 4));
        result[i] = BitConverter.Int32BitsToSingle(bits);
    }
    return result;
}

Valutazione. Con 256 dimensioni, il payload grezzo per feature è 1.024 byte; un milione di feature corrisponde a circa 1,024 GB decimal di soli valori vettoriali, prima di geometrie, indici, overhead geodatabase, Base64 e copie in memoria. Questa è una semplice stima D×4×N, non un benchmark del motore Esri. È necessario conservare il model identity insieme ai dati ed evitare ranking incrociati fra versioni dello stesso encoder senza valutazione di compatibilità. Documentazione: storage degli embedding · tool e requisiti CPU/GPU/licenze.

Experience Builder: un Agent Skill è una specifica operativa, non una garanzia di correttezza

Esri ha pubblicato a settembre un Agent Skill open source per lo sviluppo di custom widget ArcGIS Experience Builder. Contiene regole di struttura, riferimenti alle API, separazione fra settings e runtime, indicazioni su data source, map view, tema, traduzioni e skeleton. La guida Esri descrive l'inserimento del pacchetto skills/arcgis-exb-widget-dev nelle cartelle riconosciute da agenti compatibili; l'estensione non sostituisce il progetto Developer Edition né compila automaticamente ogni widget. Fonti: articolo tecnico Esri (14 settembre) · repository SDK Resources e skill ufficiale.

Natural-language requirement
          |
          v
    Coding agent
       /      \
Skill references   Repository constraints
       \      /
       Generated widget
      /    |     \
manifest config  runtime/setting
       |       |       |
       +---- build + tests ----+
                 |
        runtime verification

Nel codice reale un widget deve rispettare le convenzioni del repository di estensioni, collocarsi sotto client/your-extensions/widgets/ (o repository equivalente), pubblicare un manifest.json corretto e distinguere configurazione persistita da codice di esecuzione. Un agente che genera un componente React visivamente plausibile ma aggira DataSourceComponent, JimuMapViewComponent o le API di configurazione non ha risolto il problema di integrazione. Fonti: getting started with widget development · contratto del manifest.

Esiste anche un vincolo di ciclo di vita verificabile: Esri ha annunciato il pensionamento dei Maps SDK for JavaScript widgets utilizzati dai custom widget nelle versioni interessate a partire da Q1 2027, raccomandando la migrazione a web components. Non significa che ogni custom widget Experience Builder smetterà di funzionare: la criticità riguarda quelli che dipendono dalle vecchie API widget del Maps SDK. Esri indica inoltre il passaggio a React 19 in Experience Builder 1.19 e successivi. Fonte: avviso ufficiale di retirement e perimetro di impatto.

Analisi architetturale. Uno skill di sviluppo diventa parte della supply chain: va versionato e verificato rispetto alla versione di Experience Builder effettivamente distribuita. Il controllo automatico deve verificare dipendenze Maps SDK deprecate, API Jimu usate, manifest, compatibilità del builder, bundle e smoke test in browser. Un agent skill riduce l’incertezza sulle convenzioni, ma non sposta la responsabilità della qualità dal maintainer all’agente.

Durable AI Agents: replay deterministico, side effect non deterministici

Il 12 ottobre Microsoft ha pubblicato una lettura concreta di Durable Task con scenari di provisioning Azure, approvazione umana, pagamenti, webhook e incident management. Il riferimento usato negli esempi è il Durable Task .NET SDK v1.26.0 con Durable Task Scheduler. La lezione architetturale è precisa: una cronologia persistente consente la ricostruzione deterministica del flusso, ma non garantisce exactly-once per le operazioni sui sistemi esterni. Fonte: Durable Task in the Real World (12 ottobre).

Incident -> Collect evidence -> Generate proposal -> Save proposal identity
                                                |
                                        Human approval
                                                |
                          proposal hash + authorized decision
                                                |
                                Execute remediation (idempotency key)
                                                |
                                   Read-back / reconciliation
                                                |
                                          Verification

In un workflow agentico la generazione del modello è un side effect: un retry può produrre un piano differente. L'output autorizzato deve avere identità immutabile e fingerprint dei parametri. Se un worker termina dopo che un provider ha eseguito l'operazione ma prima che la sua risposta sia registrata nell'orchestrazione, la ripartenza non può presumere che l'operazione sia fallita. Occorre interrogare il sistema esterno con una chiave stabile, riconciliare lo stato ed eventualmente marcare NeedsIntervention. Fonte: failure windows e riconciliazione documentati da Microsoft.

Analisi. Qui si separano tre livelli spesso confusi: deterministic replay dell'orchestrazione, idempotency dell'operazione applicativa e authorization dell'effetto richiesto. Un orchestratore può riprodurre la stessa decisione di workflow; non può rendere idempotente un pagamento, una DELETE, un deployment ARM o un tool MCP privo di meccanismi di deduplicazione e read-back. Nei sistemi agentici questa separazione è un confine di sicurezza, non soltanto una tecnica di recovery.

LLM/API lifecycle: il breaking change più costoso può arrivare dal modello, non dal codice

Nella settimana appena conclusa sono documentati due cambiamenti operativi rilevanti nell'API OpenAI. Il 14 ottobre è stata comunicata la deprecazione di snapshot GPT-4o, GPT-4o mini e GPT-4.1, con rimozione indicata al 14 gennaio 2027 e migrazione raccomandata verso gpt-6-luna. Il 15 ottobre GPT-Live ha aggiunto l'integrazione nativa con Decisions: decisioni attivabili tramite delega o dai delta di trascrizione in input/output. Fonti: tabella ufficiale delle deprecazioni · changelog API del 15 ottobre.

La Decisions API, introdotta in beta il 6 ottobre, espone classificazioni/valutazioni tipizzate (predicate, choice, score) e al momento documenta gpt-6-luna come modello supportato. Questo pattern è diverso dal tool calling generale della Responses API: è un percorso per decisioni strutturate su input testuali o visivi, non un runtime universale per tool e workflow. Fonte: riferimento POST /decisions.

Voice conversation (GPT-Live)
       |
 input/output transcript deltas
       |
   Decision evaluation
       |
 typed predicate / choice / score
       |
 policy checks + application authority
       |
      allowed action

Analisi. L'integrazione voice/decisions può ridurre il lavoro applicativo di instradamento dei segnali in scenari vocali a bassa latenza, ma crea un tema di consistenza: i transcript delta sono aggiornamenti intermedi e una decisione statistica non conferisce autorizzazione. Le azioni con effetti permanenti devono restare sotto controllo di un backend con identità, policy, conferma e journal. Anche con delega a Responses, le credenziali e il modello di autorizzazione devono restare nell'applicazione. Fonti: delegation, tools e responsabilità applicative · schema delle sessioni e trigger Decisions.

Per la migrazione dei modelli, il piano corretto non è sostituire una stringa in configurazione: occorre misurare regressioni su schemi JSON, tool use, politiche di rifiuto, latenza p95/p99, costo per task riuscito e probabilità di escalation. Un modello sostitutivo nominalmente consigliato dal provider non prova l'equivalenza sul dominio applicativo. È necessaria una suite di eval ripetibile con rollback e osservabilità per versione.

Technical Takeaways

  • Runtime async. Misurare le call chain reali: meno state machine materializzate non equivale a meno attese di I/O, né garantisce compatibilità perfetta di tooling non aggiornato.
  • .NET 11 RC2. HTTP/2, CBOR e crittografia sperimentale riguardano confini di protocollo: le prove di interoperabilità contano più dell'elenco di API.
  • GeoAI. Separare filtro spaziale e ranking semantico; conservare metadata del modello, definizione di soglia e unità geografiche insieme ai risultati.
  • SHAP. Spiegare un modello basato su embedding non significa recuperare causalità né il significato univoco delle coordinate latenti.
  • Experience Builder. Skills, repository conventions, version pinning e CI compongono un unico contratto tecnico; la migrazione ai web components è una verifica concreta di questo contratto.
  • Agenti durevoli. Replay deterministico, idempotenza e permessi sono tre problemi distinti. Il fallimento da testare è quello tra l'effetto esterno e il suo acknowledgement.
  • Model lifecycle. La scadenza di uno snapshot è un evento di release engineering: migrare con eval, canary, metriche e piano di ripristino.

Lab della settimana

Lab 1 — .NET 11: verificare la riduzione del lowering async

Prerequisiti: SDK .NET 11 RC2, shell e un progetto console. Copiare Program.cs qui sotto e aggiungere la proprietà Features in forma condizionale nel .csproj. Compilare due volte in Release, confrontando la dimensione della DLL; successivamente eseguire lo stesso carico con e senza opt-in per isolare throughput e allocazioni. La differenza di dimensione è osservabile direttamente, quella prestazionale va misurata per il workload. Protocollo e benchmark estesi: sorgenti originali dei benchmark · SDK RC2.

<PropertyGroup>
  <TargetFramework>net11.0</TargetFramework>
  <Features Condition="'$(RuntimeAsync)' == 'true'">$(Features);runtime-async=on</Features>
</PropertyGroup>
Console.WriteLine(await Steps.A());

static class Steps
{
    public static async Task<int> A() => await B();
    private static async Task<int> B() => await C();
    private static async Task<int> C()
    {
        await Task.Yield();
        return 42;
    }
}
dotnet build -c Release -p:RuntimeAsync=false -o classic --no-incremental
dotnet build -c Release -p:RuntimeAsync=true  -o runtime --no-incremental
# Confrontare la dimensione delle DLL nei due output.

Criterio di successo: la build si conclude in entrambe le configurazioni, l'output del programma è identico e la DLL viene confrontata separatamente dai file di runtime e dalle dipendenze. Con soli tre metodi il delta può essere modesto; il post Microsoft mostra anche un campione a dieci layer.

Lab 2 — ArcGIS Pro: retrieval vettoriale su query poligonale

Prerequisiti: ArcGIS Pro con licenza Advanced, una feature class di embedding compatibili e un layer poligonale che rappresenti la query. La generazione preventiva degli embedding richiede un model definition .emd/.dlpk e framework deep learning, con Image Analyst per embedding da immagini. I parametri qui sotto corrispondono all'API documentata. Fonti: Generate Embeddings Using AI Models · Find Similar Features Using Embeddings.

import arcpy

embedding_features = r"C:\lab\gis.gdb\embeddings"
query_features = r"C:\lab\gis.gdb\query_polygon"
output_features = r"C:\lab\gis.gdb\similar_results"

arcpy.geoai.FindSimilarFeaturesUsingEmbeddings(
    embedding_features, query_features, output_features, 0.75
)

Controlli: ripetere la query cambiando soltanto poligono e soglia; confrontare risultati ad alta similarità con un buffer geografico. Se una feature risulta semanticamente simile ma lontana, non è necessariamente un errore: è il comportamento che l'esperimento deve distinguere. Documentare modello, dimensione D, extent, dataset e percentuale di candidati che superano la soglia.

Lab 3 — Agent Skill per Experience Builder: A/B test sulle convenzioni

Usare lo stesso requisito e lo stesso coding agent in due directory pulite della stessa versione di Experience Builder Developer Edition: una senza skill e una con la cartella skills/arcgis-exb-widget-dev di Esri installata secondo le istruzioni dell'agente. Implementare un widget che scelga una feature layer nelle impostazioni e mostri al runtime il conteggio delle feature filtrate nella mappa attiva. Non cambiare prompt, agent model, dipendenze o versione del builder tra le prove. Fonti: tutorial dell’Agent Skill · repository ufficiale · guida ai custom widget.

Metriche: build pass/fail; errori di manifest; uso corretto delle API data source e Jimu map view; accessibilità e traduzioni; numero di correzioni manuali; numero di iterazioni dell'agente; test del widget nell'app pubblicata. Ripetere su più run, registrando anche i fallimenti: un singolo risultato riuscito non è una misura dell'affidabilità.

Riferimenti tecnici primari: Microsoft .NET 11 RC2 · Stephen Toub — Performance Improvements in .NET 11 · Esri — Generate Embeddings internals · Esri — Similar Features internals · Esri — AutoML/SHAP · Esri — Experience Builder Agent Skill · Microsoft — Durable Task · OpenAI API changelog.

venerdì 16 ottobre 2026

Osservabilità degli agenti AI: Microsoft Agent Framework, OpenTelemetry e Azure Monitor

NicoGIS Tech Weekly | AI Engineering · .NET · Distributed Tracing · Azure

Osservabilità degli agenti AI: Microsoft Agent Framework, OpenTelemetry e Azure Monitor

Come ricostruire il percorso di esecuzione di un agente, correlare chiamate a LLM e tool, misurare latenza e token e individuare i colli di bottiglia nei sistemi agentici distribuiti.

Deep dive tecnico · 16 ottobre 2026 · Livello avanzato

Un agente AI non esegue semplicemente una richiesta verso un modello linguistico. Può pianificare operazioni, interrogare sistemi esterni, invocare strumenti, recuperare informazioni e ripetere le chiamate al modello prima di produrre una risposta. Questo cambia radicalmente il problema dell'osservabilità.

In una normale applicazione distribuita, il tracing permette di ricostruire il percorso di una richiesta HTTP attraverso servizi, database e code di messaggi. Nei sistemi agentici occorre estendere lo stesso modello a operazioni non necessariamente deterministiche, il cui numero e ordine possono variare tra un'esecuzione e l'altra.

La domanda non è soltanto quanto tempo abbia impiegato una richiesta, ma cosa abbia effettivamente fatto l'agente durante quel tempo.

1. Dall'osservabilità applicativa all'osservabilità agentica

Si consideri un agente incaricato di analizzare un documento aziendale e produrne una sintesi. Potrebbe essere necessaria una prima inferenza per definire il piano, una ricerca, il download del documento e una seconda inferenza per la risposta finale.

Una singola richiesta produce più operazioni correlate. Senza distributed tracing, eventi separati nei log non consentono sempre di ricostruire con certezza l'esecuzione. Con una trace distribuita si possono osservare modello invocato, tool, dipendenze HTTP e database, token, errori, retry e punti di maggiore latenza.

2. OpenTelemetry GenAI Semantic Conventions

OpenTelemetry organizza la telemetria in traces, metrics e logs. Le GenAI Semantic Conventions definiscono vocabolario e attributi per descrivere operazioni di modelli generativi, agenti e tool. Esempi di nomi di operazione includono:

OperazioneSignificato
invoke_agentInvocazione di un agente
chatInterazione conversazionale con un modello
execute_toolEsecuzione di uno strumento
invoke_workflowEsecuzione di un workflow agentico, se supportata dall'instrumentation adottata
planPianificazione, nei sistemi che la rappresentano esplicitamente
retrievalRecupero di informazioni, dove previsto dall'instrumentation

Alcuni attributi utili per analizzare un'esecuzione sono:

gen_ai.operation.name
gen_ai.agent.name
gen_ai.provider.name
gen_ai.request.model
gen_ai.tool.name
gen_ai.usage.input_tokens
gen_ai.usage.output_tokens

Attenzione alla compatibilità: le convenzioni agentiche sono un'area in evoluzione; denominazioni, stabilità e adozione degli attributi possono differire in base alla versione delle specifiche e dei pacchetti. Le operazioni sopra elencate non implicano che ogni runtime le emetta automaticamente. Verificare sempre le convenzioni ufficiali per gli agenti e quelle relative alle operazioni GenAI.

3. L'architettura: da Agent Framework ad Azure Monitor

Agent Framework e i client strumentati producono segnali. OpenTelemetry raccoglie e correla i dati. Gli exporter li inviano ai backend. Separare questi livelli riduce l'accoppiamento tra orchestrazione dell'agente e prodotto di monitoraggio.

4. Laboratorio .NET: configurare tracing e instrumentation

Un esempio ASP.NET Core può utilizzare la distribuzione Microsoft OpenTelemetry. Il comando per installare il pacchetto è:

dotnet add package Microsoft.OpenTelemetry

La configurazione esatta dipende dalla versione del pacchetto. Nelle versioni che espongono UseMicrosoftOpenTelemetry e ExportTarget, uno schema di configurazione è il seguente:

using Microsoft.OpenTelemetry;

var builder = WebApplication.CreateBuilder(args);

builder.UseMicrosoftOpenTelemetry(options =>
{
    options.Exporters =
        ExportTarget.AzureMonitor |
        ExportTarget.Otlp;
});

var app = builder.Build();
app.MapGet("/health", () => Results.Ok());
app.Run();

La destinazione Azure Monitor utilizza la connection string configurata nell'ambiente:

APPLICATIONINSIGHTS_CONNECTION_STRING

Per OTLP, quando usato, è possibile configurare:

OTEL_EXPORTER_OTLP_ENDPOINT

Questa configurazione abilita la pipeline, ma non crea alcun agente. Per generare span agentici occorre istanziare e invocare componenti effettivamente strumentati.

Instrumentare chat client e agente

Le versioni di Microsoft Agent Framework basate sull'API ChatClientAgent possono essere strumentate tramite UseOpenTelemetry() per il client e WithOpenTelemetry() per l'agente:

using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

// chatClient è un IChatClient già configurato.
IChatClient instrumentedClient = chatClient
    .AsBuilder()
    .UseOpenTelemetry()
    .Build();

var agent = new ChatClientAgent(
    instrumentedClient,
    name: "DocumentAnalyzer",
    instructions:
        "Analyze documents and report verified findings."
).WithOpenTelemetry();

Frammento focalizzato sull'instrumentation, non un progetto completo eseguibile. Microsoft Agent Framework è in evoluzione: controllare overload, namespace, pacchetti e metodo di creazione dell'agente rispetto alla versione NuGet installata.

Span personalizzati con ActivitySource

Per correlare operazioni applicative si può utilizzare System.Diagnostics.ActivitySource:

using System.Diagnostics;

public static class AgentTelemetry
{
    public const string SourceName =
        "NicoGIS.TechWeekly.AgentDemo";

    public static readonly ActivitySource Source =
        new(SourceName);

    public static async Task<T> TraceDocumentLoadAsync<T>(
        Func<Task<T>> loadDocument)
    {
        using var activity = Source.StartActivity(
            "document.load", ActivityKind.Internal);

        try
        {
            var result = await loadDocument();
            activity?.SetTag("document.operation", "load");
            return result;
        }
        catch (Exception ex)
        {
            activity?.SetStatus(
                ActivityStatusCode.Error, ex.Message);
            activity?.AddException(ex);
            throw;
        }
    }
}

La ActivitySource personalizzata deve essere aggiunta alla configurazione del tracing:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
        tracing.AddSource(AgentTelemetry.SourceName));

Quando la nuova attività viene aperta in presenza di un contesto Activity.Current valido, il runtime stabilisce la relazione con lo span corrente. Le informazioni applicative possono usare attributi dedicati; le chiavi gen_ai.* vanno invece utilizzate nel rispetto delle convenzioni ufficiali.

5. TraceId, SpanId e propagazione del contesto

La correlazione si fonda sul contesto distribuito e, frequentemente, sugli header W3C Trace Context.

ElementoRuolo
TraceIdIdentifica l'intera esecuzione distribuita.
SpanIdIdentifica una singola operazione.
ParentSpanIdCollega lo span a quello padre.
traceparentPropaga il contesto tra servizi.
tracestateVeicola informazioni supplementari del contesto.
HTTP POST /analyze
└── invoke_agent DocumentAnalyzer
    ├── chat (planning)
    ├── execute_tool ReadDocument
    │   └── HTTP GET /api/documents/42
    │       └── SQL SELECT ...
    └── chat (synthesis)

Con le instrumentation HTTP corrette, il contesto attraversa il confine tra client e servizio ricevente. Nel caso dei tool MCP non bisogna dare per scontata la propagazione: trasporto, client, server e intermediari devono supportarla. Quando manca, due segmenti della stessa esecuzione possono comparire come trace indipendenti.

6. Azure Monitor: analisi con KQL

In Application Insights collegato a un workspace Log Analytics, le dipendenze possono essere interrogate tramite la tabella AppDependencies. Per ricostruire gli eventi relativi a un OperationId:

let traceId = "INSERT_TRACE_ID";
AppDependencies
| where OperationId == traceId
| project
    TimeGenerated,
    Name,
    DependencyType,
    DurationMs,
    Success,
    OperationId,
    ParentId,
    Id,
    Properties
| order by TimeGenerated asc

Per identificare le dipendenze con maggiore latenza al 95° percentile:

AppDependencies
| where TimeGenerated > ago(24h)
| summarize
    Calls = count(),
    Failures = countif(Success == false),
    AvgMs = avg(DurationMs),
    P95Ms = percentile(DurationMs, 95)
    by Name, DependencyType
| order by P95Ms desc

Queste query sono punti di partenza: il mapping delle operazioni GenAI in tabelle e proprietà di Application Insights dipende dall'exporter e dalla versione in uso. È quindi necessario verificare che gli span agentici siano realmente esportati in AppDependencies e che i loro attributi siano disponibili in Properties.

Le metriche GenAI consentono inoltre di osservare durata e consumo, ad esempio con nomi come gen_ai.client.operation.duration e gen_ai.client.token.usage quando emessi dalle librerie utilizzate. Le unità di misura non vanno confuse: DurationMs è espresso in millisecondi, mentre le convenzioni GenAI possono esprimere le durate in secondi.

7. Latenza, token e costo economico

Due esecuzioni dello stesso compito possono produrre una risposta comparabile con un consumo di risorse molto diverso.

IndicatoreEsecuzione AEsecuzione B
Chiamate LLM26
Tool eseguiti14
Token totali3.20011.800
Durata2,4 s8,7 s
Confronto dei token utilizzati (scenario illustrativo)
Esecuzione A · 3.200
Esecuzione B · 11.800

La seconda esecuzione utilizza circa 3,7 volte i token della prima. Questo non indica automaticamente un errore: può dipendere dalla complessità del compito. La trace permette però di verificare retry, retrieval ridondanti, contesti eccessivamente ampi e output dei tool poco pertinenti.

Il costo monetario non coincide con i token totali. Una stima credibile distingue token in ingresso e in uscita, modello, eventuale caching, tariffe, strumenti e risorse esterne.

8. Tracing e valutazione della qualità

L'osservabilità tecnica non equivale a una verifica della correttezza dei risultati. Un sistema agentico maturo dovrebbe rispondere a quattro domande complementari.

DimensioneDomanda
TecnicaDove si trova il collo di bottiglia?
EconomicaQuanto costa completare la richiesta?
ComportamentalePerché sono stati utilizzati determinati strumenti?
QualitativaIl risultato è corretto, pertinente e verificabile?

Il tracing è essenziale per ricostruire la traiettoria esecutiva. La valutazione della qualità richiede invece dataset di test, verifiche dei risultati e controlli sull'uso degli strumenti. Il fatto che una risposta sia stata generata senza errori tecnici non dimostra che sia corretta.

9. Sicurezza e privacy della telemetria

Prompt, risposte, argomenti dei tool e documenti possono contenere informazioni personali, riservate o segreti. Le funzionalità che acquisiscono contenuti sensibili, come EnableSensitiveData dove disponibili, non dovrebbero essere abilitate indiscriminatamente in produzione.

  • Redigere o escludere PII, credenziali e dati riservati prima dell'esportazione.
  • Applicare retention e sampling proporzionati alle necessità diagnostiche.
  • Limitare l'accesso ai workspace e alle dashboard.
  • Preferire identificatori tecnici non sensibili per la correlazione.
  • Controllare la cardinalità di tag e dimensioni metriche.

Inserire prompt interi e identificatori ad alta cardinalità nelle metriche può comportare rischi di sicurezza e costi elevati. I contenuti delle conversazioni richiedono un trattamento diverso da normali misure di durata o conteggi.

10. Conclusioni: verso l'osservabilità delle traiettorie agentiche

La principale evoluzione consiste nel passaggio dal monitoraggio di singole chiamate ai modelli all'analisi di traiettorie di esecuzione: la sequenza concreta di inferenze, tool e dipendenze attraversata dall'agente per raggiungere un risultato.

Microsoft Agent Framework fornisce i componenti di orchestrazione; OpenTelemetry introduce un modello interoperabile di tracing, metriche e log; Azure Monitor rende possibile la correlazione e l'analisi in produzione. Per ottenere un sistema affidabile occorre completare questa telemetria con controlli sulla qualità delle risposte e sulle decisioni operative.

Un agente osservabile non è automaticamente affidabile. Un agente non osservabile, però, è molto più difficile da rendere affidabile.

Fonti e documentazione

mercoledì 14 ottobre 2026

.NET 11 RC2, C#, sicurezza e architettura

La crittografia post-quantistica non richiede di aspettare l'arrivo di un computer quantistico capace di violare RSA. Il rischio harvest now, decrypt later riguarda i dati cifrati oggi e conservati per essere decifrati in futuro. Con .NET 11 RC2 Microsoft amplia le primitive crittografiche disponibili in C#, introducendo API sperimentali per Composite ML-KEM e HPKE. Per comprendere la portata del cambiamento occorre distinguere algoritmi, protocolli, formati di chiave e disponibilità effettiva delle implementazioni native.

1. Perché RSA ed ECC richiedono una transizione

RSA ed ECC basano la propria sicurezza computazionale su problemi matematici per i quali l'algoritmo quantistico di Shor, eseguito su hardware sufficientemente potente e affidabile, offre una minaccia sostanziale. Gli algoritmi simmetrici come AES non sono colpiti nello stesso modo: Grover comporta un vantaggio quadratico teorico nella ricerca esaustiva, non una rottura equivalente. Questo spiega perché la migrazione interessa soprattutto scambio di chiavi e firme digitali.

2. Il punto di partenza è .NET 10

.NET 10 ha introdotto le classi MLKem, MLDsa, SlhDsa e CompositeMLDsa in System.Security.Cryptography. ML-KEM è un meccanismo di incapsulamento di chiavi, standardizzato da NIST FIPS 203; ML-DSA e SLH-DSA sono schemi di firma, rispettivamente FIPS 204 e FIPS 205. Le classi e i metodi possono avere stati di maturità differenti: alcune API restano marcate [Experimental].

3. KEM non significa cifratura dei documenti

Un KEM produce un segreto condiviso tra mittente e destinatario. Il segreto alimenta normalmente una KDF, dalla quale si ricava una chiave per un algoritmo simmetrico autenticato come AES-GCM. Il KEM non è un sostituto diretto di AesGcm.

Recipient public key
       |
       v
   KEM Encapsulate --------> Encapsulation ciphertext
       |
       v
   Shared secret
       |
       v
  Key derivation
       |
       v
     AES-GCM ------------> Encrypted payload + authentication tag

Recipient private key + encapsulation ciphertext
       |
       v
   KEM Decapsulate --> same shared secret

4. Composite ML-KEM: il nuovo tassello di .NET 11

La RC2 introduce API sperimentali per combinare un meccanismo post-quantum ML-KEM con un meccanismo classico. L'obiettivo dei KEM compositi è ottenere una costruzione che mantenga proprietà di sicurezza anche qualora una delle componenti risulti vulnerabile, secondo le ipotesi e il combiner specificati. Non è corretto concatenare arbitrariamente due segreti e chiamare il risultato 'hybrid secure': il formato, il contesto e la derivazione devono essere definiti da una specifica interoperabile.

Il supporto dipende dalla piattaforma, dai parametri algoritmici e dallo stato delle API sperimentali. Prima di introdurle in un protocollo occorre fissare una versione, verificare gli algoritmi disponibili e testare interoperabilità e downgrade resistance.

5. HPKE: un framework, non un singolo algoritmo post-quantum

Hybrid Public Key Encryption (HPKE) è un framework di cifratura che combina KEM, KDF e AEAD. Il nome 'Hybrid' non significa automaticamente 'classico + post-quantum': HPKE può essere implementato con suite completamente classiche. Nella RC2 il supporto .NET è sperimentale e le suite post-quantum ML-KEM/ibride non risultano ancora implementate. È quindi scorretto presentare la sola presenza di Hpke come prova che un'applicazione utilizzi già cifratura post-quantum end-to-end.

6. Firme post-quantum e certificati

ML-DSA e SLH-DSA rispondono a un problema differente dal KEM: autenticità e integrità. La transizione interessa certificati X.509, CA, trust store, TLS, HSM, firme di software e documenti. Un certificato firmato con un nuovo algoritmo non diventa utilizzabile ovunque soltanto perché il runtime conosce la primitiva: il sistema operativo, la catena di fiducia, il peer e il protocollo devono supportarlo. Microsoft sta sperimentando ecosistemi PKI post-quantum in ambienti controllati, non con certificati pilota pubblicamente trusted.

7. Laboratorio 1: capability detection in C#

Il primo test riproducibile consiste nel verificare il supporto delle primitive già introdotte in .NET 10. È preferibile a un esempio che presupponga l'esistenza di algoritmi nativi su qualsiasi sistema.

using System;
using System.Security.Cryptography;

Console.WriteLine($"ML-KEM supported: {MLKem.IsSupported}");
Console.WriteLine($"ML-DSA supported: {MLDsa.IsSupported}");
Console.WriteLine($"SLH-DSA supported: {SlhDsa.IsSupported}");

if (!MLKem.IsSupported)
{
    Console.Error.WriteLine("ML-KEM unavailable on this platform.");
    return;
}

Console.WriteLine("ML-KEM available: ready for platform-specific tests.");

Creazione del progetto: dotnet new console -f net11.0. Installare SDK .NET 11 RC2 e controllare la versione con dotnet --info. Su Linux il supporto dipende anche da OpenSSL (per le primitive .NET 10, Microsoft indica OpenSSL 3.5 o successivo). Non sopprimere indiscriminatamente i warning SYSLIB: sono indicatori di contratti ancora sperimentali.

8. Laboratorio 2: una firma ML-DSA in ambiente compatibile

La prova seguente mostra la struttura dell'API, da validare sulla piattaforma e sulla specifica build SDK utilizzata.

using System;
using System.Security.Cryptography;
using System.Text;

if (!MLDsa.IsSupported)
{
    Console.WriteLine("ML-DSA not supported on this host.");
    return;
}

byte[] message = Encoding.UTF8.GetBytes("Post-quantum signature test");
using MLDsa signer = MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa65);
byte[] signature = signer.SignData(message);
bool valid = signer.VerifyData(message, signature);
Console.WriteLine($"Signature valid: {valid}");

La firma non cifra il messaggio. In un'applicazione reale, verificare sempre la provenienza della chiave pubblica, il formato di esportazione, l'identità del firmatario e le policy di rotazione.

9. Architettura di un servizio di cifratura crypto-agile

Application API
    |
    v
Cryptography abstraction
    |-- Algorithm policy / versioning
    |-- Key management / KMS / HSM
    |-- KEM + KDF
    |-- AEAD encryption
    |-- Signature validation
    |-- Telemetry (never log secrets)
    v
Versioned encrypted envelope
    |-- suite identifier
    |-- key identifier
    |-- encapsulated key
    |-- nonce
    |-- ciphertext
    |-- authentication tag

Un envelope cifrato dovrebbe includere identificatori di versione e suite, e associare metadati critici come additional authenticated data (AAD). Le chiavi non devono essere registrate nei log. Il cambio di algoritmo deve essere governato da policy, non da un fallback silenzioso verso suite più deboli.

10. Minacce e verifiche indispensabili

Una migrazione richiede test di downgrade, manipolazione dei ciphertext, error handling indistinguibile quando necessario, controllo della memoria, zeroization, gestione delle chiavi e compatibilità con certificati e protocolli. Per la crittografia composita verificare esplicitamente il comportamento quando una delle componenti fallisce. Per HPKE verificare che le suite realmente negoziate soddisfino la policy richiesta.

11. Benchmark: misurare l'intero protocollo

Misurare soltanto il tempo di una firma o di un'incapsulazione è insufficiente. Servono throughput, latenza p50/p95/p99, allocazioni, dimensioni di chiavi e ciphertext, handshake TLS, dimensioni delle catene X.509, CPU e interoperabilità tra sistemi operativi. Confrontare i risultati su hardware e implementazioni native dichiarate, evitando di presentare misure locali come caratteristiche universali dell'algoritmo.

12. Strategia di migrazione enterprise

La prima fase è un inventario: TLS endpoint, certificati, CA, HSM, JWT, CMS, firme del codice, VPN, backup e archivi con confidenzialità di lungo periodo. Segue la classificazione dei dati in base alla durata della riservatezza. Poi vengono introdotti test di interoperabilità in ambienti non produttivi, telemetria sugli algoritmi negoziati e un piano di rotazione e rollback esplicito. La scelta di una nuova API .NET non rende automaticamente post-quantum l'intera infrastruttura.

13. Cosa cambia davvero con .NET 11 RC2

La novità è l'arrivo di API sperimentali per Composite ML-KEM e HPKE, che completano il percorso avviato con le primitive PQC di .NET 10. Composite ML-KEM riguarda il key establishment composito; HPKE fornisce un framework di cifratura, ma il supporto iniziale non equivale a suite post-quantum già disponibili. Per progettare un sistema sicuro occorre verificare build, piattaforma, algoritmi supportati, standard e interoperabilità.

14. Conclusioni

La post-quantum cryptography è soprattutto un problema di architettura e migrazione: protocolli, formati, certificati, chiavi e dipendenze infrastrutturali. .NET 11 mette a disposizione ulteriori strumenti, ma la proprietà più preziosa resta la crypto-agility: poter aggiornare gli algoritmi senza riscrivere l'intero sistema e senza compromettere le garanzie di sicurezza.

Fonti tecniche