Porta il tuo agente su Guard

Un percorso passo per passo da “l’agente chiama Stripe da solo” a “l’agente propone e COLVO esegue”: prima osservi, poi approvi a mano, poi lasci passare in automatico le azioni piccole e sicure.

Scritto a partire dal codice · aggiornato il 28 set 2026 · Manca qualcosa o c’è un errore? Scrivici

Checklist

  1. La suite è verde. La versione attuale del tuo agente supera la suite di rimborsi e disdette in COLVO Test, con tre tentativi per scenario.
  2. Stripe è collegato con una chiave limitata. Scrittura su rimborsi e abbonamenti, tutto il resto in lettura o nessun accesso. Solo l’executor di COLVO la vede.
  3. Il tuo backend emette un mandato per ticket. Cliente, pagamenti, abbonamenti, azioni, limiti e una scadenza — decisi dal tuo codice, mai dal modello.
  4. Modalità osservazione per una settimana. L’agente continua ad agire come oggi e segnala ogni azione; COLVO registra cosa avrebbe deciso e controlla il risultato in Stripe.
  5. Applica le regole con le approvazioni. Togli la chiave Stripe all’agente, fai passare ogni azione da POST /v1/operations e imposta l’approvazione automatica a zero, così una persona approva ogni azione.
  6. Alza l’approvazione automatica. Quando chi revisiona approva ogni giorno gli stessi piccoli rimborsi, lasciali passare in automatico. Tieni il kill-switch a portata di clic.

1. Collega Stripe con una chiave limitata

In Stripe crea una chiave limitata con Refunds: write, Subscriptions: write, Customers, Charges, PaymentIntents, Invoices: read e tutto il resto su none. Collegala dalla pagina del progetto (Connections → Connect Stripe) insieme al secret di firma dei webhook; COLVO controlla i permessi in sola lettura (nessun rimborso di prova) e mostra l’URL del webhook da aggiungere in Stripe con gli eventi refund.created, refund.updated, charge.refunded, customer.subscription.updated, customer.subscription.deleted. Le chiavi live devono essere limitate (rk_live_…); una chiave sk_live_ ad accesso completo viene rifiutata. La chiave è cifrata per organizzazione e non viene mai restituita dall’API, mostrata nei log o passata a un modello.

Non hai ancora un account Stripe? POST /v1/projects/{id}/connections/sandbox te ne dà uno simulato per provare tutto il percorso.

2. Emetti un mandato dal tuo backend

Il mandato è l’autorizzazione con cui lavora l’agente. Il tuo backend lo crea quando si apre un ticket o una conversazione, partendo da dati di cui si fida già — il cliente autenticato, i suoi ordini — e non da quello che dice il modello. Riferimento completo dei campi: Mandati e connessioni.

// backend — when ticket #4711 opens (backend key, never shipped to the agent)
const mandate = await fetch('https://colvo.app/v1/mandates', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.COLVO_BACKEND_KEY}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    project_id: process.env.COLVO_PROJECT_ID,
    source_request_id: ticket.id,                         // 'ticket_4711'
    subject: { customer_id: customer.stripeId, payment_ids: orders.map((o) => o.paymentIntent), subscription_ids: [customer.subscriptionId] },
    actions: ['refund.create', 'subscription.cancel'],
    limits: { currency: 'eur', max_amount_minor: 5000, auto_allow_up_to_minor: 0, max_operations: 2,
              cancel_modes_allowed: ['period_end'], cancel_modes_review: ['immediate'] },
    expires_at: new Date(Date.now() + 24 * 3600_000).toISOString(),
  }),
}).then((r) => r.json());
// hand only mandate.mandate_id and an agent key (cak_…) to the agent

3. Prima osserva (per i clienti non cambia nulla)

In modalità osservazione l’agente mantiene il suo codice attuale. Dopo aver agito — o aver deciso di non farlo — segnala cosa ha fatto. COLVO valuta la stessa policy che applicherebbe, salva la decisione che avrebbe preso e rilegge il provider per controllare che l’effetto corrisponda alla risposta. Non blocca mai, non esegue mai e non ha mai bisogno di accesso in scrittura.

