Skip to main content
Version: Current

Process · advanced — a ticket that ends in a refund

The case. A customer asks for a refund in a ticket. The agent reads the ticket and the invoice, offers the allowed refund and prepares the reply. A support person confirms: the refund goes out, then the reply, which quotes its number.

What the example shows. Several tools (support, payments), reads that inform the agent, a validation by the payment tool, two writes executed in order by a single submit, the second reading the result of the first.

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

What happens​

stepCoreresult
the agent reads the ticket and the invoicelire-ticket, verifier-facturethe context, without writing anything
offers the refundrembourser → proposeRefund; auto_trigger opens the journeyone card per refundable line
the chosen linereason requested, then checkRefundPolicyreserved, or refused with the payment tool's reason
the reply preparedrepondre → draftReply; depends_on: remboursersecond line
"Approve" + confirmationcreateRefund, then sendReply with refund_idtwo receipts
the refund failsADR-028 §3the reply is put on hold: you do not announce a refund that did not happen

Key points — and what is missing​

This case works today, but several guarantees still rest on the agent or on the person:

  • The order: nothing requires reading the invoice before offering. The instructions ask for it; Core does not guarantee it.
  • The amount: it comes from the payment tool (proposeRefund), not from the model. Good. But nothing compares this amount with the invoice that was read.
  • The approval: the submit confirmation is the approval. There is no threshold ("above €50, a manager"), and no person different from the one who prepares.
  • The irreversible: createRefund and sendReply are declared reversible: false, and the MCP door announces the submit as destructive. Nothing else reads it: the submit order stays the config's. deleteCustomer is listed exposed: false: documented, never reachable.
  • The recipient of the reply comes from the support draft; nothing prevents a reply hijacked by malicious content in the ticket.

The target version of this example closes each of these points.