Documentation

Make it behave

Rules, workflows, watchers and escalations.

Audience: consultant. Prerequisite: 02 — Build the app.

⚙️ The same semantic validation this page relies on for rules and watchers also now checks definitions and reporting timezones — see 05 — Deploy and run. Two widgets claiming one term while computing it differently refuses a publish, as does a widget whose number differs from the term it claims (narrowing, grouping or sorting the term’s measure is fine), and a date bucket that reports in a different timezone from the rest of the app without saying why. An incomplete definition, and an app that has not declared its reporting timezone yet, only report.

An app that only stores data is a database with a nice front end. Behavior is what makes it worth deploying: it notices things, routes them to the right person, and moves work along without anyone watching.

All of this is Engagement ▸ Build.


The five mechanisms, and when to use which

MechanismFires whenUse it for
Rulea record changes“when a request is created, notify the ops manager”
Workflowa rule, an event, or a schedulemulti-step processes with waits, branches and approvals
Anomaly watchera metric moves oddly, on a schedule“refunds spiked”, “volume collapsed”
Surfacing rulea record matches a standing condition“these need a human”, ranked in Attention
Escalation chainan alert is not acknowledged“if nobody responds in 15 minutes, tell the next person”

The mental model: detection proposes, rules act. A watcher notices; a rule decides what to do about it. Keeping them separate is why you can retune detection without rewiring notifications.

What the app does on its own

Build ▸ Behavior (/engagements/:id/build/behavior) is one list of everything the app does by itself — rules and workflows together — each read as a sentence: When an appointment is updated → Start escalation. Each says Live or Not live, and when it is not live, why (a workflow still in draft, a rule turned off). Open a row to edit it.

⚙️ What you save here runs. New rule writes a rule, not a draft waiting for a second publishing step somewhere else — there is one place to author a rule and one meaning for “saved”.

Rules

New rule, on the Behavior list, opens the rule editor. It reads as the rule it builds:

  • When — a record (Appointment, Pet, …) and what happens to it: created, updated, deleted, run by hand, or an anomaly detected on it. Run by hand means a multi-step flow’s Run rule step runs it — no button in the client’s app can start a rule, and the editor and the list both say so. (Driven in a deployed app: a page button set to run a rule sent nothing.)
  • Only if — optional; a condition over that record’s real fields.
  • Then — one or more actions: notify, alert, set a field, assign, create a record, start an escalation, run a workflow, send a webhook. A field’s value takes that field’s own control — a choice list for a choice field, a number box for a number.
  • Name — optional. Left empty, the rule is named by its own sentence. Its machine key follows the name and sits under Advanced.

A rule is live the moment it is saved, while it is On.

⚙️ Every action the editor offers runs — notify, alert, start escalation, run workflow, send webhook, set a field, assign, and create a record. A rule’s own write does not fire other rules (so a rule can never loop); a chain of steps is what a workflow is for.

⚠️ Offered only if it runs. Require approval is not offered, because approvals cannot yet be created for an app; a rule saved with one before says, in the editor and on the list, that it does not run. The editor does not offer On a schedule or When a business event happens either — nothing fires a rule on those. For a schedule, build a multi-step flow, which can run on one.

If you are looking for start_escalation: it is Start escalation, an action in this editor — the only control that points an escalation chain at anything.

⚙️ Operators, triggers and actions are written as words, once. The choices you read — “is greater than”, “is at least (≥)”, “when a record is created”, “Start escalation” — come from one table per vocabulary in @ttp/contracts, beside the schema that defines the stored values — so a screen shows “is at least (≥)”, never the stored code gte, and every screen shows the same words for the same thing.

Scope every rule to an entity. A rule with no entity applies to every entity — so with two watchers, each one’s anomalies notify both audiences. This has bitten before; the wizards now set the entity for you, but check it when authoring by hand.

Recipients

A notify action’s recipient is a picker, in one of two modes:

  • a person or role — chosen from the engagement’s real directory, or
  • a dynamic token — {{event.assigneeId}}, resolved when the rule fires.

Free text survives only in the token mode, because a token is not a catalog member. Everything else is picked, because a typo’d recipient resolves to nobody and the notification silently reaches no one.

Workflows (Behavior Canvas)

Build a multi-step flow, on the Behavior list, opens the canvas; so does any workflow’s row.

The canvas is the single workflow editor. A workflow runs only once it is published — the list says Not live — a draft until then. Nodes are typed and click-authored — you do not hand-write JSON. The palette offers conditions, record operations, email, attention-queue items, AI invocation, HTTP calls, variables and arithmetic, waits, and document-generation requests.

What starts a workflow — and where it runs

⚙️ Every way of starting a workflow actually starts one:

  • On a schedule runs at its times, in the studio and in the deployed app. The first run is the next time after you publish it; it does not catch up on times it missed while dormant. Times are UTC (a UK summer 08:00 schedule runs at 09:00 local).
  • When an anomaly is detected runs when a watcher records a new anomaly, not one already on record. It narrows to the record type (or watcher) you pick. A record type no watcher watches can never start it. The canvas’s Records picker warns you and names the types that are watched, and the list shows Not live with the reason.
  • When someone runs it by hand has a Run now on its row in the Behavior list. That runs it here, in the studio. The client’s app has no button that can start a workflow yet, and the row says so. The same holds for a rule set to run by hand: a page button cannot run a rule in the client’s app, so Run rule is not offered as a page action.

