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
| step | Core | result |
|---|---|---|
| the agent reads the ticket and the invoice | lire-ticket, verifier-facture | the context, without writing anything |
| offers the refund | rembourser → proposeRefund; auto_trigger opens the journey | one card per refundable line |
| the chosen line | reason requested, then checkRefundPolicy | reserved, or refused with the payment tool's reason |
| the reply prepared | repondre → draftReply; depends_on: rembourser | second line |
| "Approve" + confirmation | createRefund, then sendReply with refund_id | two receipts |
| the refund fails | ADR-028 §3 | the 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:
createRefundandsendReplyare declaredreversible: false, and the MCP door announces the submit as destructive. Nothing else reads it: the submit order stays the config's.deleteCustomeris listedexposed: 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.