Ajouter une porte d'approbation humaine
Mettre en pause un appel d'outil à haut risque pour obtenir la validation d'un humain grâce au modèle de checkpoint du SDK KLA, acheminé vers le Decision Desk.
Certaines actions d'agent sont trop lourdes de conséquences pour s'exécuter sans supervision : supprimer un compte, traiter un paiement, publier un document. Ce guide montre comment encadrer l'un de ces appels avec une porte d'approbation humaine en utilisant le SDK KLA Control Plane. KLA Control Plane est une couche govern-in-place de sécurité d'exécution, d'audit et de gouvernance : vous instrumentez le code d'agent que vous exécutez déjà. Un seul checkpoint suffit pour qu'un appel d'outil se mette en pause en attendant un humain, soit acheminé vers un réviseur, et ne reprenne qu'après une validation explicite.
Fonctionnement d'une porte
Vous encadrez l'appel à risque dans un checkpoint. Le checkpoint soumet une Decision Request (l'action et son contexte) au moteur de politiques KLA, qui la résout en l'un de quatre résultats, par ordre de précédence : allow, warn, require_approval ou block. Quand la politique renvoie require_approval, l'exécution se met en pause. KLA ouvre une Escalation (une unité de travail en pause dans l'attente d'un humain) et l'achemine vers le Decision Desk, l'espace de travail où les réviseurs autorisés approuvent ou refusent les actions en attente. Par défaut, l'appel checkpoint du SDK interroge le proposal par sondage jusqu'à ce qu'un réviseur décide, puis fait valoir l'approbation et rend le contrôle à votre code ; le serveur exécute l'action approuvée exactement une fois.
sequenceDiagram
participant App as Votre agent
participant KLA as Checkpoint KLA
participant Desk as Decision Desk
App->>KLA: checkpoint("process_payment", context)
KLA->>KLA: Évaluer la Decision Request
Note over KLA: résultat = require_approval
KLA->>Desk: Ouvrir une Escalation
Desk-->>KLA: Le réviseur approuve ou refuse
KLA-->>App: Décision résolue
App->>App: Exécuter ou gérer le refusAjouter le checkpoint
Ci-dessous, un appel à haut risque process_payment est protégé avec le SDK de gouvernance (kla-governance pour Python, @kla-digital/governance pour Node.js). Le SDK sonde au checkpoint jusqu'à ce que l'Escalation soit résolue au Decision Desk. Les paramètres de l'action (compte, montant) sont liés à l'approbation, si bien que la politique décide sur les valeurs réelles et que le réviseur les voit.
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;
}
Configurez le client une seule fois, au démarrage, avec l'URL de base du control plane, un jeton d'accès client credentials et le rattachement de l'agent : l'identifiant de l'agent enregistré, sa version de release épinglée et un identifiant de mandat actif. L'identité du tenant provient du jeton vérifié ; le SDK n'envoie jamais de champ tenant dans le corps de la requête. Chaque appel de checkpoint envoie Authorization: Bearer <token> à https://api.kla.digital en votre nom.
Gérer un refus proprement
Un réviseur peut refuser l'Escalation, ou la politique peut renvoyer un block ferme. Les deux cas remontent de checkpoint sous la forme d'un DecisionDenied accompagné de la décision résolue ; le transport a réussi et le résultat est enregistré. Traitez l'exception comme une branche attendue :
- Lisez les reason codes. Chaque décision autre que
allowporte desreason_codeslisibles par machine (par exemplePAYMENT_OVER_THRESHOLD) et uneremediationlisible par un humain. Branchez sur les codes ; n'analysez jamais la prose. - Rendez le refus visible. Renvoyez un message clair à l'utilisateur appelant ou à l'agent en amont (« Le paiement nécessite l'approbation d'un responsable et a été refusé »). Le refus est déjà enregistré comme Lineage Record, vous n'avez donc pas besoin de le journaliser séparément pour l'audit.
- Ne réessayez pas à l'aveugle. Une action refusée ne doit pas revenir automatiquement dans le même checkpoint. Escaladez vers un parcours humain dans votre propre produit.
Un checkpoint require_approval peut sonder aussi longtemps qu'un réviseur en a besoin. Exécutez les appels protégés dans un worker ou une tâche d'arrière-plan, afin qu'aucune connexion de requête côté utilisateur ne reste ouverte pendant toute l'attente. Passez timeout (timeoutMs en Node.js) pour borner l'attente : à son expiration, le SDK lève ApprovalTimeout, ce qui signifie que l'approbation reste en attente côté serveur. Rappelez checkpoint avec le même proposal id pour reprendre l'attente. Pour ne pas attendre du tout, passez wait_for_approval=False (waitForApproval: false) ; la décision en attente est renvoyée immédiatement et vous la faites valoir plus tard avec resume_action (resumeAction).
Rendez l'appel protégé idempotent. Passez un proposal_id stable (ici, dérivé du compte et du montant) pour que, lorsque l'action reprend après approbation (ou si votre worker redémarre en cours de route), le débit sous-jacent s'exécute exactement une fois. KLA lie l'approbation à ce proposal, de sorte qu'une nouvelle exécution après validation se résout en la même décision approuvée au lieu d'ouvrir une seconde Escalation.
Avant la mise en production
Rédigez la politique de la porte dans le Policy Builder et lancez une Simulation : rejouez des Decision Requests représentatives contre le brouillon et confirmez qu'un paiement de montant élevé se résout en require_approval tandis qu'un paiement de faible montant se résout en allow. Une fois validée, la politique est compilée en un policy pack signé et entre en production. Dès lors, chaque appel protégé produit une trace défendable : la requête, le résultat, le verdict du réviseur et l'action résultante, le tout capturé dans un Lineage Record que vous pourrez exporter plus tard sous forme de Sealed Evidence Bundle.
