← Indice documentazione Microprogettazione › approval_ux

myclaw

approval_ux — i flussi di approvazione
Microprogettazione v1.0 — 21 aprile 2026
Secondo dei tre documenti trasversali della fase 1.
Copre la critica bloccante #5 + mitigazione tensioni 1 e 2 del giudizio di fase 0.

Pubblico: chi costruirà l'interfaccia tra agent e utente. Lettura: 20 min.

Indice

  1. Scopo: perché l'approvazione è il punto critico
  2. Tre antipattern da evitare
  3. Il ciclo di approvazione canonico
  4. Batching: approva simili per N minuti
  5. Pausa di lettura: il ritardo di 3 secondi
  6. Revoca: il comando /undo
  7. Tutor mode: anti-automation-bias
  8. Differenze per canale (CLI, Telegram, voce)
  9. Contratto Python
  10. Alternative considerate e scartate
  11. Test di conformità
  12. Riferimenti

1. Scopo: perché l'approvazione è il punto critico

L'approvazione è dove l'utilità (agisco in fretta), l'intelligenza (so cosa chiedere e come) e l'autonomia (non disturbo se non serve) si incontrano in un'unica superficie. Sbagliare qui non è un bug, è un fallimento di prodotto: gli utenti cliccano "ok" riflessi, gli agenti abusano, e la Supervised mode diventa una firma in bianco.

Questo documento è il contratto di comportamento dei flussi di approvazione: quando si chiede, come si presenta, quanto si aspetta, come si batcha, come si revoca, come si rompe il pilota automatico. È la reificazione delle tensioni 1 e 2 del giudizio di fase 0.

Cosa copre

Cosa non copre

Owner di tipi cross-componente (vedi types.html §3): ApprovalRequest, Approval, BatchGrant, UndoResult sono definiti qui (§9). Consumatori: gateway (costruzione), channel (rendering), policy (draft), observability (audit). Modifiche richiedono aggiornamento di types §3.

2. Tre antipattern da evitare

Prima di descrivere il design, nominiamo tre modelli di approvazione che abbiamo esplicitamente rifiutato. Nominare il fallimento previene la sua sottigliezza futura.

AntipatternPerché fallisce
"Rubber-stamp yes" Pulsante "Approva" sempre identico, sempre immediato, testo generico "Procedo?". Dopo 10 interazioni l'utente clicca senza leggere. L'approval fatigue è un effetto noto in UX di sicurezza (cookie consent, TLS warning). Contromisura: pausa forzata (§5) + testo specifico per azione.
"Everything or nothing" L'utente concede autonomy Full "per non essere disturbato" e la sicurezza svanisce in blocco. Oppure Supervised chiede per ogni singola scrittura di file e l'utente abbandona dopo un giorno. Contromisura: batching per classi (§4), non per tutto.
"Modal sincrono" L'agente blocca tutto finché l'utente non risponde. Se Roberto è in riunione, l'operazione muore. Contromisura: timeout espliciti per canale + outcome user_aborted pulito + "riprendi più tardi" se senso.

3. Il ciclo di approvazione canonico

Ogni volta che la Policy restituisce "approve_required", nasce una ApprovalRequest che attraversa cinque stati. Il runtime del §8 del doc agent_runtime si sospende finché la richiesta non è decisa (qualunque il verdetto).

Policy → approve_required (cap §4 di agent_runtime) Pending timeout: 2 min default Granted esplicito utente click "Approva" Batch auto-granted classe azione in finestra §4 Denied rifiuto utente click "Nega" Timeout nessuna risposta timeout_at raggiunto Never blacklist permanente "mai più chiederlo" Granted / Batch → azione eseguita Denied / Timeout / Never → outcome user_aborted
Figura 1 — Gli stati di una ApprovalRequest. Da Pending si va a cinque destinazioni. La transizione a Never registra una blacklist esplicita: quella classe di azione non chiederà mai più approvazione a questo sender, viene sempre negata.

Cosa contiene una richiesta di approvazione

Il messaggio presentato all'utente non è mai "Procedo?" generico. È strutturato in 4 pezzi obbligatori:

PezzoEsempio
Azionefs_write · shell_run · telegram_send_to ...
Bersaglio / effetto"scrivere /home/roberto/notes.md" · "eseguire apt update"
Motivo (perché lo sto facendo)"per aggiungere quello che mi hai dettato due minuti fa"
Reversibilitàreversibile · ⚠ irreversibile · reversibile con costo

