Guide

Aggiungere un gate di approvazione umana

Mettere in pausa una singola chiamata a uno strumento ad alto rischio per il via libera di un revisore, usando il pattern checkpoint del KLA SDK, instradata al Decision Desk.

5 min di lettura1029 parole

Alcune azioni degli agenti sono troppo rilevanti per essere eseguite senza supervisione: eliminare un account, elaborare un pagamento, rilasciare un documento. Questa guida mostra come avvolgere una di queste chiamate in un gate di approvazione umana usando il KLA Control Plane SDK. KLA Control Plane è un layer govern-in-place di sicurezza a runtime, audit e governance: si strumenta il codice dell'agente che già si esegue. Un singolo checkpoint è sufficiente per far sì che una chiamata a uno strumento si metta in pausa in attesa di una persona, venga instradata a un revisore e riprenda solo dopo un via libera esplicito.

Come funziona un gate

Si avvolge la chiamata a rischio in un checkpoint. Il checkpoint invia una Decision Request (l'azione con il suo contesto) al motore di policy di KLA, che la risolve in uno di quattro esiti in ordine di precedenza: allow, warn, require_approval o block. Quando la policy restituisce require_approval, l'esecuzione si mette in pausa. KLA apre una Escalation (un'unità di lavoro in pausa in attesa di una persona) e la instrada al Decision Desk, lo spazio di lavoro in cui i revisori autorizzati approvano o negano le azioni in sospeso. Per impostazione predefinita la chiamata checkpoint dell'SDK interroga il proposal a intervalli finché un revisore non decide, poi riscatta l'approvazione e restituisce il controllo al vostro codice; il server esegue l'azione approvata esattamente una volta.

sequenceDiagram
  participant App as Il vostro agente
  participant KLA as Checkpoint KLA
  participant Desk as Decision Desk
  App->>KLA: checkpoint("process_payment", context)
  KLA->>KLA: Valutare la Decision Request
  Note over KLA: esito = require_approval
  KLA->>Desk: Aprire una Escalation
  Desk-->>KLA: Il revisore approva o nega
  KLA-->>App: Decisione risolta
  App->>App: Eseguire o gestire il diniego

Aggiungere il checkpoint

Qui sotto, una chiamata ad alto rischio process_payment viene protetta con l'SDK di governance (kla-governance per Python, @kla-digital/governance per Node.js). L'SDK interroga il checkpoint finché l'Escalation non viene risolta al Decision Desk. I parametri dell'azione (conto, importo) sono vincolati all'approvazione, così la policy decide sui valori reali e il revisore li vede.

Python

from kla_governance import ActionRequest, AgentIdentity, DecisionDenied, KLAClient

client = KLAClient(
    base_url="https://api.kla.digital",
    credential=get_access_token,
    agent=AgentIdentity(agent_id="agt_9f81a7", release_version="1.4.2"),
    mandate_id="mnd_payments",
)

def process_payment(account_id: str, amount: float):
    try:
        # Polls here if policy returns require_approval, until a
        # reviewer resolves the Escalation on the Decision Desk.
        decision = client.checkpoint(
            ActionRequest(
                tool="payments",
                operation="charge",
                destination="gateway:acct_main",
                parameters={"account_id": account_id, "amount": amount},
            ),
            proposal_id=stable_id_for(account_id, amount),
        )
    except DecisionDenied as denial:
        # block, a denied approval, or an expired approval.
        raise PaymentNotAuthorized(
            f"Payment not authorized: {denial.reason_codes} ({denial.remediation})"
        )

    receipt = gateway.charge(account_id, amount)
    decision.log_success(receipt_id=receipt.id)
    return receipt

TypeScript

import { KLAClient, DecisionDenied } from '@kla-digital/governance';

const client = new KLAClient({
  baseUrl: 'https://api.kla.digital',
  credential: () => getAccessToken(),
  agent: { agentId: 'agt_9f81a7', releaseVersion: '1.4.2' },
  mandateId: 'mnd_payments',
});

async function processPayment(accountId: string, amount: number) {
  let decision;
  try {
    // Polls here if policy returns require_approval, until a
    // reviewer resolves the Escalation on the Decision Desk.
    decision = await client.checkpoint(
      {
        tool: 'payments',
        operation: 'charge',
        destination: 'gateway:acct_main',
        parameters: { accountId, amount },
      },
      { proposalId: stableIdFor(accountId, amount) }
    );
  } catch (error) {
    if (error instanceof DecisionDenied) {
      // block, a denied approval, or an expired approval.
      throw new PaymentNotAuthorizedError(
        `Payment not authorized: ${error.reasonCodes} (${error.remediation})`
      );
    }
    throw error;
  }

  const receipt = await gateway.charge(accountId, amount);
  await decision.logSuccess({ receiptId: receipt.id });
  return receipt;
}

Configurate il client una sola volta, all'avvio, con l'URL di base del control plane, un token di accesso client credentials e il binding dell'agente: l'id dell'agente registrato, la sua versione di release fissata e l'id di un mandato attivo. L'identità del tenant proviene dal token verificato; l'SDK non invia mai un campo tenant nel corpo della richiesta. Ogni chiamata di checkpoint invia Authorization: Bearer <token> a https://api.kla.digital per vostro conto.

Gestire un diniego con garbo

Un revisore può negare l'Escalation, oppure la policy può restituire un block netto. Entrambi i casi emergono da checkpoint come DecisionDenied con la decisione risolta allegata; il trasporto è riuscito e l'esito è registrato. Trattate l'eccezione come un ramo previsto:

  • Leggete i reason code. Ogni decisione diversa da allow porta reason_codes leggibili dalla macchina (per esempio PAYMENT_OVER_THRESHOLD) e una remediation leggibile dalle persone. Ramificate sui codici; non analizzate mai la prosa.
  • Rendete visibile il diniego. Restituite un messaggio chiaro all'utente chiamante o all'agente a monte ("Il pagamento richiede l'approvazione di un responsabile ed è stato rifiutato"). Il diniego è già registrato come Lineage Record, quindi non serve registrarlo separatamente per l'audit.
  • Non riprovate alla cieca. Un'azione negata non deve rientrare automaticamente nello stesso checkpoint. Fate escalation verso un percorso umano nel vostro prodotto.
⚠️ Warning

Un checkpoint require_approval può restare in polling per tutto il tempo che serve al revisore. Eseguite le chiamate protette in un worker o in un task in background, così nessuna connessione di richiesta rivolta all'utente resta aperta per l'intera attesa. Passate timeout (timeoutMs in Node.js) per limitare l'attesa: allo scadere, l'SDK solleva ApprovalTimeout, il che significa che l'approvazione è ancora in sospeso lato server. Richiamate checkpoint con lo stesso proposal id per riprendere l'attesa. Per non attendere affatto, passate wait_for_approval=False (waitForApproval: false); la decisione in sospeso viene restituita subito e la riscatterete più tardi con resume_action (resumeAction).

💡 Tip

Rendete idempotente la chiamata protetta. Passate un proposal_id stabile (qui derivato da conto e importo) così che, quando l'azione riprende dopo l'approvazione (o se il vostro worker si riavvia a metà), l'addebito sottostante venga eseguito esattamente una volta. KLA lega l'approvazione a quel proposal, quindi una nuova esecuzione dopo il via libera si risolve nella stessa decisione approvata invece di aprire una seconda Escalation.

Prima del rilascio

Scrivete la policy del gate nel Policy Builder ed eseguite una Simulation: riproducete Decision Request rappresentative contro la bozza e verificate che un pagamento di importo elevato si risolva in require_approval mentre uno di importo basso si risolva in allow. Una volta validata, la policy viene compilata in un policy pack firmato ed entra in produzione. Da quel momento ogni chiamata protetta produce una traccia difendibile: la richiesta, l'esito, il verdetto del revisore e l'azione risultante, tutto acquisito come Lineage Record che potrete esportare in seguito come Sealed Evidence Bundle.

Aggiungere un gate di approvazione umana | Developer Docs | KLA Control Plane