Passa al contenuto principale

Agenti desktop senza pixel

Avanzato
What you'll learn
  • Capire cosa legge un agente basato sull'albero di accessibilità e in cosa differisce, a livello di protocollo, da un agente basato su screenshot
  • Imparare la regola di retry che la maggior parte degli harness di computer use sbaglia — e perché «è fallita?» è la domanda sbagliata
  • Vedere perché i ref sono legati a uno snapshot e cosa dovrebbe fare un harness, invece di tirare a indovinare, quando uno diventa obsoleto
  • Conoscere il modello di proprietà a tre stati che impedisce a un agente di agire su una lettura fallita
  • Riconoscere i due modi di fallire che questo approccio NON risolve: alberi troncati e app senza superficie di accessibilità
  • Capire la superficie di sicurezza: tutto ciò che è a schermo diventa input dell'agente

Il 28 ottobre 2026 Vercel Labs ha taggato la v1.0.0 di agent-desktop e il 31 ottobre era in cima alla classifica trending di GitHub. È una CLI Rust e libreria C, Apache-2.0, che dà a un agente il computer use su macOS, Windows e Linux leggendo l'albero di accessibilità del sistema operativo — la stessa struttura che consuma uno screen reader: AX API su macOS, UI Automation su Windows, AT-SPI su Linux. L'inquadramento di Vercel è «agent-browser, per tutto il tuo computer».

Ogni articolo uscito finora si ferma al titolo: alberi al posto dei pixel, quindi i click arrivano dove devono. È vero ed è la metà meno interessante.

La metà interessante è che il repository include un CONCEPTS.md che è, di fatto, una specifica su come un harness di computer use dovrebbe gestire l'incertezza — e risponde a domande che le API di computer use mainstream lasciano interamente a te. Se costruisci loop di agenti, leggi questa pagina per il modello anche se non installerai mai lo strumento. La nostra pagina agenti computer-use a confronto copre le API basate su screenshot di Anthropic, OpenAI e Google; questa è l'altra architettura.

Cosa cambia quando leggi l'albero invece dello schermo​

Un agente a screenshot riceve un'immagine e restituisce coordinate. Il contratto è: ecco dei pixel, dimmi dove cliccare. Tutto ciò che l'agente sa dell'interfaccia deve dedurlo visivamente e ogni azione è una scommessa su una coordinata.

Un agente ad albero riceve struttura. Uno snapshot restituisce JSON con un ref per ogni elemento su cui l'agente può agire, e le azioni nominano un ref anziché un punto.

Guided walkthrough1 of 4
  1. Fai lo snapshot dell'albero di accessibilità di una finestra, app o superficie. Ottieni JSON — ruoli, nomi, stati — con un ref per ogni nodo azionabile e un ID di snapshot a cui i ref appartengono.

Le differenze pratiche che ne derivano sono più grandi di quanto sembri:

  • I ref sopravvivono ai cambi di layout che rompono le coordinate. Un pulsante che scende di 200 px nella finestra è lo stesso nodo nell'albero. Per un agente a screenshot è una nuova scommessa.
  • È headless per impostazione predefinita. L'input passa dall'API di accessibilità senza muovere il puntatore di sistema né rubare il focus, quindi l'agente può pilotare le app mentre tu continui a usare la macchina. Esiste una modalità --headed per la sintesi di input fisico quando un'app ignora l'input programmatico. È un modello operativo davvero diverso dagli agenti a screenshot, che si contendono il cursore con te.
  • Gli snapshot sono testo, non immagini. Conta per il conto e per la finestra di contesto — vedi perché gli agenti bruciano token. Ma non è una vittoria gratis, perché gli alberi densi diventano grandi; la risposta del repo è mappare prima in modo grossolano (--skeleton --compact) e poi scendere in una regione con --root.
  • A volte servono comunque gli screenshot. Lo strumento include screenshot accanto a snapshot. Le app renderizzate su canvas, le interfacce disegnate a mano e i giochi hanno poca o nessuna superficie di accessibilità, e nessuna lettura dell'albero ne inventa una.

Il loop minimo osserva / agisci / verifica

npm install -g agent-desktop
agent-desktop version

# macOS only: accessibility permission must be granted first
agent-desktop permissions --guide

# 1. observe — -i limits output to interactive elements
agent-desktop snapshot --app Finder -i

# 2. act — the ref MUST carry its snapshot ID; a bare @e8 is rejected
agent-desktop click @s8f3k2p9:e8

# 3. verify — re-snapshot and confirm the change landed
agent-desktop snapshot --app Finder -i

