Chapter 4 of 11 · ~1 min

Rules first

Everything that has a rule gets a rule. In Kettle Co.'s process that is more than it first seems: is the enquiry complete, is the quantity above the minimum of ten, is the country one we sell to, is this an existing customer. None of these needs a model, and each was once decided by a rep glancing at an email.

How the agent built it. Three checks that do not depend on each other run inside a Parallel node, one per branch. Two are Code steps: small functions over named inputs, returning an object. The third is a Tool step that asks the CRM whether the company exists, through a connection the workflow only names. A fourth Code step, after the join, reads the three results and decides: qualified or not, with reasons. A Conditional then either declines politely or carries on.

Two details worth noticing. The parallel node's failure mode is set to skip, so if the CRM is unreachable the enquiry is treated as new rather than failing; the qualify step records that the check did not happen. And the minimum-quantity rule lives in one function that can be read, tested and changed without touching anything else. When the business changes the minimum, one number changes.

What the run shows. Three branches starting within a few milliseconds of each other, each writing its own result, the join, then the rule. No model was called. The whole section took just over a second, most of it the CRM round trip.

Experiment

Runs in your browser

The checks, the qualifying rule and the gate as the agent wrote them, and what the recorded run shows for this section.

Three checks in parallel, one rule, one gate. Excerpt from enquiry-to-quote.json as the agent wrote it after chapter 4's decision.

{
"type": "Parallel",
"id": "checks",
"join": { "mode": "all" },
"failure": { "mode": "skip" },
"branches": [
{ "id": "completeness", "children": [
{ "type": "Code", "id": "check",
"input": { "company": "{{enquiry.company}}", "quantity": "{{enquiry.quantity}}", "product_line": "{{enquiry.product_line}}" },
"expression": "function ({ company, quantity, product_line }) { var reasons = []; … if (!(qty >= 10)) reasons.push('minimum order is 10 units'); … return { ok: reasons.length === 0, reasons: reasons }; }" } ] },
{ "id": "territory", "children": [
{ "type": "Code", "id": "served",
"input": { "country": "{{enquiry.country}}" },
"expression": "function ({ country }) { var served = ['United Kingdom', 'Ireland', 'France', 'Germany', 'Netherlands', 'Spain', 'Italy']; return { served: served.indexOf(country) >= 0, country: country }; }" } ] },
{ "id": "customer", "children": [
{ "type": "Tool", "id": "lookup", "tool": "find_account", "connection": "crm",
"input": { "name": "{{enquiry.company}}" } } ] }
]
},
{
"type": "Code",
"id": "qualify",
"input": { "complete": "{{checks.completeness.check}}", "territory": "{{checks.territory.served}}", "account": "{{checks.customer.lookup}}" },
"expression": "function ({ complete, territory, account }) { … return { qualified: reasons.length === 0, reasons: reasons, existing_customer: existing, account_id: …, crm_checked: account !== null && account !== undefined }; }"
},
{
"type": "Conditional",
"id": "gate_qualified",
"condition": { "operator": "eq", "field": "qualify.qualified", "value": true },
"false": [ { "type": "Message", "id": "decline", "participant_role": "prospect", "content": "Thank you for the enquiry. We cannot quote this one: {{qualify.reasons}}. …" } ],
"true": [ … the rest of the process … ]
}
  1. 1If the CRM cannot be reached, the branch is skipped and the run continues. The qualify step records that the check did not happen.
  2. 2The rule from the brief, as one line of code. Change the number here and nowhere else.
  3. 3A logical name. The definition never learns the CRM's address or credentials; the deployment binds the name.
  4. 4A branch's result is read as parallel-id, branch-id, node-id. The function sees only what its input names.
  5. 5One boolean decides the route. Declines are a Message on the false branch; everything else is on the true branch.

The recorded run, this section

  1. 14:06:34.067 checks · Parallel · started; three branch items admitted
  2. 14:06:34.204 territory/served · Code · {served: true, country: "United Kingdom"} · 3 ms
  3. 14:06:34.213 completeness/check · Code · {ok: true, reasons: []} · 4 ms
  4. 14:06:34.212 customer/lookup · Tool find_account @ crm · provider operation success, 1 attempt · 1,135 ms
  5. 14:06:35.472 checks · joined (all three succeeded)
  6. 14:06:35.473 qualify · Code · {qualified: true, reasons: [], existing_customer: false, crm_checked: true} · 2 ms
  7. 14:06:35.477 gate_qualified · Conditional · true branch

No model was called in this section. The whole of it took 1.4 seconds, 1.1 of them waiting for the CRM.

Big question

Which check in your process is done by a person today only because nobody wrote the rule down?