The submit
The submit ends the playbook: it evaluates its submit guard, runs the reserved writes in order — the skills' calls and the playbook's —, and closes the journey.
submit_guard:
form: {accept_terms: {type: boolean, required: true, label: "I accept the terms and conditions"}}
confirm: true
guards: [{policy: commande-inhabituelle}]
submitted_by:
- {who: agent, when: "total <= 50 and agent.mode == 'autonomous'"}
- {who: {role: support-manager}, when: "total > 50"}
- {who: owner}
submitted_by — who can submit
Rules in order; the first one that matches applies.
who | meaning |
|---|---|
owner | the verified owner of the room: a person whose identity was proven by a provider (Identity). A number that was only typed in is not enough |
principal | the person who sent the agent |
user.approver | the authority above the user: the approver set by the client organisation (product), their manager (process) |
{role: x} | a user who has this role |
agent | the agent itself |
All are users. An admin never approves or submits a business action. See Human In The Loop.
- Without
submitted_by, a human in the room submits, even anonymous: the customer's click. This is today's behaviour. Requiringownercloses this path to anyone who only holds the room code. - No autonomy without a condition: a
who: agentrule withoutwhenis refused at load time. An agent rule that covers ahighand irreversible write gives a warning. - Evaluated by Core, at submit time. Neither hiding nor showing a tool grants or removes this right.
- The receipt says who submitted, and under which rule.
When several layers apply (the playbook, a sub-identity, a profile), the strictest wins: if one requires a human, it is a human.
An order is declared on the playbook
A call lives where what it files lives:
- A call that files one action of a skill — a refund, a delivery, a pickup — lives on the skill or on its kind (Skills).
- A call that sends the lines of several tasks in one request — an order: the dishes, the
drink, the cutlery, in a single
placeOrder— is declared once, on the playbook, insubmit, next to itssubmit_guard. - A skill used outside any playbook keeps all its calls.
playbooks:
- name: take-meal-order
tasks:
plats: {skill: takeMealSelection}
boisson: {skill: takeDrinkSelection, after: [plats]}
submit:
- name: order
tool: placeOrder
args:
body.items: "$selection[task=plats,boisson]"
body.customer.phone: "$submit.phone"
body.idempotency_key: "$action.key"
line: {product_id: "$line.ref", quantity: "$line.quantity", variant_id: "$line.fields.variante"}
extra_lines:
- {ref: couverts-avec, quantity: "$submit.couverts", when: "$submit.couverts > 0"}
- {ref: couverts-sans, when: "$submit.couverts == 0"}
output: {order_id: {from: "$output.id", type: id}}
submit_guard:
form:
phone: {type: phone, required: true, label: "Téléphone"}
couverts: {type: quantity, required: true, label: "Combien de couverts ?"}
- Lines are taken by task:
$selection[task=plats,boisson]sends the lines of those two tasks;$selection[kind=dish]those of a kind, among the playbook's tasks.$selectionalone is refused in a playbook's call: what it sent would depend on the lines present in the room. - A line leaves through one call only. In a playbook where a call takes a task's lines, the calls of that task's skill do not run for those lines: the playbook's call sends them. Outside that playbook, the skill keeps its calls.
- A line no call sends is refused: at load, a skill whose lines leave through no call — neither its own, nor that of a playbook that uses it; at submit, a line chosen outside a playbook by a skill with no call of its own is held, with the sentence that says to open the journey. It never leaves for nowhere.
- A playbook's call does not read
$item: it sends several lines. What comes from a line goes throughline:; what comes from the person, through the submit's form ($submit.<field>). - It is cited by its name alone:
$submitted[order].order_id. A skill's call is cited by its task and its name ($submitted[geste.pay]). The names of a playbook's calls are unique in that playbook. A task whoseparamsread$submitted[order]waits for the tasks whose lines the call takes. - Two playbooks that place the same order each declare it: each submit has its call, never two
copies of one call in one submit. A YAML anchor (
&order,*order) avoids copying it.
line: — the shape of a line
line: says what one line looks like to the tenant. It is declared on the call that sends the
lines: the playbook's, or a skill's.
| form | sends |
|---|---|
line: {product_id: "$line.ref", quantity: "$line.quantity"} | one object per line, with the tenant's names |
line: "$line.ref" | the bare reference, for a tenant that expects a list of identifiers |
| nothing | {slug: "$line.ref", qty: "$line.quantity"}, Convoy's shape |
The sources: $line.ref (the item's reference), $line.quantity, $line.price (the line's billed
price), $line.fields.<field> (a field of the line). All are checked at load:
$line.fields.<field>must be a declared field of the line's kind: a field ofpresent.fieldsor a line field. A name no kind of the call's lines declares is refused.- A key whose source does not exist on a line is left out, never sent empty: an optional field left unanswered, a field this line's kind does not have.
- The
"$line.ref"form is refused on a task that has quantities: the quantity would be lost. - Check
line:against the tenant's schema. A key it does not know, it ignores and applies its own default, with no error: withoutline:, a tenant that expectsquantityreceivesqtyand ships a single piece.
extra_lines — lines sent without being chosen
Some lines leave with the order without anyone choosing them: the cutlery, when the restaurant keeps it as two products at 0 F.
extra_lines:
- {ref: couverts-avec, quantity: "$submit.couverts", when: "$submit.couverts > 0"}
- {ref: couverts-sans, when: "$submit.couverts == 0"}
refis a reference in the tenant's catalogue.quantityis of typequantity: a number, or$submit.<field>;1without it. A quantity that is not a positive integer holds the call, with the sentence that says so: never a line at 0.whenis written in the condition language. It is the only place where a condition reads$submit.<field>: a field of the form of the same playbook'ssubmit_guard, or of the station's for a skill's call outside a playbook (checked at load). A field of that form read as a quantity refuses a negative number: nowhenthen lets an order leave without the line planned.- The added lines join the call's lines: it takes them from one
$selection[…]source, or it is refused. They take the shape of itsline:; an added line has onlyrefandquantity, and the keys that read$line.priceor$line.fieldsare left out of it. - A
refmust exist at the tenant. The provisioner checks it at every provisioning. A missingrefis a provisioning error: the station does not go live, rather than send a ghost line. Through which read of the tenant it checks is not fixed yet: an added line was shown by no skill.
Execution
- In order: a call that reads
$submitted[…]runs after the one it reads; otherwise, the tasks' calls in the order of the tasks, then the playbook's in the order it writes them. Irreversible ones last. - A write that reads the result of a failed write is put on hold, never run on an empty value.
- Idempotency per action (
$action.key): a retry, a double click, an automation triggered twice produce only one effect. - Partial failure: what was executed stays executed. Cancelling goes through the operation
declared as
reversible, as an explicit action.
A sub-playbook has its own submit
Its submit runs only its actions. The parent resumes with its result. See Playbooks.