4. Batching: approva simili per N minuti

La singola richiesta "approvi questa operazione?" è una firma singola. Il batching permette di dare una firma per classe: "per i prossimi 10 minuti, tutte le operazioni simili sono approvate per default".

DECISIONE v1: finestra batching default = 10 minuti, configurable fino a 60 minuti nei preset utente.
Valori sotto i 2 minuti non vengono esposti (poco valore; sopra i 60 serve approvazione esplicita "sono sicuro").

Cos'è una "classe di azione"

La classe è fissata da Policy e ha granularità intenzionalmente stretta:

effect_class: "fs_write:~/downloads/*"          # scrittura in ~/downloads e sotto
effect_class: "shell_run:apt_family"            # apt / apt-get / aptitude
effect_class: "telegram_send:@famiglia"         # messaggi al gruppo famiglia
effect_class: "fs_delete:~/trash/*"             # eliminazione file in ~/trash

Una classe non è "qualunque fs_write". Quella è una scorciatoia pericolosa: includerebbe scrittura in ~/.bashrc. La classe lega verbo + ambito.

Esempio concreto

📱 Telegram · 22:14
myclaw

Vuoi scaricare il file rapporto_marzo.pdf da drive.google.com in ~/downloads/?
effect class: fs_write:~/downloads/* · reversibile

attendi 3s... Nega
✓ approva fs_write:~/downloads/* per 10 min

L'ultimo pulsante è il batching. Premendolo, myclaw annota internamente:

BatchGrant(
  sender="telegram:@roberto",
  effect_class="fs_write:~/downloads/*",
  expires_at="2026-04-21T22:24:00Z",
  created_by_request_id=UUID(...)
)

Per i 10 minuti successivi, richieste che matchano effect_class e sender transitano da Pending direttamente a Batch auto-granted senza presentazione all'utente. Ma la trace continua a registrarle (Legge 3, tracciabilità).

Regole forti sul batching

5. Pausa di lettura: il ritardo di 3 secondi

È il cuore della mitigazione contro approval fatigue e dual-process theory. Il pulsante "Approva" non è cliccabile per 3 secondi dopo la visualizzazione della richiesta. Suona paternalistico: lo è, volutamente.

Perché 3 secondi

In letteratura HCI (Nielsen, Norman) la "speed bump" è un pattern noto: l'introduzione di un attrito minimo per rompere l'automazione ha riduzione del 30-50% di errori su azioni rischiose.

DECISIONE v1: 3 secondi per azioni medium-risk, 5 secondi per high-risk, 0 secondi per le azioni batchate (già pre-approvate). Configurable via config/default.yaml.

Come è implementata

Il pulsante è reso visivamente disabilitato (grigio, italico "attendi 3s...") con un countdown. A click durante la pausa, il click è ignorato e un micro-haptic/visual feedback segnala "non ancora". A fine pausa, il pulsante cambia aspetto e accetta input.

Nelle CLI (§8) la pausa si traduce in: il prompt [y/N] non accetta input per 3 secondi; i caratteri digitati prima vengono scartati. Nessuna magia: è lo stesso effetto.

6. Revoca: il comando /undo

La pausa di lettura previene l'errore prima del click. La revoca lo ripara dopo. Un comando /undo (Telegram), un ctrl-c esteso (CLI) o un vocale "fermati" (voce futura) arresta l'esecuzione in corso e, se possibile, inverte l'effetto.

CasoCosa fa /undo
Azione in preparazione (sandbox sta per partire)Cancella prima che il tool venga eseguito. Outcome user_aborted.
Azione in esecuzioneInvia SIGTERM al processo sandbox. Il tool ha 5s per chiudere pulitamente, poi SIGKILL.
Azione completata reversibileInverte l'effetto se il tool espone revert(). Esempi: fs_write con snapshot, telegram_send che diventa telegram_delete (se entro 48h).
Azione completata irreversibileNon fa nulla ma registra l'intento e l'impossibilità nell'audit log. Avverte l'utente.

Il comando /undo agisce sulla trace più recente dello stesso sender. Per agire su una trace specifica: /undo <trace_id_short>.

Reversibilità non è simmetria. "Ho mandato un messaggio a mio cugino" + "/undo" = "elimino il messaggio se ancora possibile", non "il cugino dimentica". La reversibilità vera è un'illusione per interazioni con terzi. La UX deve comunicarlo: ogni azione verso esterno mostra "⚠ effetto solo dentro casa" prima dell'approvazione.

7. Tutor mode: anti-automation-bias

Paradosso della selezione: più un neurone (o una classe di azione) è "affidabile", meno l'utente lo controlla, più automation bias cresce. Il tutor mode introduce una rottura deliberata ogni K approvazioni consecutive.

OK 1 OK 2 OK 3 ... OK 9 OK 10 Tutor mode richiesta rafforzata: - pausa 10s - summary espanso - "guarda davvero" OK 1 reset contatore OK 2 Dopo 10 approvazioni consecutive della stessa classe, l'11-esima forza un tutor mode. Configurable. Default K=10. Si può disabilitare (sconsigliato).
Figura 2 — Il tutor mode. Ogni 10 approvazioni consecutive della stessa effect_class, la successiva viene presentata con pausa di lettura estesa (10s), summary espanso e un breve alert "hai approvato 10 volte di fila questa azione, guardala davvero". Dopo ogni tutor-interrupt il contatore si resetta.

Cosa cambia in un tutor-interrupt

8. Differenze per canale

CLI (fase 1)

🖥️ myclaw chat · CLI
roberto> scarica il rapporto di marzo
myclaw sta leggendo la lista dei file...

⚠ approvazione richiesta
  azione: fs_write + web_fetch
  effetto: scaricare rapporto_marzo.pdf (stimati 2.4 MB)
  bersaglio: ~/downloads/rapporto_marzo.pdf
  reversibile: sì (fs_delete)
  classe: fs_write:~/downloads/*

(attendi 3 secondi per rispondere)

[y/N/b/never] _
  y = approva · N = nega · b = approva classe per 10 min · never = blacklist

Telegram (fase 3)

Inline buttons, messaggio editabile, emoji sobrio. Il pulsante Approva si abilita dopo 3s via edit del messaggio.

💬 Telegram @myclaw_bot · 22:14
Vuoi che scarichi?
📄 rapporto_marzo.pdf
da drive.google.com
~/downloads/ (~2.4 MB)
reversibile · classe: fs_write:~/downloads/*

✅ attendi 3s... ❌ No
✓ approva classe per 10 min

Voce (futuro)

Pausa di lettura = TTS lento del summary (non accelerato). La conferma vocale richiede parola-chiave riconosciuta (es. "procedi") per evitare false conferme da rumori. Un "sì" generico non basta: serve intent esplicito.

Timeout per canale

CanaleTimeout defaultRazionale
CLI30 secondiUtente davanti al terminale, attivo.
Telegram (DM)2 minutiUtente può essere momentaneamente occupato.
Telegram (canale di casa)5 minutiPiù permissivo, meno urgenza.
Voce15 secondiConversazione sincrona.

9. Contratto Python

from typing import Protocol, Literal
from dataclasses import dataclass
from datetime import datetime
from uuid import UUID

@dataclass
class ApprovalRequest:
    request_id: UUID
    trace_id: UUID
    sender: str                    # es. "telegram:@roberto"
    channel: str
    tool_name: str
    args: dict
    effect_class: str              # es. "fs_write:~/downloads/*"
    summary: str                   # one-line human readable
    reversibility: Literal["reversible", "reversible_with_cost", "irreversible"]
    risk: Literal["medium", "high"]
    requested_at: datetime
    timeout_at: datetime

@dataclass
class Approval:
    request_id: UUID
    granted: bool
    granter: str                   # sender identity
    granted_at: datetime
    via: Literal["explicit", "batch", "timeout_deny", "never_list"]
    batch_window_expires_at: datetime | None = None
    reason: str | None = None      # motivazione, se negato

class ApprovalBroker(Protocol):
    async def request(self, req: ApprovalRequest) -> Approval:
        """Presenta la richiesta, attende, restituisce esito."""
        ...

    async def active_batches(self, sender: str) -> list["BatchGrant"]:
        """Elenco dei batch grant attivi per il sender."""
        ...

    async def revoke_batch(self, sender: str, batch_id: UUID) -> None:
        """Invalida un batch attivo."""
        ...

    async def undo(self, sender: str, trace_id: UUID | None = None) -> UndoResult:
        """Tenta la revoca dell'ultima azione (o di una specifica)."""
        ...

@dataclass
class BatchGrant:
    batch_id: UUID
    sender: str
    effect_class: str
    created_at: datetime
    expires_at: datetime
    approval_counter: int          # quante richieste batched so-far
    source_request_id: UUID

@dataclass
class UndoResult:
    trace_id: UUID
    status: Literal["undone", "partial", "irreversible", "already_completed"]
    message: str

10. Alternative considerate e scartate

AlternativaPerché scartata
Approvazione vocale in CLI ("dì sì o no") Ridondante con la tastiera, introduce ambiguità di riconoscimento. Rimandata al canale voce dedicato.
Scala graduale 1-5 invece di approva/nega Overhead cognitivo × 5, nessun beneficio misurabile. Un binario + "mai più" basta.
Approvazione delegata ad altri utenti di casa (moglie, figlio) Fase 3+, con multi-utente. Prima serve il pairing multiplo.
AI critic che approva al posto dell'utente Ricadrebbe in self-judge ottimistico (Huang 2023). La cosa che rende utile l'approvazione è che l'umano guardi.
Batch perpetuo ("approva fs_write:~/downloads/* per sempre") Collassa verso autonomy Full mascherata. Massimo 60 minuti. Per permanenza serve modificare la Policy.

11. Test di conformità

InvarianteTest
Pausa di lettura rispettataClick prima di 3s non risolve la richiesta (resta Pending). Deve accettare al 3.01s.
Batch non estende a forbiddenBatchGrant per fs_write:~/* non deve auto-approvare una fs_write:/etc/hosts (forbidden).
Batch non per azioni irreversibiliRichieste con reversibility=irreversible non espongono il pulsante batch.
Tutor mode ogni 10Dopo 10 granted consecutivi per stessa effect_class, l'11° ha pause_s=10 e flag tutor=true nella trace.
Reset tutor su altro esitoSe tra i 10 consecutivi c'è un deny o timeout, il contatore riparte da 0.
Timeout rispettato per canaleApprovalRequest su CLI con timeout 30s chiude in Timeout se nessuna risposta.
Undo su azione in corsoSIGTERM al sandbox entro 50ms dal comando /undo; SIGKILL entro 5.1s.
Autonomy change invalida batchCambio da Supervised a ReadOnly cancella tutti i batch attivi di quel sender.
Append-only nell'auditOgni Approval (granted o denied) produce una riga JSONL; nessuna modifica retroattiva.
Never list persisteDopo "mai più chiederlo" su effect_class X, future richieste X risultano sempre via=never_list, granted=false.

12. Riferimenti

RiferimentoCosa abbiamo preso
Nielsen Norman Group, "Speed Bumps for UX"La pausa di lettura 3s come anti-click-riflesso (§5).
Kahneman, Thinking Fast and SlowDual-process theory: la pausa forza il passaggio system 1 → system 2 (§5).
Parasuraman & Manzey 2010, Complacency and Bias in Automation UseAutomation bias: la giustificazione scientifica del tutor mode (§7).
Cookie consent UX studies (varie, 2018-2023)Come non fare approval fatigue: classe, non singolo evento (§4).
Giudizio di fase 0 — critica bloccante #5Il mandato di scrivere questo doc.
Tensioni 1 e 2 del giudizio di fase 0Antropomorfizzazione e automation bias: mitigate qui in modo strutturale.

Continua a leggere

microprogettazione · 25 min
agent_runtime
Dove l'ApprovalRequest è innescata: §4 di agent_runtime, transizione "policy decision = approve_required". I due doc si parlano da vicino.
chiusura fase 0
Prospettive & Giudizio v1
Le tensioni 1 e 2 del giudizio che questo doc reifica in meccanismi concreti (pausa, batching, tutor).
indice microprogettazione
Torna alla landing microprogettazione
Il prossimo in coda: eval.html, terzo dei trasversali.
home
← Indice documentazione
Tutti i documenti.

myclaw — approval_ux microprogettazione v1.0 — 2026-04-21
Secondo dei tre doc trasversali di fase 1. Prossimo: eval.html.