Company automations
An automation is a rule of one company: "when this happens, do that". You compose it from triggers and actions that the core and the installed modules provide, write it as a JSON graph and save it with omm automation. Use this page when the human asks for an automation, or when your module registers triggers or actions and you want to test them on a real graph.
Draft first, enable only with consent
omm automation create and update always save a disabled automation. Show the human the graph and what it will do, and run omm automation enable only after they agree. Enabling, deleting or changing an enabled automation acts on the company's real data; ask first.
What an automation is
- Triggers start it: a module event (an order was created, a shipment changed status) or the built-in
cron. An automation can have several triggers; any of them starts it. - Actions form a graph. A run starts at
startActionId, executes the action and follows one of its branches to the next action. A plain action has one output,next; a condition hastrueandfalse; module actions may declare their own outputs. A branch set tonullends the run. - The trigger hands the actions event params, for example the order id. Every key is full:
<moduleCode>:KEY, such ascore/orders:ORDER_ID. - Each firing creates a run. Runs of one company go through one queue, one at a time: a long action delays every later run of the company.
- Only an enabled automation reacts to events. There are no loops: a graph with a cycle is rejected and an action runs at most once per run. Repetition comes from a trigger (
cron) or from an action that waits.
Graph format
The file you pass to omm automation create -f (update takes any subset of the fields):
{
"name": "Weekday check of the vacation mode",
"desc": "Every weekday at 8:00: mail the office while the vacation mode is on",
"triggers": [
{ "type": "cron", "params": { "pattern": "0 8 * * 1-5" } }
],
"startActionId": "check_vacation",
"actions": {
"check_vacation": {
"type": "var_condtion",
"params": { "key": "vacation_mode", "checkOperation": "equals", "valueToCheck": "on" },
"branches": { "true": "notify", "false": "mark_checked" }
},
"notify": {
"type": "send-email",
"params": {
"email": "office@example.com",
"subject": "Vacation mode is still on",
"message": "<p>{{ $_automationName }}: orders are on hold.</p>"
},
"branches": { "next": null }
},
"mark_checked": {
"type": "set_var",
"params": { "key": "last_check", "value": "workday" },
"branches": { "next": null }
}
}
}
| Field | Rules |
|---|---|
name, desc | name required for create; desc optional |
triggers | array of { type, params }, at least one; type is the full element type |
actions | object actionId → { type, params, branches, saveResultAs? }; actionId matches ^[a-zA-Z0-9_-]+$ and is yours to choose (check_paid, set_status) |
branches | output name → next actionId or null; names come from the element's outputs (no outputs: only next); a missing output ends the run |
startActionId | the first action; must exist |
saveResultAs | optional key under which the action's result is kept for later actions ({{ $key }} in send-email); without it only the last result is available as $last |
Every action must be reachable from startActionId. isEnabled is not part of the file: omm rejects a file that contains it.
Elements
omm automation elements lists what the company can use; omm automation element <type> prints the full definition: the params schema, outputs of an action, emits of a trigger and requires of an action. Never guess a type or a param name; read the definition.
Built-in elements of the core:
| Type | Kind | Params | Notes |
|---|---|---|---|
cron | trigger | pattern | cron expression, 5 fields or 6 with seconds; the event carries only core:pattern |
delay | action | value | seconds to wait, integer > 0; the run pauses, then continues on next |
set_var | action | key, value | sets a company automation variable (shared and persistent, not per run) |
var_condtion | action | key, checkOperation (equals, includes, not_equals), valueToCheck | spelled exactly like this; outputs true / false |
send-email | action | email, subject, message | message is HTML rendered as a mustache template, see below |
send-template-email | action | templateId, recipientEmail and/or recipientVar | a company message template; at least one recipient |
Module elements have the type <moduleCode>:<code>, for example core/orders:order_created. They need params.__instanceId: the id of the module instance that runs them (omm instances -m <moduleCode> --json, or the allowed values in the param's options.values). It is required even when the company has a single instance.
Event keys connect triggers and actions:
- A trigger declares in
emitsthe keys it delivers; an action declares inrequiresthe keys it needs. Both use full keys,core/orders:ORDER_ID. - Every action's
requiresmust be emitted by every trigger of the automation; with several triggers only the common keys count. The core rejects a graph that breaks this (REQUIRES_NOT_EMITTED). - At run time an action whose keys are missing is skipped: the run continues on
next, the step is marked as skipped and the company gets the problemAUTOMATION_ACTION_SKIPPED.
Params:
- A param with
options.valuesaccepts only the listedids. A param withoptions.module+options.methodtakes values from that module method (for example an order status id); fetch them withomm call <method> -m <module> --params '<options.params as JSON>' --json. The core does not check these values, so do not guess them. - Types:
string,number(a number or numeric text, maybe with aformatsuch aspositive_integer),boolean,datetime,array(ofof),template_var, andinfo(descriptive, give no value). - Variables are rendered only where the core supports them.
send-emailmessageis a mustache template:{{ $event.core/orders:ORDER_ID }},{{ $last }},{{ $<saveResultAs key> }},{{ $_automationName }}; triple braces and partials are forbidden;emailandsubjectare not rendered. Atemplate_varparam takes a variable pathmoduleCode:group.fieldwithout braces, such ascore/orders:customer.email. Anywhere else{{ }}reaches the action as plain text.
Build loop with omm
Add --json to every command, as everywhere in omm. All commands work on the working company (omm company use, or --company <id>).
- See what exists.
omm automation elements --json(filters:--kind trigger|action,--query <text>) andomm automation list --json; a similar existing automation is the best template (omm automation get <id> --json). - Read every element you use.
omm automation element <type> --json: params, required fields,outputs,emits/requires. - Write the graph file. Instance ids from
omm instances -m <moduleCode> --json, values of dynamic params from the module method. - Save a draft.
omm automation create -f graph.json --json(or-f -with the graph on stdin). It prints{ "automationId", "name", "isEnabled": false }. The core validates the whole graph; an invalid one is rejected with 422 and nothing is saved, see Validation errors. - Check it.
omm automation get <id> --jsonshows the saved graph and itsvalidation(element statuses,blocking). - Show the human the graph in plain words: trigger, conditions, what each branch does, which instance it acts on.
- Enable only on consent.
omm automation enable <id> --json. The core rejects it while an element is unavailable or an action param is invalid. - Watch the runs.
omm automation runs --automation <id> --json, thenomm automation run <runId> --jsonfor the steps. - Fix with update. Take
omm automation get <id> --json, edit it, removeisEnabledand send it back withomm automation update <id> -f file --json. Read-only fields (_id,validation,generatedName, …) are dropped byomm.triggersandactionsare replaced as a whole, so always send the full graph.updatenever changesisEnabled, but an update of an enabled automation takes effect at once: ask first, ordisableit before the change.
Other commands: omm automation disable <id>, omm automation duplicate <id> (a disabled copy, not validated) and omm automation delete <id> --yes (also removes its runs and problems; Human).
Validation errors
create and update send the graph with strict validation. A 422 arrives on stderr in --json mode as:
{"error":"Automation graph is invalid","status":422,"validation":{"errors":[{"path":"actions.wait.params.value","code":"INVALID_TYPE","message":"expected a number"}]}}
In text mode each error is a line path: message (CODE). Read path, fix that place in the file and send the whole graph again. INVALID_SHAPE is checked first; the other rules run only on a well-shaped graph, so new errors can appear after a fix.
| Code | Meaning |
|---|---|
INVALID_SHAPE | a graph field has the wrong type, for example actions as an array |
UNKNOWN_FIELD | a field outside the format (trigger: type, params; action: type, params, branches, saveResultAs), or an unknown field of a template variable |
NO_TRIGGER | triggers is empty |
START_ACTION_MISSING | startActionId does not point to an action |
INVALID_ACTION_ID | an actionId outside ^[a-zA-Z0-9_-]+$ |
UNKNOWN_BRANCH | a branch name the action does not declare in outputs |
BRANCH_TARGET_MISSING | a branch points to an action that does not exist |
CYCLE | a branch leads back to an earlier action |
UNREACHABLE_ACTION | an action cannot be reached from startActionId |
ELEMENT_UNKNOWN | the type does not exist in the company (made up, withdrawn, or an action used as a trigger) |
ELEMENT_UNAVAILABLE | the module is not installed, all its instances are disabled, or __instanceId points to a disabled instance |
UNKNOWN_PARAM | a param outside the element's schema, or a value given to an info field |
REQUIRED | a required param is missing |
INVALID_TYPE | a value of the wrong type |
INVALID_FORMAT | a number that breaks its format |
INVALID_OPTION | a value outside options.values |
REQUIRES_NOT_EMITTED | an action needs event keys that not every trigger emits |
INVALID_VAR_PATH | a template_var value is not moduleCode:group.field |
UNKNOWN_MODULE | the variable's module is not installed or declares no variables |
UNKNOWN_GROUP | the variable group does not exist |
LIST_AS_VALUE | a list field used as a single value |
TEMPLATE_NOT_FOUND | the message template does not exist |
KEYS_NOT_EMITTED | the trigger does not emit the keys the variables need |
RECIPIENT_REQUIRED | send-template-email without recipientEmail and recipientVar |
A rejected enable uses the lighter enabling rules (element availability, action param rules) and reports the blocking elements as triggers.0: <type> (<status>) plus invalid params.
Element statuses and audit
omm automation get <id> --json has a status for every trigger and action in validation (text mode lists only the blocking ones):
| Status | Meaning | Blocks? |
|---|---|---|
ok | built-in, or present in the version of an active instance | no |
version-unknown | the module is active but this version has not reported its elements yet (just deployed or starting) | no |
missing | the module version no longer has this element | yes |
instance-disabled | __instanceId points to a disabled, removed or foreign instance | yes |
module-not-installed | no enabled instance of the module: not installed, or every instance disabled | yes |
An automation with any blocking element cannot be enabled. The core also audits enabled automations at start and about 30 seconds after module changes (disable, uninstall, version update): a blocking one is disabled (lastUpdateBy: core:audit) and the company gets the problem AUTOMATION_ELEMENT_MISSING listing the elements and reasons. The core never enables it again by itself: fix the graph (another instance, another element) or re-enable the instance, then ask the human to enable the automation. Events from the disabled period are not replayed.
Runs
omm automation runs lists runs, newest first (filters --automation <id>, --status <status>, --event <value> for a value of any event param such as an order id, --limit <n>, max 50). omm automation run <runId> prints the header, the event params and the steps with OFFSET, ACTION ID, CODE, BRANCH and OUTCOME.
| Status | Meaning |
|---|---|
running | an action is executing; stuck for long usually means the core restarted mid-run (it is not resumed) |
done | the run reached the end of the graph; this does not mean every action did something |
errored | an action threw; later actions did not run; the reason is in errorMsg (<type>: <message>) |
aborted | on resume the automation was disabled or deleted, or the paused action was removed from the graph |
paused | waiting until resumeAt |
resuming | picked up after a pause, waiting in the company queue |
Step outcomes: ok; skipped, missing: <keys> (the event lacked the action's requires); paused until <time> (a delay, or a module action that paused); retry (attempts: N), paused until <time> (the action asked to run again later: repeated attempts update this one step); error: <message>. Actions the run never reached have no step.
- A run resumes along the current graph: a fix made while runs are paused applies to them. The action a run is paused on is not executed again after a plain pause (
delay), only after a retry; if that action was removed, the run ends asaborted. - Finished runs are deleted about 3 days after they started; paused runs live until at least 3 days after
resumeAt. No run for an older event proves nothing; read the module's logs and history instead. donewith no effect: compare the steps with the graph. Look for askippedstep or a condition that took a branch with no action.
Variables
Company automation variables are text values shared by all automations of the company, read by var_condtion and written by set_var. They persist between runs.
omm automation var list --json
omm automation var get vacation_mode --json
omm automation var set vacation_mode on --json
A new value applies at once to every enabled automation that reads the variable.
