Lleva tu agente a Guard
Un camino paso a paso de “el agente llama a Stripe por su cuenta” a “el agente propone y COLVO ejecuta”: primero observas, luego apruebas a mano y después dejas pasar automáticamente las acciones pequeñas y seguras.
Checklist
- La suite está en verde. La versión actual de tu agente supera la suite de reembolsos y cancelaciones en COLVO Test, con tres intentos por escenario.
- Stripe está conectado con una clave restringida. Escritura en reembolsos y suscripciones; todo lo demás, lectura o sin acceso. Solo el executor de COLVO la ve.
- Tu backend emite un mandato por ticket. Cliente, pagos, suscripciones, acciones, límites y una caducidad, decididos por tu código y nunca por el modelo.
- Modo observación durante una semana. El agente sigue actuando como hoy e informa de cada acción; COLVO registra lo que habría decidido y comprueba el resultado en Stripe.
- Aplica las reglas con aprobaciones. Quítale la clave de Stripe al agente, haz pasar cada acción por
POST /v1/operationsy pon la aprobación automática a cero para que una persona apruebe cada una. - Sube la aprobación automática. Cuando los revisores aprueben a diario los mismos reembolsos pequeños, déjalos pasar automáticamente. Ten el kill-switch siempre a un clic.
1. Conecta Stripe con una clave restringida
En Stripe, crea una clave restringida con Refunds: write, Subscriptions: write, Customers, Charges, PaymentIntents, Invoices: read y todo lo demás en none. Conéctala desde la página del proyecto (Connections → Connect Stripe) junto con el secreto de firma de webhooks; COLVO comprueba los permisos en solo lectura (sin reembolso de prueba) y te muestra la URL del webhook que debes añadir en Stripe con los eventos refund.created, refund.updated, charge.refunded, customer.subscription.updated, customer.subscription.deleted. Las claves live deben ser restringidas (rk_live_…); una clave sk_live_ de acceso completo se rechaza. Se cifra por organización y nunca la devuelve la API, ni aparece en logs ni se pasa a un modelo.
¿Todavía no tienes cuenta de Stripe? POST /v1/projects/{id}/connections/sandbox te da una simulada para ensayar todo el recorrido.
2. Emite un mandato desde tu backend
El mandato es la autorización con la que trabaja el agente. Tu backend lo crea cuando se abre un ticket o una conversación, a partir de datos en los que ya confía (el cliente con sesión iniciada, sus pedidos) y no de lo que diga el modelo. Referencia completa de los campos: Mandatos y conexiones.
// 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 agent3. Primero observa (para los clientes no cambia nada)
En modo observación, el agente mantiene su código actual. Después de actuar (o de decidir no hacerlo) informa de lo que hizo. COLVO evalúa la misma política que aplicaría, guarda la decisión que habría tomado y vuelve a leer el proveedor para comprobar que el efecto coincide con la respuesta. Nunca bloquea, nunca ejecuta y nunca necesita acceso de escritura.
POST /v1/observations: clave de agente o de 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"Envía "performed": false cuando el agente decidió no actuar: la decisión hipotética se registra igualmente, solo que no hay nada que verificar. Las acciones observadas bajo un mandato cuentan para sus límites exactamente igual que las operaciones, así que un segundo reembolso que superaría el total aparece como DENY. Una verificación que no coincide abre un incidente y dispara tus alertas, aunque no se haya bloqueado nada.
La página Observations de la consola muestra cada observación con su decisión hipotética y su verificación, además de un resumen semanal: cuántas acciones se habrían permitido, enviado a revisión o denegado, y cuántas respuestas no coincidían con el estado del proveedor. Cuando esa lista lleve una semana sin sorpresas, pasa al siguiente paso.
4. Aplica las reglas: una llamada sustituye a la de Stripe
Quita la clave de Stripe del entorno del agente. Donde antes llamaba a Stripe, ahora propone. Empieza con auto_allow_up_to_minor: 0 para que cada acción sea REVIEW y una persona la apruebe en la página Approvals de la consola. COLVO envía un email a todos los owners y editores cuando hay una acción en espera (y publica en los canales de alertas suscritos a las aprobaciones), les recuerda al 75 % de la ventana y, si nadie decide a tiempo, cancela la operación antes de enviar nada y les avisa.
Node / TypeScript
npm install @colvo/sdk: cero dependencias, Node 18+. Los reintentos son seguros: el SDK envía una clave de idempotencia determinista, así que un reintento devuelve la operación original en lugar de duplicarla.
// 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 / DENYPython
pip install colvo: solo biblioteca estándar, 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¿No hay SDK para tu stack? Es una sola llamada HTTP: POST /v1/operations con Authorization: Bearer cak_…, un header Idempotency-Key y el cuerpo JSON de arriba; consulta la API de Guard.
n8n
- Sustituye el nodo Stripe por un nodo HTTP Request:
POST https://colvo.app/v1/operations, headerAuthorization: Bearer {{ $env.COLVO_AGENT_KEY }}, headerIdempotency-Key: {{ $json.ticket_id }}:refund, cuerpo JSON como el de arriba. - Añade un nodo Switch sobre
{{ $json.decision }}con cuatro salidas: ALLOW, REVIEW, HOLD, DENY. - Cada salida define el texto de la respuesta (tabla de abajo) antes de Respond to Webhook. Elimina la credencial de Stripe del workflow.
5. Dile la verdad al cliente
La decisión le dice al agente lo que ha pasado de verdad. Tradúcela a la respuesta en el código, no en el prompt, para que el modelo no pueda prometer un reembolso que nunca se envió.
| Decisión | Qué pasó | Respuesta del estilo de |
|---|---|---|
ALLOW | En cola; ejecutada una vez y luego verificada en Stripe | “Tu reembolso de €50 ya está de camino a tu tarjeta.” |
REVIEW | Esperando a que una persona apruebe ese digest exacto | “He pedido a un compañero que apruebe tu reembolso; recibirás un email cuando esté hecho.” |
HOLD | Ahora no es seguro (en pausa, límite alcanzado, proveedor ilegible); no se ha enviado nada | “He registrado tu solicitud; se procesará en breve.” |
DENY | Fuera del mandato; no se envía nada, nunca | “No puedo reembolsar eso en este pedido; lo máximo que puedo reembolsar son €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. Sube la aprobación automática
Tras una o dos semanas de aprobaciones, fíjate en lo que los revisores aprueban siempre: normalmente reembolsos pequeños sobre un pago reciente del propio cliente. Define auto_allow_up_to_minor a ese nivel en los mandatos que emite tu backend (p. ej. 2000). Todo lo que esté por encima sigue yendo a una persona; todo lo que esté fuera del mandato sigue siendo DENY.
- Kill-switch: POST
/v1/projects/{id}/pause: cada nueva propuesta pasa aHOLDy las ejecuciones en cola esperan. - Alertas:
MISMATCHyUNVERIFIABLEabren un incidente y disparan tus webhooks de alertas. - Sigue probando: el bloqueo en CI sigue activo en cada versión del agente.
Opcional: el acceso vive en tu propio sistema
Muchos productos conceden el acceso desde su propia base de datos, no desde Stripe. Para las cancelaciones, COLVO puede leer el acceso desde tu servicio, de modo que “mantiene el acceso hasta el día 31” se verifica de principio a fin. Añade una conexión Service con una URL y un secreto de firma; COLVO la llama en modo solo lectura:
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" }Tras una cancelación period_end, la verificación espera access_enabled: true y access_until al final del periodo pagado; tras una cancelación immediate, access_enabled: false. Cualquier discrepancia es un MISMATCH. La URL pasa por las mismas reglas de egress que los endpoints de los agentes.
¿Quieres verlo antes de conectar nada? Prueba la demo interactiva o reserva una llamada de 30 minutos y escribimos contigo el primer mandato.