L'autonomia di un agente dovrebbe fermarsi dove finisce l'annullabile
Ogni modello di autonomia per agenti AI che ho visto disegna la stessa scala.
In fondo, l'agente suggerisce e un umano decide. Poi prepara un'azione e un umano la approva. Poi esegue azioni approvate. In cima, agisce da solo entro una certa autorità delegata. Su quale gradino stia un'azione lo decidono rischio e valore: un agente può fissare una riunione da solo, ma non può approvare un prestito.
È una scala ragionevole, ed è ordinata secondo la cosa sbagliata. Rischio e valore ti dicono se vale la pena compiere un'azione e quanto conta se va storta. Non ti dicono l'unica proprietà che decide quanto può andare male davvero un errore: se si può annullare.
È un argomento di design, non una storia di guerra. Nasce dall'applicare ciò che questa serie ha detto sulla reversibilità ai sistemi a cui oggi tutti stanno cercando di dare autonomia.
Revocabile non è la rassicurazione che sembra
La rassicurazione che accompagna quasi ogni design di agenti è che l'autonomia è revocabile. C'è un kill switch. I permessi si possono ritirare a runtime. Se l'agente si comporta male, lo fermiamo.
Tutto vero, e tutto riguarda il futuro. Revocare un agente protegge ogni azione che non ha ancora compiuto e nessuna di quelle che ha già compiuto. L'email al cliente è partita. Il record è stato cancellato. L'annuncio è pubblico. Ritirare i permessi dell'agente un minuto dopo non cambia niente di tutto questo.
Quindi la domanda che decide quanta libertà può avere un agente in sicurezza non è se possiamo fermarlo. È se possiamo contenere ciò che ha fatto prima che lo fermassimo.
La reversibilità è un tetto, non un punteggio
Guardate le azioni in questo modo, e la scala si riordina da sola.
Un'azione grande che si può annullare senza difficoltà può reggere molta autonomia. Riallocare una voce di budget in un piano in bozza, creare un ambiente di test, mettere una pre-autorizzazione rimborsabile. Se l'agente sbaglia, qualcuno torna indietro e il costo è un pomeriggio.
Un'azione piccola che non si può annullare dovrebbe reggerne pochissima, qualunque sia il suo valore. Un'email di due righe a un cliente. Una cancellazione senza backup. Un post sull'account aziendale. Ognuna costa poco presa da sola, e ognuna è permanente nel momento in cui accade.
Per questo non aggiungerei la reversibilità al punteggio di autonomia come un fattore pesato in più. Un punteggio permette a un valore alto in una dimensione di compensarne uno cattivo in un'altra, e l'irreversibilità è esattamente la proprietà che non dovrebbe essere compensata. Fissa il tetto. Rischio e valore possono abbassare un'azione sotto quel tetto. Niente dovrebbe alzarla sopra.
Dichiarala, non lasciarla giudicare all'agente
Se la reversibilità è il tetto, la domanda successiva è chi decide cosa sia reversibile.
Non l'agente. Se l'agente classifica le proprie azioni, il cancello poggia proprio sul giudizio che esiste per contenere, e una classificazione sbagliata ma sicura di sé è indistinguibile da una corretta finché non è successo qualcosa di irreversibile.
La reversibilità sta nel contratto della capability, dichiarata da chi possiede la capability, accanto agli input, agli output e agli effetti collaterali che quel contratto già definisce. Chi possiede il servizio dei pagamenti sa quali operazioni si possono annullare. Chi possiede il servizio di messaggistica sa che un messaggio inviato resta inviato. Quella conoscenza va scritta dove un cancello deterministico possa leggerla, non ricostruita da un modello a runtime.
La reversibilità scade
Ecco la parte che quasi tutti i modelli di autonomia trascurano del tutto, ed è la stessa che questa serie ha trovato nelle decisioni di architettura: il costo di annullare qualcosa non è fissato al momento della decisione. Cresce dopo.
Un pagamento con carta è il caso più chiaro. Un'autorizzazione si può annullare prima di essere incassata, e il cliente quasi non se ne accorge. Dopo l'incasso e il regolamento, i soldi possono tornare indietro solo con un rimborso, che è una nuova transazione con la propria traccia. Un messaggio fermo in una outbox si può ritirare. Una volta consegnato, no. Una modifica di configurazione dietro un flag si può spegnere finché qualcos'altro non comincia a dipenderne.
Quindi la reversibilità non è un flag. È un flag con una finestra, e la finestra la fissa lo stato del dominio, non l'agente. Il contratto deve dire entrambe le cose: si può annullare, e fino a quando.
Questo cambia anche il modo di leggere i passi di staging e di compensazione. Una pre-autorizzazione prima dell'incasso, un ritardo prima che un messaggio parta, una bozza prima di una pubblicazione: non sono burocrazia. Sono il modo in cui compri una finestra. Trasformano un'azione irreversibile in una reversibile per un po', che spesso basta perché un umano o un controllo intercettino l'errore.
Reversibile non è compensabile
C'è la tentazione di trattare come reversibile qualunque azione abbia un passo di compensazione. Non lo è.
Un'azione di compensazione corregge l'esito. Non cancella l'originale. Rimborsare un pagamento già regolato ripristina il saldo, ma l'addebito è avvenuto, compare in un estratto conto, può aver fatto scattare un controllo antifrode, un aggiornamento dei punti fedeltà o una scrittura contabile a valle. Un'email di correzione non disinvia la prima. La storia resta, e resta anche tutto ciò che ci ha reagito.
Quindi il contratto ha bisogno di tre valori, non di due. Reversibile significa che si può annullare senza traccia finché la finestra è aperta. Compensabile significa che si può correggere ma lascia una storia. Irreversibile significa né l'uno né l'altro. Meritano tetti diversi, perché falliscono in modi diversi.
Un'approvazione è una claim su uno stato
L'ultimo buco compare nel passo di approvazione stesso.
Un design sensato lega un'approvazione all'azione esatta: lo stesso target, gli stessi parametri, lo stesso esito atteso. Se uno di questi cambia dopo l'approvazione, l'approvazione non vale più. È necessario, e non basta.
Prendi un'approvazione data alle dieci del mattino per un rimborso che si può annullare fino al regolamento di mezzogiorno. La coda di esecuzione si intasa. L'azione parte alle dodici e mezza. Dell'azione non è cambiato niente. Delle sue conseguenze è cambiato tutto. Chi l'ha approvata ha approvato un'azione reversibile, e quella che è stata eseguita era un'azione compensabile.
Un'approvazione non dice soltanto "questa azione va bene". Dice "questa azione va bene perché si può ancora annullare". È una claim su uno stato, e dovrebbe scadere con quello stato. Quindi il cancello controlla al commit, non solo all'approvazione. L'approvazione porta una scadenza, la più vicina tra la propria durata e la fine della finestra di reversibilità. Subito prima dell'esecuzione, un cancello deterministico rilegge la finestra dal dominio, perché la finestra può essersi spostata, e si rifiuta di procedere se si è chiusa. A quel punto l'azione torna in approvazione con le regole delle azioni che non si possono più annullare.
In un flusso a stadi, significa che la finestra si controlla due volte: una quando l'azione viene validata, e di nuovo nel momento in cui viene eseguita. Tutto ciò che sta in mezzo è tempo in cui il mondo può cambiare sotto un'approvazione che sembra ancora valida.
Il codice
Due piccoli pezzi rendono tutto concreto. Il contratto della capability dichiara la reversibilità, e il tetto ne deriva invece di essere calcolato con un punteggio.
// capability.ts
// Reversibility is declared by the capability owner, never estimated by the agent.
export type Reversibility =
// Can be undone without trace, but only until a point in time set by the domain.
| { kind: 'reversible'; undo: string }
// Can be corrected afterwards, but the original action and its effects remain.
| { kind: 'compensable'; compensate: string }
| { kind: 'irreversible' }
export type Autonomy = 'suggest' | 'prepare' | 'execute-with-approval' | 'execute'
export interface Capability {
name: string
reversibility: Reversibility
externallyVisible: boolean // anything a customer, partner or regulator can see
}
const ORDER: Autonomy[] = ['suggest', 'prepare', 'execute-with-approval', 'execute']
// The ceiling is set by what can be undone. Risk and value can lower it, never raise it.
export function ceiling(cap: Capability): Autonomy {
if (cap.reversibility.kind === 'irreversible' || cap.externallyVisible) return 'prepare'
if (cap.reversibility.kind === 'compensable') return 'execute-with-approval'
return 'execute'
}
export function allowed(cap: Capability, requested: Autonomy): Autonomy {
const max = ceiling(cap)
return ORDER.indexOf(requested) <= ORDER.indexOf(max) ? requested : max
}
L'approvazione si lega all'azione esatta e al tempo, e il cancello la ricontrolla subito prima dell'esecuzione. Legge la finestra dal dominio in entrambi i momenti, mai dall'agente.
// approval.ts
// An approval is a claim about a state: "this is fine because it can still be undone".
// It expires with that state, so it is checked again at commit.
import { createHash } from 'node:crypto'
export interface StagedAction {
capability: string
target: string
params: Record<string, unknown>
}
export interface Approval {
actionHash: string
approvedBy: string
expiresAt: number // the earlier of the approval's own TTL and the reversibility window
}
const APPROVAL_TTL_MS = 15 * 60 * 1000
export const hashAction = (a: StagedAction) =>
createHash('sha256')
.update(JSON.stringify([a.capability, a.target, Object.entries(a.params).sort()]))
.digest('hex')
export function approve(
action: StagedAction,
approvedBy: string,
reversibleUntil: number, // read from the domain at validate time
now = Date.now()
): Approval {
if (reversibleUntil <= now) throw new Error('window already closed: approve as irreversible')
return {
actionHash: hashAction(action),
approvedBy,
expiresAt: Math.min(now + APPROVAL_TTL_MS, reversibleUntil),
}
}
export type CommitDecision =
| { ok: true }
| { ok: false; reason: 'action-changed' | 'approval-expired' | 'window-closed' }
// Called by the deterministic gate immediately before execution, never by the agent.
export function checkAtCommit(
action: StagedAction,
approval: Approval,
reversibleUntil: number, // read from the domain AGAIN: the window may have moved
now = Date.now()
): CommitDecision {
if (hashAction(action) !== approval.actionHash) return { ok: false, reason: 'action-changed' }
if (now >= reversibleUntil) return { ok: false, reason: 'window-closed' }
if (now >= approval.expiresAt) return { ok: false, reason: 'approval-expired' }
return { ok: true }
}
Nota cosa l'agente non fa mai, qui. Non decide cosa sia reversibile, non emette approvazioni, e non esegue il controllo al commit. Prepara azioni. Tutto ciò che determina se avverranno sta in codice che l'agente non può aggirare con le parole.
C'è un tetto anche qui
Si può esagerare, e l'esagerazione ha un modo di fallire preciso.
Se ogni azione richiede un'approvazione umana, gli umani smettono di leggere le approvazioni. Una coda di quaranta richieste identiche al giorno abitua le persone a cliccare sì, e un'approvazione che nessuno legge è peggio di nessuna, perché sposta la responsabilità su qualcuno che non ha mai deciso davvero. La stessa cosa succede a qualunque rinnovo che finisce in una coda invece che su una persona.
Quindi il punto non è mettere un cancello su tutto. È fare in modo che i cancelli stiano dove finisce l'annullabile, e da nessun'altra parte. Lascia che l'agente agisca liberamente dentro finestre davvero aperte, tieni gli umani per i momenti in cui una finestra sta per chiudersi, e spendi la loro attenzione dove non si può ricomprare.
La regola che ti lascio
Rischio e valore decidono se vale la pena compiere un'azione. La reversibilità decide quanta libertà può avere un agente mentre la compie. Dichiarala nel contratto, dalle una finestra, tieni il compensabile separato dal reversibile, e tratta ogni approvazione come una claim che scade con lo stato in cui è stata data.
Quindi ecco la domanda che farei su qualunque agente a cui stai per dare più autonomia. Quali delle azioni che può compiere oggi nessuno nella tua organizzazione riuscirebbe ad annullare entro domattina, e chi le ha approvate, e quando?