# dense UI? map coarsely, then drill into one region
agent-desktop snapshot --app Slack --skeleton --compact
agent-desktop snapshot --root @s8f3k2p9:e42

L'output è JSON con un booleano ok e data oppure un error che porta un codice, una disposition e un suggerimento. I comandi si raggruppano in osservazione (snapshot, find, get, screenshot, list-surfaces), azione su ref (click, type, set-value, scroll, toggle, expand), tastiera e mouse grezzi (press, hover, drag, key-down), gestione di finestre e app (launch, focus-window, resize-window, list-apps) e sistema (permissions, status, batch, skills).

L'idea da rubare: consegnato, non riuscito​

Ecco la regola, e la cosa singolarmente più utile del repository:

La distinzione che conta non è successo contro fallimento. È consegnato contro non consegnato.

Pensa a cosa fa un agente quando un comando type restituisce un errore. Il comportamento istintivo dell'harness — riprovare in caso di errore — è attivamente pericoloso, perché «errore» fonde due situazioni completamente diverse:

  • I tasti non hanno mai raggiunto il campo. Riprovare è gratis.
  • I tasti potrebbero essere arrivati e a fallire è stata la lettura di conferma. Riprovare digita tutto due volte.

Un harness che non sa distinguerle invierà due volte i form, premerà due volte Invia e duplicherà righe senza che nessuno se ne accorga. Per questo agent-desktop classifica ogni mutazione in tre stati di consegna invece che in un booleano:

Stato di consegnaSignificatoRiprovare?
Non consegnatoErrore di argomenti, accesso negato o comando di sola lettura — l'azione dimostrabilmente non ha mai raggiunto il bersaglioSicuro. Riprova liberamente
Consegnato non verificatoL'input è stato inviato ma l'effetto non è confermato — l'esito tipico della sintesi di input fisicoNon sicuro. Rileggere lo stato è l'unica mossa corretta
ConsegnatoEffetto confermato rileggendo l'elementoFatto; niente da riprovare

Le letture hanno una classificazione parallela a quattro vie, e la prima è la più sottile:

  • Assenza accertata — al provider è stato chiesto e il bersaglio non ha davvero quella proprietà o quel pattern. Una risposta definitiva, mai ritentata.
  • Fallimento ritentabile — un guasto di trasporto come un timeout o una connessione caduta. Vale la pena ripetere entro un budget.
  • Elemento non disponibile — è sparito. Si risolve come ref obsoleto se l'elemento era stato risolto; è ritentabile se capita a metà visita.
  • Fallimento terminale — un diniego o un codice non classificato. Definitivo, mostrato così com'è.

Lo strumento lo espone direttamente: riprovi solo quando error.disposition.retry vale safe.

Key takeaways
  • Se oggi scrivi un tuo loop di computer use, porta con te questa idea anche se non porti nient'altro. Classifica ogni azione in base a se POTREBBE essere arrivata, e fai di «potrebbe essere arrivata» un ramo distinto che rilegge lo stato invece di riprovare. L'idempotenza non è una proprietà del tuo codice di retry — è una proprietà della singola azione, e solo il livello dello strumento sa quale è quale.

Perché i ref sono legati a uno snapshot​

In agent-desktop un ref ha l'aspetto @e8, ma non puoi mai usarlo da solo — un ref nudo fallisce con INVALID_ARGS. Deve essere qualificato dal suo ID di snapshot: @s8f3k2p9:e8.

Sembra un attrito. È un sistema di tipi deliberato. La ragione dichiarata: i ref sono deterministici dentro un singolo snapshot ma non sono stabili tra un cambio di interfaccia e l'altro. L'ID di snapshot è un namespace, e ogni lookup è confinato al namespace in cui è stato creato — uno snapshot preso sotto una sessione richiede poi quello stesso scope di sessione.

Così la sintassi scomoda rende irrappresentabile un vero errore di categoria. Non puoi portarti per sbaglio un identificatore da un'osservazione di tre azioni fa e vederlo risolversi in silenzio in qualunque cosa occupi ora quello slot. Confrontalo con il mondo degli screenshot, dove una coordinata di uno screenshot vecchio è sintatticamente indistinguibile da una fresca e si risolve in qualcosa ogni volta.

Risoluzione fail-closed: rifiutarsi di indovinare​

Quando l'azione parte, il ref viene risolto contro l'albero live attraverso una sequenza graduata, e l'ordine codifica un giudizio su quali prove siano affidabili:

Guided walkthrough1 of 4
  1. Segui lungo l'albero corrente il percorso registrato al momento dello snapshot.