What an Add to attention queue step raises is visible to the client’s own staff, on Needs attention in their app’s side rail (see the end-user guide). Each item can be marked done.

Branches, and the record that started the run

  • A condition has two exits, Yes and Otherwise. Draw from the exit you mean. The Otherwise path runs only when the condition does not hold, rather than running every time, even when the condition was true.
  • A run started by a record can use that record. Write {{record.title}} (any field, plus {{record.id}}, {{record.createdAt}}) in a title, a message or a value; Calculate can use record.amount * 2. Update record and Delete record offer The record that started this run. Until then, {{…}} was shown exactly as typed, and a workflow could not reach its own record.
  • Has related record and Within time window read the record that started the run, so they mean the same thing in Simulate and in a real run.
  • User has role is not offered. Nothing that starts a run supplies the acting person’s roles, so it could never be true. A workflow that already carries one still opens.

Two honesty notes worth knowing before you promise a client something:

  • Request document records an auditable generation request. No file is rendered — there is no PDF render service yet. The node says so.
  • Invoke AI is metered against the engagement’s AI budget. Attribution is a picker limited to this engagement or workspace-level — never another engagement.

Approvals

A workflow can wait for a human. The person is notified, the run pauses, and completing the task resumes it.

⚠️ The canvas cannot add a “wait for a person” step. The runtime supports one — a workflow created through the API can wait for a person — but the palette does not offer the step, so a consultant cannot author it by clicking. If the workflow behind a waiting task has been deleted, resuming fails loudly rather than hanging silently.

  • Name who decides. A wait step without an assignee is refused at publish, because its task would go to nobody.
  • A task is for its assignee. A seat sees and completes a task only when it is assigned to them, by name or by a role they hold, and they may read the record that started the run. So a region manager’s inbox holds only their region’s approvals. Administrators see every task.
  • Cancelling a run closes its open task, so an inbox does not keep a decision nobody is waiting for.

Anomaly watchers

Build ▸ Anomaly Watchers (/engagements/:id/build/anomaly-watchers).

A watcher is a metric over an entity, in a time window, with a detection method. Author it in the five-step wizard: Metric → Method → Tune & preview → Action → Save.

MethodFires when
Thresholdthe value crosses a fixed number
Rolling baselinethe value deviates from its own recent mean/median
Seasonalit deviates from the same point in previous cycles
Contextualit deviates from its peer group (per customer, per region)
Percentage changeit moves more than N% versus a prior window

Tune & preview replays the last 90 windows and shows you where the watcher would have fired. Use it. A watcher tuned without a preview is a guess.

Sustained windows

Threshold watchers have a Sustained windows setting. Leave it at 1 and the watcher fires on the first breaching window. Set it to 2 or 3 and the breach must persist that many consecutive windows before an event fires — which is how you stop a one-window spike from paging someone at 3am.

Editing a watcher

An existing watcher’s configuration is editable: open it and use Edit configuration, which reopens the same wizard with the watcher loaded. You never need to delete and recreate a watcher to retune it — doing so would throw away its detection history.

A watcher that notifies nobody

Detection alone changes nothing. A watcher needs a rule with an anomaly_detected trigger to turn a detection into a notification. The wizard’s Action step lets you create that rule inline, scoped to the watcher’s entity. If you skip it, the platform raises a configuration warning — a watcher that can detect but never notify is half-built, and it says so rather than looking healthy.

Watchers run on their own

Deployed watchers are scanned on a schedule by the running app. Nobody has to click anything. That is the point: the app notices while everyone is asleep.

⚙️ An anomaly that is still going is one event, not one per scan. Each scan looks at the window ending now. When that window overlaps an event this watcher has already raised for the same group, it is the same anomaly seen again: the event is refreshed in place, keeps its acknowledged status, and does not notify again — so a watcher scanning hourly does not turn one anomaly into ten copies and ten alerts.

Surfacing rules

Build ▸ Surfacing Rules (/engagements/:id/build/surfacing).

Where a watcher asks “is this number strange?”, a surfacing rule asks “does this record need a human?” Matching records are ranked into Run ▸ Attention. A surfacing rule can also notify a role, using the same expansion the rule and watcher paths use.

Escalation chains

Build ▸ Escalation Chains (/engagements/:id/build/escalation-chains).

A chain is notify → wait → escalate: each step has recipients, channels, a wait, and whether acknowledgment is required. If the alert is not acknowledged inside the wait, it climbs to the next step. An acknowledged alert stops climbing.

Recipients are pickers over the engagement’s roles and client users, with a literal email as the escape hatch for someone outside the roster.

Notification routing — the part people get wrong

Routing is per event type, and the test of a correct routing is two-sided:

the right role receives it and every other role receives nothing.

Check both. A notification that reaches everyone is nearly as bad as one that reaches nobody — it trains the client to ignore the bell.

Channels (in-app, email, and the configured external channels) are set per step or per action. The in-app bell is always available; external channels need to be configured at Firm ▸ Platform ▸ Notification Channels (/tenant-notification-config).


What “done” looks like for Behavior

  • Every rule is scoped to an entity.
  • Every watcher has a preview you actually looked at, and a notify rule.
  • Every notification was tested two-sided (right role yes, other roles zero).
  • Nothing that acts on a schedule depends on you remembering to click it.
  • You have not promised the client a rendered PDF.