Appearance
Automatic workflow triggers
A published workflow can start by itself: a run per ingested event of the types it names (trigger event), or per new subject (trigger subject.created). The integrating system sends events (POST /events) and creates subjects (POST /subjects, POST /decide) as it does anyway, and the runs follow, without a POST /workflow-runs per event. A filter narrows what starts a run, a debounce window keeps a subject's burst of events to two runs, and a rate cap bounds the runs a minute.
Arming a trigger
A workflow's trigger starts runs once the workflow is published with a triggerConfig:
bash
curl -X PUT "$API/workflow-definitions/card-monitoring" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"name": "Card monitoring",
"trigger": "event",
"triggerConfig": {
"eventTypes": ["payment"],
"filter": "event.amount > 1000.0 && event.attributes.status == \"approved\"",
"debounceSeconds": 300,
"maxRunsPerMinute": 600
},
"definition": { "entryStepId": "rules", "steps": [ … ] }
}'| Field | What it does |
|---|---|
eventTypes | The event types that start a run, up to 20. Required for event; subject.created takes none. |
subjectKinds | Only subjects of these kinds start a run (user, merchant, seller); any when unset. |
filter | A CEL expression over event, subject and now that must be true, up to 2 000 characters; checked when saved. |
debounceSeconds | 0 to 86 400 (default 0). Within this long of a subject's last start, its matches wait for the window's end, where the newest of them starts one more run and the others are coalesced into it. |
maxRunsPerMinute | 1 to 6 000, across subjects. Starts over the cap wait until the oldest start of the last minute is a minute old; none is dropped. |
Saving refuses (400) a manual workflow with a config, an event config without event types, a subject.created config with event types and a filter that does not compile or does not answer a boolean. An event or subject.created workflow saved without a config, as workflows were before triggers started runs, keeps its trigger as a label: it starts nothing, and the builder's check (POST /workflow-definitions/validate) warns trigger_not_armed. In the console the workflow's properties hold the trigger and its config, and Start runs automatically arms it.
Drafts start nothing; the published version's config is the one that counts, read when an event is stored and again when its run starts. Publishing a version with the trigger manual stops it: starts it had queued are cancelled.
The filter
The filter reads the event and the subject as they are when the run starts:
event:id,type,amount,currency,occurredAt,attributes,ip,deviceId,cardFingerprint,email. Empty forsubject.created.subject:id,kind,externalId,status,tags,riskScore,data.now, and the functionsdaysSince,lower,upperandtoNumber.
A filter runs under the same step budget as a rule's expression. One that fails (a missing field read without has(), a string compared with a number) gives the start up as failed: guard optional fields, as in has(event.attributes.mcc) && event.attributes.mcc == "7995".
What a triggered run gets
The run's trigger is event or subject (and decide for a POST /decide escalation), and its context starts with what started it besides subjectId and subject:
json
{
"trigger": {
"source": "event",
"event": { "id": "4133", "type": "payment", "amount": 6200, "currency": "EUR", "occurredAt": "…", "attributes": { … } }
}
}so its rules read trigger.event.amount as they read a step's output. A velocity rule in the run counts the triggering event too: it is stored before the run starts.
Exactly once
The start is written in the transaction that stores its source: an event that is stored has its starts queued, and a duplicate (the same idempotencyKey) queues nothing. Each queued start names its run's id before it starts, so a worker that dies mid-start or a retry starts the same run, once. The worker's trigger dispatcher takes queued starts up within a second; every worker runs it.
Following the starts
GET /workflow-triggers (runs:read) lists the queued starts newest first, filtered by definitionKey, status, subjectId and from/to; the console shows a workflow's latest under its trigger.
| Status | Meaning |
|---|---|
pending | Waiting for the dispatcher, or for its debounce window or the rate cap (dueAt). |
starting | Its run is being started; a failed start is tried again, up to 10 times, backing off. |
started | Its run started (runId). |
coalesced | A newer match of its subject's debounce window started the run (runId) for it. |
filtered | The filter or subjectKinds kept it out. |
cancelled | The workflow is no longer published with that trigger or event type, or its source is gone. |
failed | The filter failed, or the run did not start in 10 attempts (lastError). |
Operators watch the dispatcher with aletheia.workflow.trigger.count and aletheia.workflow.trigger.lag, and three alerts (Operations).