Skip to main content
View as Markdown

Reusable Actions

Define an action template once in app.actions and invoke it from any automation with a ref action, so a shared step has a single source of truth.

The same step usually appears in several automations: a chat alert, an audit-log write, a call to an internal API. Copying it into each one means every future change has to be made in every copy.

actions is a library of named templates. Define the step once, with $variable placeholders where the inputs differ:

app.yaml
actions:
  - name: notify-slack
    action:
      type: http
      operator: post
      props:
        url: $env.SLACK_WEBHOOK_URL
        body: { text: '$message' }

Then invoke it from any automation, passing the values:

app.yaml
automations:
  - name: order-alert
    trigger: { type: record, table: orders, events: [create] }
    actions:
      - name: alert
        $ref: notify-slack
        $vars: { message: 'New order recorded.' }

Template properties

Path Kind Values Default Description
name string Unique action template name for $ref referencing (kebab-case)
action object (truncated) An action configuration embedded in a reusable template. Same shape as an automation action minus the step name (supplied at the $ref call site).
variables object Variable declarations with default values. Override via $vars when referenced.
aiAccess boolean | object (truncated) Per-entity declaration of MCP/AI exposure intent. Boolean shorthand (true = enabled with defaults) or rich config object. Schema author declares; operator activates via MCP_ENABLED env var.

name is a kebab-case identifier, unique across the app, and is what $ref resolves. action is the step itself — any type and operator pair an automation step accepts.

The template has no step name

Inside an automation, name is how a later step references an output, so it belongs to the call site rather than to the template. That is why action is a nested block: the template declares what to do, and the invocation declares what to call it.

Invoking a template

Path Kind Values Default Description
name string Step name for referencing outputs (e.g., "fetchUser"). Must be alphanumeric + underscore.
label string Human-readable label for this action step
continueOnError boolean false Continue workflow even if this action fails (default: false)
timeout number Per-action timeout in ms (1000-900000). Terminates the action when exceeded.
type enum ref ref Constant value 'ref' for type discrimination in discriminated unions
$ref string Name of the action template to invoke (must match a template in app.actions[])
$vars object Variables to substitute in the referenced template (overrides template defaults)

retry

Path Kind Values Default Description
retry object Automatic retry behavior for failed executions with fixed or exponential backoff
retry.maxAttempts number Maximum retry attempts (1-10)
retry.delayMs number Base delay between retries in milliseconds (100-60000, default: 1000)
retry.strategy enum fixed, exponential Retry strategy: fixed delay or exponential backoff (default: fixed)

Writing type: ref is optional — a $ref is unambiguous on its own, so { name: alert, $ref: notify-slack } and the explicit form are the same action. The call site's name is what run history records, not the template's.

Variables

A placeholder is a $ followed by an alphanumeric name: $message, $channel.

Rule Detail
Where they resolve In any string value, at any depth inside the template's action block
Precedence $vars at the call site wins over the template's own variables defaults
Unknown names Left untouched, so $env.SLACK_WEBHOOK_URL and {{trigger.data.id}} survive substitution

That last rule is what lets a template mix all three reference families. Declare the defaults you want, and leave environment references and automation template variables to be resolved later by their own engines.

app.yaml
actions:
  - name: notify-team
    variables:
      channel: general
      message: Something happened.
    action:
      type: http
      operator: post
      props:
        url: $env.SLACK_WEBHOOK_URL
        body: { channel: '$channel', text: '$message' }

automations:
  - name: order-alert
    trigger: { type: record, table: orders, events: [create] }
    actions:
      - { name: alert, $ref: notify-team, $vars: { message: 'New order.' } }
      - { name: escalate, $ref: notify-team, $vars: { channel: ops, message: 'Check stock.' } }

Exposing a template to AI

A template carrying an aiAccess block becomes a directly invocable MCP tool named after the app and the template, with its variables as the tool's parameters — so the declaration that makes a step reusable also makes it callable.

app.yaml
actions:
  - name: archive-order
    variables:
      reference: ''
    action:
      type: record
      operator: update
      props:
        table: orders
        filter:
          conditions: [{ field: reference, operator: equals, value: '$reference' }]
        data: { archived: true }
    aiAccess:
      description: Archive one order by its reference.
      annotations: { readOnly: false, destructive: false, idempotent: true }

Whether the server actually mounts those tools stays an operator decision, governed by an environment variable rather than by the configuration.

What is refused

Rule Detail
Unique names A duplicate would make a $ref ambiguous, so it is refused when the configuration is decoded
ref is reserved The name collides with the method exposed to code-action bodies, and is refused by name
References resolve A $ref naming a template that does not exist is caught at startup, before anything can run

When not to reach for a template

A step used by exactly one automation belongs inline in that automation. A template referenced from a single call site adds indirection without removing any duplication, and the indirection is paid on every later reading of both files.

Last updated September 24, 2026

This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.

Built with Sovrium