Graine AI

Paths and data

How a run chooses its next step — rules, Otherwise and the workflow's fallback — the values every step can read, saving your own values, and filling requests with them.

After a step, the run takes one of its paths. The paths are tried in order, and the first whose rule holds is taken. When none holds, the step's Otherwise path is taken. A step with no Otherwise falls back to the workflow's own, so every run reaches an End, even one nobody planned for.

Rules

A rule compares values. In the dashboard you pick a value, a comparison and what to compare it with, and join several with all of or any of.

ComparisonHolds when
is / is notThe two are equal, or not. Text is compared without case unless you ask for exact case.
more than, at least, less than, at mostBoth are numbers and the comparison holds. Anything that is not a number makes it false.
is one ofThe value is in your list of up to 100.
containsThe text, or the list, contains it.
is setThe value exists.
alwaysAlways. Use it for the one path out of a step.

A value that is missing makes a comparison false, never an error, so put the paths that need a value first and let Otherwise catch the rest. Values can be worked out first, too: call.duration_s divided by 60, or a balance minus a payment.

In a definition, a path is {"when": <rule>, "then": {"to": "<step>"}}, with an optional label that names it on the canvas (the editor also keeps each step's position in layout; neither changes how a run moves):

{ "when": { "all": [
    { "cmp": "==", "left": { "var": "call.status" }, "right": { "const": "completed" } },
    { "cmp": ">=", "left": { "var": "call.duration_s" }, "right": { "const": 30 } }
  ] },
  "then": { "to": "facts" } }

The forms a rule can take are cmp (with ==, !=, >, >=, <, <=, in, contains), all, any, not, exists and always. A value is {"var": "call.status"}, a constant {"const": ...}, or a sum {"op": "/", "args": [...]} with +, -, * or /.

What a step can read

ValuesCome fromLast
entry.*The contact as it entered: for a person of your app, their ID in your app is entry.user_id (and its field entry.user_field)The whole run
run.*The run itself: id, workflow_id, version, campaign_id, callback_urlThe whole run
call.*The latest Call stepUntil the next Call step
extraction.*The latest Read call results stepUntil the next Call step
response.*The latest HTTP request stepUntil the next one
callback.*The latest Wait for your system stepUntil the next one
message.*The latest WhatsApp, SMS or email stepUntil the next one
automation.*The latest Run an automation stepUntil the next one
history.*The latest Check call history step: how many times the person was called lately, by any of your agents, and how many of those they answeredUntil the next one
trigger.*What started the run: for a call that ended, trigger.call (status, direction, duration, its id); for a chat that ended, trigger.chat (its id, how many messages, how long it lasted)The whole run
state.*Values you saved on a pathThe whole run
node.<step>.*Every step's own results, kept by step idThe whole run

node.<step>.* is how you read an earlier step after a later one has replaced it. After a second call, node.first_call.call.status still says how the first call went.

A Check call history before a Call is how you keep a person from being called too often by all your agents together — a rule on what it counted, and the call only on Otherwise:

"cases": [
  { "when": { "cmp": ">=", "left": { "var": "history.calls" }, "right": { "const": 3 } }, "then": { "to": "called_enough" } },
  { "when": { "cmp": ">=", "left": { "var": "history.answered" }, "right": { "const": 1 } }, "then": { "to": "already_talked" } },
  { "when": { "cmp": "==", "left": { "var": "history.status" }, "right": { "const": "unavailable" } }, "then": { "to": "not_checked" } }
],
"on_no_match": "call"

Two of them, one by number and one in the app, are both kept (node.<step>.history.*), so a rule can add them up with +.

Saving your own values

A path can save values as the run takes it, up to 10. A saved value is kept for the whole run under state.*. Use it to remember a choice, keep a count, or hold something for a later request.

{ "when": { "cmp": "==", "left": { "var": "extraction.interested" }, "right": { "const": true } },
  "then": { "to": "crm", "effects": [ { "set_value": { "state.lead_temperature": { "const": "warm" } } } ] } }

Values in requests

A request's URL path, headers and body can use placeholders, {name}, each defined in the step's values with where it comes from, a type and whether it is required. A placeholder that is the whole of a body field keeps its type, so a number stays a number.

{ "body": { "phone": "{phone}", "amount": "{amount}" },
  "variables": {
    "phone": { "value": { "var": "entry.mobile_number" }, "required": true },
    "amount": { "value": { "var": "entry.amount_due" }, "type": "number" }
  } }

A required value that is missing ends the run

The run ends as failed (required_variable_missing) rather than send a request with a hole in it. An optional value that is missing is left empty, and the run's timeline notes it.

Checked before it is published

Check (and publishing) refuses a workflow that could misbehave, and pins each problem to its step:

Check: the problems listed beside the canvas, each one marked on the step it is about

  • a path to a step that does not exist;
  • a loop that is not made with Try again;
  • a Read call results step that is not right after its Call;
  • a Wait for your system with nothing to do on timeout;
  • a Check call history that counts back less than 1 hour or more than 90 days, or not in whole hours or days;
  • a request to a private address, or a URL whose host comes from a value;
  • an agent that is not yours, or was deleted;
  • an End without a label and an outcome.

It also refuses a workflow over its limits: 200 steps, 25 paths a step, 10 saved values a path, 50 start fields, 512 KB. An agent that is switched off is a warning, not a refusal: the workflow can be published, and its calls wait until the agent is switched on.

On this page