POST /v1/observations — chiave agente o backend.

{ "mandate_id": "…",                       // optional: without one the would-be decision is DENY (no authority)
  "source_request_id": "ticket_4711",
  "action": "refund.create",
  "parameters": { "payment_id": "pi_01", "amount_minor": 5000, "currency": "eur" },
  "provider_id": "re_3Nx…",                  // the refund / subscription the agent touched, if any
  "context": { "user_message": "…", "agent_reply": "Refunded €50 to your card." } }
{ "observation_id": "…", "would_decide": "REVIEW",
  "reasons": ["amount 5000 above auto-allow 0 (within max 5000)"],
  "verify_state": "PENDING",                 // → VERIFIED | MISMATCH | UNVERIFIABLE
  "flags": [] }                              // e.g. "would_deny", "state_mismatch", "no_mandate"

Invia "performed": false quando l’agente ha deciso di non agire — la decisione ipotetica viene comunque registrata, solo che non c’è nulla da verificare. Le azioni osservate sotto un mandato contano sui suoi limiti esattamente come le operazioni, quindi un secondo rimborso che supererebbe il totale risulta DENY. Una verifica non corrispondente apre un incidente e fa scattare i tuoi avvisi, anche se nulla è stato bloccato.

La pagina Observations della console mostra ogni osservazione con la decisione ipotetica e la verifica, più un riepilogo settimanale: quante azioni sarebbero state consentite, mandate in revisione o negate, e quante risposte erano in disaccordo con lo stato del provider. Quando per una settimana quella lista è noiosa, passa al passo successivo.

4. Applica le regole: una chiamata sostituisce quella a Stripe

Togli la chiave Stripe dall’ambiente dell’agente. Dove prima chiamava Stripe, ora propone. Parti con auto_allow_up_to_minor: 0, così ogni azione è REVIEW e una persona la approva nella pagina Approvals della console. COLVO manda un’email a tutti gli owner e gli editor quando un’azione è in attesa (e pubblica sui canali di avviso iscritti alle approvazioni), li richiama al 75 % della finestra e, se nessuno decide in tempo, annulla l’operazione prima che venga inviato qualcosa e li avvisa.

Node / TypeScript

npm install @colvo/sdk — zero dipendenze, Node 18+. I nuovi tentativi sono sicuri: l’SDK invia una chiave di idempotenza deterministica, quindi un nuovo tentativo restituisce l’operazione originale invece di raddoppiarla.

// before
await stripe.refunds.create({ payment_intent: 'pi_01', amount: 5000 });
return reply('Refunded €50!');

// with Guard
import { Colvo, messageFor } from '@colvo/sdk';
const colvo = new Colvo({ apiKey: process.env.COLVO_AGENT_KEY }); // cak_… — can only propose and read

const op = await colvo.operations.propose({
  mandate_id, source_request_id: ticket.id, action: 'refund.create',
  parameters: { payment_id: 'pi_01', amount_minor: 5000, currency: 'eur' },
  context: { user_message: lastUserMessage, agent_reply: draftReply },
});
return reply(messageFor(op)); // honest for ALLOW / REVIEW / HOLD / DENY

Python

pip install colvo — solo libreria standard, Python 3.9+.

import os
from colvo import Colvo, ColvoError, message_for

colvo = Colvo(os.environ["COLVO_AGENT_KEY"])

def propose_refund(mandate_id, ticket_id, payment_id, amount_minor, user_message):
    op = colvo.operations.propose({
        "mandate_id": mandate_id, "source_request_id": ticket_id,
        "action": "refund.create",
        "parameters": {"payment_id": payment_id, "amount_minor": amount_minor, "currency": "eur"},
        "context": {"user_message": user_message},
    })
    return message_for(op)   # ColvoError(409, "business_key_conflict") if a different effect was already proposed

Nessun SDK per il tuo stack? È una sola chiamata HTTP: POST /v1/operations con Authorization: Bearer cak_…, un header Idempotency-Key e il body JSON visto sopra — vedi l’API di Guard.