L'impegno di progetto sta in ciò che accade quando nulla di tutto questo produce una corrispondenza sicura: la risoluzione fallisce in modo chiuso. Corrispondenze mancanti, obsolete e ambigue vengono rifiutate — STALE_REF, AMBIGUOUS_TARGET — invece di essere risolte con la miglior ipotesi. Su macOS l'implementazione conserva gli oggetti di accessibilità nativi proprio perché un elemento non possa essere identificato male in silenzio quando l'ordine dell'albero si sposta sotto i suoi piedi.

È l'opposto di come degrada un agente a pixel. Una coordinata obsoleta colpisce sempre qualcosa; un ref obsoleto restituisce un errore. Scambiare un'azione sbagliata silenziosa con un fallimento rumoroso è l'intera pretesa di affidabilità, ed è lo scambio giusto per qualunque cosa un agente faccia senza supervisione.

What you'll learn
  • Lo stesso ragionamento compare nel trattamento a due facce dei risultati parziali nel repo: se uno snapshot esaurisce il suo budget di tempo a metà visita, restituisce ciò che ha osservato invece di scartare la visita — ma riporta la completezza per nodo. Un harness che ignora quel campo ragionerà con sicurezza su un albero troncato e concluderà che un pulsante non esiste. Controlla la completezza prima di concludere un'assenza.

Proprietà a tre stati: Absent, Unknown, Known​

Un meccanismo più piccolo con conseguenze sproporzionate. Le proprietà non vengono mai ridotte a un booleano. Tre stati restano distinti:

  • Known — un valore è stato letto.
  • Absent — al provider è stato chiesto e davvero non ha alcun valore.
  • Unknown — è fallita la lettura stessa.

La maggior parte degli harness appiattisce Unknown in false o null, e un agente poi agisce su una lettura fallita come se fosse un risultato negativo: la checkbox «non è spuntata» (non siamo riusciti a leggerla), il campo «è vuoto» (la query è andata in errore). La formulazione del repo è che l'incertezza deve viaggiare — quando una fonte di prova a priorità più alta fallisce, quel fallimento viene portato avanti invece di essere spianato. I nomi accessibili sono calcolati nel core da slot ordinati per rango, non in ogni adapter di piattaforma, così la logica condivisa non vede mai un token specifico di piattaforma e le stesse regole di incertezza valgono ovunque.

Se da questa pagina porti via due cose, porta via la semantica di consegna e questa. Sono due casi della stessa disciplina: non lasciare che un'incapacità di sapere si spacci per conoscenza.

Due limiti da nominare​

L'interaction lease copre solo il dispatch. L'esclusività dell'input è a livello di macchina e detenuta da un solo processo alla volta, ma il lease deliberatamente non copre l'attesa che precede un'azione, perché aspettare che un elemento diventi azionabile può durare a lungo. La conseguenza pratica: un elemento può diventare azionabile e poi cambiare di nuovo tra la tua attesa e il tuo dispatch. È esattamente per questo che il passaggio di verifica non è facoltativo.

Il supporto di piattaforma non è uniforme e Linux è il bordo sfilacciato. Albero di accessibilità, click e digitazione funzionano su tutti e tre i sistemi operativi, ma su Linux click e digitazione passano da AT-SPI con avvertenze X11/GNOME, gli screenshot richiedono X11 o un portal Wayland, la clipboard richiede wl-clipboard sotto Wayland, la gestione delle app è solo avvio ed elenco, e le notifiche sono GNOME Shell. macOS richiede che il permesso di accessibilità sia concesso esplicitamente, più un TMPDIR di proprietà dell'utente con modalità 0700. Windows non richiede alcun permesso speciale a pari livello di integrità del terminale. Su circa 60 comandi, non tutti sono operativi su ogni piattaforma — controlla agent-desktop status sulla macchina su cui intendi davvero girare.

La superficie di sicurezza è l'intero schermo​

Va detto chiaramente, perché la comodità è seducente.

Un agente ad albero di accessibilità legge il contenuto testuale delle applicazioni, una cattura strettamente più ricca di uno screenshot della finestra in primo piano — incluso, potenzialmente, contenuto che è a schermo ma non era ciò che stavi guardando. Il repo è esplicito: snapshot, screenshot e contenuto della clipboard possono contenere dati sensibili e vanno tenuti fuori dai log pubblici. Incorpora una vera mitigazione: i secure field. Quando una piattaforma marca un elemento come segreto, il percorso di lettura trattiene sia il suo valore sia le prove testuali correlate, e il percorso di azione può scriverci dentro ma non ripete mai il valore tentato o osservato.

