Skip to main content

Nodes and transitions

A node is one place a participant can be in a journey; a transition is a one-way way out of it. Nodes carry no content, only actions to run on arrival and the transitions out.

start standard answered, end
┌─────────┐ always ┌─────────────┐ if ≥ 10 ┌─────────┐
│ welcome │─────────────►│ sleep-check │─────────────►│ support │
└─────────┘ └─────────────┘ priority 0 └─────────┘
│ answered, priority 1
▼
┌──────────┐
│ week-one │
└──────────┘

Nodes​

  • id is the wiring. Transitions point at it, so changing it breaks every transition into the node. name is the label, free to change.
  • type is start (where an assignment begins), standard, or end (reaching it completes the journey; its transitions are never read). Beyond that the types are markers: a transition may lead into a start, and one out of an end is accepted and ignored.
  • Loops are allowed. A node may point back at itself or an earlier node; give the way back a When that needs something from the participant, so each lap waits for it.

Transitions​

A transition is when something happens, if something holds, go to a node:

{ "toNodeId": "support",
"when": [{ "source": "sequenceResponses.SLEEP_CHECK", "operator": "answered" }],
"if": { "source": "sequenceResponses.SLEEP_CHECK.latest.scores.TOTAL", "operator": "at_least", "value": 10 } }

Both are made of the same clause: a source (a path naming the one thing, by code), an operator, and a value only where it compares what the source holds.

when lists what makes the transition look; any one is enough. Its clauses take event operators: what has just happened to the source, or the time it has reached.

SourceOperatorLooks
journeys.currentcheckedOn arriving, and every time the journey looks again
sequenceResponses.<sequence>answeredEach time that sequence is answered while they are here
atomResponses.<atom>answeredEach time that atom is answered, in a sequence or on its own
time.inNode, time.inJourneyreached, with a value in secondsFrom that long after arriving at the node, or after the journey started
A date (extras.QUIT_DATE.latest, a date answer)reachedFrom the date and time it holds
A date's days (extras.QUIT_DATE.latest.daysUntil)reached, with a valueWhen the days come to it
tags.<tag>, groups.<group>, relationships.<relationship>, cohorts.<cohort>created, deletedWhen its assignment to the person is created or deleted (shown as "assignment created", "assignment deleted")
extras.<extra>.latestset, cleared, changedWhen the extra is given a value, cleared or given another

if is a condition of state operators that must hold for the transition to be taken; without one, the When alone decides. Things are named by their code (Referencing a participant's data).

  • Order decides. Transitions with a priority are checked first, lowest first; the rest follow in the order written. The first whose When looks and whose If holds is taken, so put the specific case before the general one: a checked When with no If first swallows every one after it.
  • A checked When with no If written last is the "otherwise".
  • A journey looks again at the next moment anything could change: a time or date coming due, a reading arriving, the hour or the day turning. A wait is the node's clock reaching it.

Checks​

CheckWhen
duplicate_node_id, duplicate_action_id, transition_target_missing, a wait shorter than the floor, and journey_too_large (more than 500 nodes, 50 transitions from one node, 32 levels of nesting or 500 clauses in one rule)Every save, draft included: the save is refused
start_node_missing, multiple_start_nodes, dead_end_node (a node that isn't end and has no way out)Errors: a version read back lists them, and publishing is refused while there are any
A path or token that doesn't read, or can't be used where it is written (unknown_path, not_here, personal_in_external, …)Errors, the same way
node_unreachable, journey_empty, since_active_without_node_waitWarnings: listed, never blocking