n8n

  1. Sostituisci il nodo Stripe con un nodo HTTP Request: POST https://colvo.app/v1/operations, header Authorization: Bearer {{ $env.COLVO_AGENT_KEY }}, header Idempotency-Key: {{ $json.ticket_id }}:refund, body JSON come sopra.
  2. Aggiungi un nodo Switch su {{ $json.decision }} con quattro uscite: ALLOW, REVIEW, HOLD, DENY.
  3. Ogni uscita imposta il testo della risposta (tabella qui sotto) prima di Respond to Webhook. Elimina la credenziale Stripe dal workflow.

5. Di’ la verità al cliente

La decisione dice all’agente cosa è successo davvero. Mappala sulla risposta nel codice, non nel prompt, così il modello non può promettere un rimborso che non è mai stato inviato.

DecisioneCosa è successoRisposta del tipo
ALLOWIn coda; eseguita una volta, poi verificata in Stripe“Il tuo rimborso di €50 sta tornando sulla tua carta.”
REVIEWIn attesa che una persona approvi esattamente quel digest“Ho chiesto a un collega di approvare il tuo rimborso — riceverai un’email quando sarà fatto.”
HOLDNon è sicuro adesso (in pausa, limite raggiunto, provider non leggibile); non è stato inviato nulla“Ho registrato la tua richiesta; verrà gestita a breve.”
DENYFuori dal mandato; non viene inviato nulla, mai“Non posso rimborsare questo importo su questo ordine — il massimo che posso rimborsare è €50.”
// @colvo/sdk and the Python package ship this as messageFor / message_for (with overrides); the logic, to adapt:
function messageFor(op: { decision: string; parameters: { amount_minor?: number } }) {
  const eur = ((op.parameters.amount_minor ?? 0) / 100).toFixed(2);
  switch (op.decision) {
    case 'ALLOW':  return `Your €${eur} refund is on its way back to your card.`;
    case 'REVIEW': return `I've asked a colleague to approve your €${eur} refund — you'll get an email when it's done.`;
    case 'HOLD':   return "I've logged your request; it will be processed shortly.";
    default:       return "I can't do that on this order, but I've passed your request to the team.";
  }
}

6. Alza l’approvazione automatica

Dopo una o due settimane di approvazioni, guarda cosa approvano sempre i revisori — di solito piccoli rimborsi su un pagamento recente del cliente stesso. Imposta auto_allow_up_to_minor a quel livello nei mandati che emette il tuo backend (ad es. 2000). Tutto ciò che sta sopra continua ad andare a una persona; tutto ciò che è fuori dal mandato resta DENY.

  • Kill-switch: POST /v1/projects/{id}/pause — ogni nuova proposta diventa HOLD, le esecuzioni in coda restano in attesa.
  • Avvisi: MISMATCH e UNVERIFIABLE aprono un incidente e fanno scattare i tuoi webhook di avviso.
  • Continua a testare: il blocco in CI resta attivo a ogni rilascio dell’agente.

Opzionale: l’accesso vive nel tuo sistema

Molti prodotti concedono l’accesso dal proprio database, non da Stripe. Per le disdette, COLVO può leggere l’accesso dal tuo servizio, così “mantiene l’accesso fino al 31” viene verificato da capo a fondo. Aggiungi una connessione Service con un URL e un secret di firma; COLVO la chiama in sola lettura:

GET https://api.yourco.com/colvo/access?customer_id=cus_01
X-Colvo-Signature: t=1790000000,v1=<hex hmac-sha256 of "t.customer_id">

200 { "customer_id": "cus_01", "access_enabled": true, "access_until": "2026-10-31T23:59:59Z" }

Dopo una disdetta period_end, la verifica si aspetta access_enabled: true e access_until alla fine del periodo pagato; dopo una disdetta immediate, access_enabled: false. Un disaccordo è un MISMATCH. L’URL passa per le stesse regole di egress degli endpoint degli agenti.

Vuoi vederlo prima di collegare qualcosa? Prova la demo interattiva o prenota una call di 30 minuti e scriviamo con te il primo mandato.

Porta il tuo agente su Guard · Docs · COLVO