Questo protegge le password. Non protegge l'email aperta, il DM di Slack o la scheda cliente nella finestra accanto. Due conseguenze:

  1. Le trascrizioni diventano artefatti sensibili. I log di agente da una sessione desktop somigliano più a una registrazione dello schermo che alla cronologia di un terminale. Trattali di conseguenza — vedi cosa carica il tuo agente.
  2. Ogni superficie leggibile è una superficie di injection. Qualunque testo mostrato da un'app diventa input dell'agente, quindi un invito di calendario, un nome di file o un messaggio di chat costruiti ad arte sono un vettore di prompt injection senza alcuna richiesta di rete. Lo stesso ragionamento del web che il tuo agente legge vale per il desktop, e la prompt injection ne è il manuale introduttivo. Un agente con esclusività d'input a livello di macchina e senza movimento del puntatore è anche, per costruzione, uno le cui azioni non vedrai accadere.
Watch out
  • Headless per impostazione predefinita è insieme una funzione di usabilità e un pericolo per la supervisione. Poiché l'agente non muove il tuo puntatore né ruba il focus, non c'è alcun segnale ambientale che stia agendo. Se lo esegui senza supervisione, il controllo compensativo deve essere il log e un'app bersaglio ristretta — non la tua attenzione. La nostra pagina sul hardening degli autonomous run copre lo schema generale.

Quando questa architettura è la scelta giusta​

SituazioneCosa scegliere
App desktop nativa con vero supporto all'accessibilità (Finder, Office, la maggior parte delle app Cocoa/WinUI)Agente ad albero. Ref stabili, snapshot di testo economici, nessun calcolo di coordinate
App web in un browserPrima un harness specifico per browser — il DOM è un albero migliore della proiezione AX del browser. Vedi agenti computer-use a confronto e browser con login condiviso
App canvas/WebGL, gioco, interfaccia disegnata a mano, desktop remotoAgente a screenshot. Non c'è alcuna superficie di accessibilità da leggere
L'agente deve girare mentre tu continui a lavorareAgente ad albero, modalità headless — niente furto di focus, niente lotta per il puntatore
Azioni ripetute, mutanti, senza supervisioneAgente ad albero, e rispetta la semantica di consegna. È il caso per cui esiste l'intero progetto
Verifica visiva («ha un bell'aspetto?»)Screenshot, qualunque sia l'harness. Un albero non può dirti che il layout è rotto
Premi Invio o Spazio per girare la carta. Usa le frecce sinistra e destra per spostarti tra le carte.Termine mostrato.
1 / 10

Check yourself

0/5
  1. Il tuo harness invia un comando `type` in un campo di un form e riceve un errore con stato di consegna 'delivered unverified'. Qual è la mossa successiva corretta?
  2. Perché agent-desktop rifiuta un ref nudo come `@e8` e richiede `@s8f3k2p9:e8`?
  3. Uno snapshot ritorna senza il pulsante che il tuo agente si aspettava. Cosa devi controllare prima di concludere che il pulsante non esiste?
  4. Quale task è il PEGGIOR candidato per un agente ad albero di accessibilità?
  5. Qual è la principale NUOVA esposizione di sicurezza di un agente ad albero desktop rispetto a un agente a screenshot confinato a una sola scheda del browser?
Key takeaways
  • Gli alberi di accessibilità sostituiscono una scommessa sulle coordinate con un riferimento nominato, quindi i cambi di layout che rompono un agente a screenshot diventano un non-evento — ma canvas, giochi e interfacce disegnate a mano non hanno alcun albero da leggere.
  • Ruba il modello di consegna: classifica le azioni in base a se POTREBBERO essere arrivate, non a se hanno restituito un errore. «Potrebbe essere arrivata» merita una rilettura, mai un retry.
  • Legare i ref a uno snapshot è un sistema di tipi, non attrito — rende impossibili da esprimere i bug da identificatore obsoleto invece di lasciarli sbagliare in silenzio.
  • La risoluzione fail-closed è la pretesa di affidabilità: una coordinata obsoleta colpisce sempre qualcosa, un ref obsoleto restituisce STALE_REF. Preferisci il fallimento rumoroso per tutto ciò che è senza supervisione.
  • Tieni Unknown distinto da Absent. Un agente che legge una query fallita come risultato negativo agirà con sicurezza sul nulla.
  • L'operatività headless elimina il segnale ambientale che un agente sta lavorando. Se lo esegui senza supervisione, log e un'app bersaglio ristretta sono il controllo — non la tua attenzione.

Fonti e approfondimenti​

Avanti​