Skip to content

modernx-admin — AI automation builder: trigger params

The natural-language automation builder (POST /api/ai/build-automation, implemented in lib/automations/ai/) turns a merchant’s plain-English description into a structured automation graph via spec.ts (the model’s output contract) → compile.ts (compileSpecToGraph, deterministic) → validateAutomationGraph. As of 2026-08-03 the trigger half of that pipeline can carry params, matching how action/condition nodes already worked.

Action and condition nodes were always compiled as { ...def.defaultParams(), ...spec.params }, but the trigger node was hardcoded to params: {} regardless of its type’s defaultParams(). This was invisible in production because both triggers registered at the time (orderPlaced, orderStatusChanged) have empty param schemas. It becomes a real bug the moment any trigger requires non-empty params — e.g. auto.trigger.cartAbandoned ({ minutes: number, 15..10080 }, landing on the sibling epic modernx-admin-noi): an AI-drafted cart automation would compile with invalid trigger params and always fail validation until the merchant fixed it by hand in the canvas.

Two independent fixes landed together, because either alone is insufficient:

  1. compile.ts merges trigger defaults. compileSpecToGraph now builds the trigger node the same way as action/condition nodes: { ...def.defaultParams(), ...(spec.trigger.params ?? {}) }. An unregistered trigger type still compiles — defaults fall back to {} — so compileSpecToGraph still never throws.
  2. spec.ts’s trigger gained a params field. AiAutomationOutputSchema’s trigger is now z.object({ type: z.enum(TRIGGER_TYPES), params: z.record(z.string(), z.unknown()).optional() }).strict(), so the model can express trigger configuration the description states (e.g. “abandoned for 3 hours” → { minutes: 180 }). It’s optional, not required like action/condition params — most triggers are paramless, and this keeps every existing construction site (stubAutomation, fixtures, the route’s repair round) valid unchanged. Model-supplied params win over defaultParams() per key, not as an all-or-nothing replacement.

buildSystemPrompt() (lib/automations/ai/build.ts) documents each param-carrying trigger’s shape on its line (mirroring how condition/action shapes are already rendered) via a new TRIGGER_PARAM_SHAPES map, and instructs the model to set trigger params from the description instead of leaving them at their defaults.

TRIGGER_PARAM_SHAPES is seeded with an auto.trigger.cartAbandoned entry even though that trigger isn’t registered yet as of this writing — it ships on modernx-admin-noi. It’s harmless to seed early because prompt lines are rendered by iterating the live TRIGGER_TYPES from the registry, not this map, so the entry does nothing until the trigger itself is registered. This was deliberate: modernx-admin-noi independently edits the same trigger-line-rendering code and adds its own TRIGGER_PARAM_SHAPES map (with a comment saying trigger params can’t be set — no longer true after this epic). Whichever branch merges second resolves the lib/automations/ai/build.ts conflict by keeping this epic’s rendering logic plus noi’s cartAbandoned entry — both halves are wanted, and the fix here is written generically against NodeTypeDef.defaultParams() so it needs no further change once cartAbandoned (or any future param-carrying trigger) lands.

Why tests use a synthetic trigger, not cartAbandoned

Section titled “Why tests use a synthetic trigger, not cartAbandoned”

compile.test.ts builds a local new NodeRegistry() with a fake test.trigger.windowed type ({ minutes: int 15..10080 }, defaultParams: () => ({ minutes: 60 })) rather than asserting against the real cartAbandoned, so this epic’s tests don’t depend on modernx-admin-noi having merged first.

lib/automations/ai/ only — no REST contract change, no Magento-side change, no UI change. The canvas already renders trigger config panels from node params like any other node, and the route already compiles → validates → offers the model one repair round for an out-of-range value (e.g. a 5-minute window below cartAbandoned’s 15-minute floor).

See also Automations engine entity model for the runtime side these compiled graphs execute against, and Scan-trigger infrastructure for the periodic-scan mechanism cartAbandoned itself will run on once registered.

Source: modernx-admin PR #405.