Aller au contenu principal
Version: Actuelle

Process · avancé — un ticket qui finit en remboursement

Le cas. Un client demande un remboursement dans un ticket. L'agent lit le ticket et la facture, propose le remboursement autorisé et prépare la réponse. Une personne du support confirme : le remboursement part, puis la réponse, qui cite son numéro.

Ce que l'exemple montre. Plusieurs outils (support, paiements), des lectures qui informent l'agent, une validation par l'outil de paiement, deux écritures exécutées dans l'ordre par un seul submit, la seconde lisant le résultat de la première.

examples/actuelle/process-advanced.yaml
# Process · advanced — a support ticket ends in a refund and a reply.
# Several tools (helpdesk, payments, email). Reads inform the agent; two writes are filed by ONE
# submit, in order, the reply carrying the refund's id; a reason and a confirmation are required.
station:
id: support-remboursement
name: Support · remboursements

global:
language: fr
currency: EUR
locale: fr-FR

domain:
domain_type: saas
entity_name: action
entity_name_plural: actions


agent:
tone: professional
name: Support
persona: "un agent de support qui prépare le geste commercial et la réponse au client"
rules:
- "Toujours lire le ticket et la facture avant de proposer un remboursement."
- "Ne jamais proposer un montant supérieur à celui de la facture."
emoji: false
portals:
web:
widget:
origins: [https://support.example.fr]

apis:
- name: helpdesk
openapi_url: https://helpdesk.example.fr/openapi.json
base_url: https://helpdesk.example.fr
operations:
- {id: getTicket, access: read}
- {id: draftReply, access: read}
- {id: sendReply, access: write, reversible: false}
auth: {type: api_key, key_name: Authorization, key_location: header, key_env: HELPDESK_KEY}
- name: paiements
openapi_url: https://payments.example.fr/openapi.json
base_url: https://payments.example.fr
operations:
- {id: getInvoice, access: read}
- {id: proposeRefund, access: read}
- {id: checkRefundPolicy, access: read}
- {id: createRefund, access: write, reversible: false}
- {id: deleteCustomer, access: write, exposed: false} # listed, never reachable
auth: {type: api_key, key_name: Authorization, key_location: header, key_env: PAYMENTS_KEY}

skills:
- name: lire-ticket
invoke: {method: getTicket}
- name: verifier-facture
invoke: {method: getInvoice}
- name: rembourser
auto_trigger: ticket-remboursement
cognitive:
objective: "proposer le remboursement autorisé pour la facture du ticket"
diagnose: [le motif du client, la ligne de facture concernée]
invoke: {method: proposeRefund} # the refundable lines of the invoice
kinds: {refund: {}}
present:
fields:
name: {from: label, type: text, role: title}
price: {from: amount, type: price, role: billed}
select:
max_items: 1
guards:
- form:
reason: {type: text, required: true, label: "Motif du remboursement"}
- check: checkRefundPolicy # the payment tool says whether this refund is allowed
submit:
- name: pay
mode: request
tool: createRefund
args:
body.line_id: "$item.ref"
body.amount: "$item.price"
body.reason: "$item.fields.reason"
body.idempotency_key: "$action.key"
output: {id: "$output.id"}

render: {component: cards}
- name: repondre
cognitive:
objective: "préparer la réponse au client qui annonce le remboursement"
invoke: {method: draftReply}
kinds: {reply: {}}
select:
max_items: 1
depends_on: rembourser # no reply about a refund that is no longer chosen
on_loss: clear
present:
fields:
name: {from: subject, type: text, role: title}
submit:
- name: send
mode: request
tool: sendReply
args:
body.draft_id: "$item.ref"
body.refund_id: "$submitted[rembourser.pay].id" # filed after the refund; held if it failed
render: {component: cards}

playbooks:
- name: ticket-remboursement
goal: "rembourser le client puis lui répondre"
tasks:
rembourser: {skill: rembourser, required: true, label: "Remboursement"}
repondre: {skill: repondre, required: true, label: "Réponse au client"}

submit_guard:
confirm: true # a person on the support team files both actions

Ce qui se passe​

étapeCorerésultat
l'agent lit le ticket et la facturelire-ticket, verifier-facturele contexte, sans rien écrire
propose le remboursementrembourser → proposeRefund; auto_trigger ouvre le parcoursune carte par ligne remboursable
la ligne choisiemotif demandé, puis checkRefundPolicyréservée, ou refusée avec la raison du paiement
la réponse préparéerepondre → draftReply; depends_on: rembourserdeuxième ligne
« Valider » + confirmationcreateRefund, puis sendReply avec refund_iddeux reçus
le remboursement échoueADR-028 §3la réponse est mise en attente : on n'annonce pas un remboursement qui n'a pas eu lieu

À retenir — et ce qui manque​

Ce cas fonctionne aujourd'hui, mais plusieurs garanties reposent encore sur l'agent ou sur la personne :

  • L'ordre : rien n'impose de lire la facture avant de proposer. La consigne le demande ; Core ne le garantit pas.
  • Le montant : il vient de l'outil de paiement (proposeRefund), pas du modèle. Bien. Mais rien ne compare ce montant à la facture lue.
  • L'approbation : la confirmation du submit est l'approbation. Il n'y a pas de seuil (« au-delà de 50 €, un manager »), ni de personne différente de celle qui prépare.
  • L'irréversible : createRefund et sendReply sont déclarées reversible: false, et la porte MCP annonce le submit comme destructif. Rien d'autre ne le lit : l'ordre du submit reste celui de la config. deleteCustomer est listée exposed: false : documentée, jamais joignable.
  • Le destinataire de la réponse vient du brouillon du support ; rien n'empêche une réponse détournée par un contenu malveillant du ticket.

La version cible de cet exemple ferme chacun de ces points.