# Automations

> Automated workflows triggered by data changes

Automations are automated workflows that execute when data is created, updated, or deleted, or on a recurring cron schedule. They enable event-driven architecture by running a sequence of tool calls in response to data mutations or time-based schedules, without requiring manual intervention.

## How Automations Work

Automations can be activated in two ways:

- **Entity triggers**: fire when a data mutation occurs (create/update/delete)
- **Cron triggers**: fire on a recurring schedule using cron expressions

### Entity Trigger Flow

When a mutation occurs (from the dashboard, an agent tool call, or an API request), the automation engine checks for matching automations and schedules them for execution:

```
Data mutation (create/update/delete)
    │
    ▼
Automation engine scans for matching automations
    │
    ├─ Match on entityType
    ├─ Match on action (created/updated/deleted)
    └─ Match on condition (optional data filter)
    │
    ▼
Matched automations scheduled asynchronously
    │
    ▼
Actions execute in order (fail-fast)
    │
    ├─ Success → trigger.executed event emitted
    └─ Failure → trigger.failed event emitted (retry if configured)
```

### Execution Characteristics

| Property | Behavior |
|----------|----------|
| Timing | Asynchronous (scheduled after the originating mutation completes) |
| Actor | Runs as the **system actor** with full permissions |
| Error handling | **Fail-fast** (first action failure stops the chain) |
| Success event | Emits `trigger.executed` |
| Failure event | Emits `trigger.failed` |
| Sources | Fires from dashboard CRUD, agent tool calls, and API mutations |

## Immediate Automations

By default, automations execute as soon as they are scheduled (immediately after the originating mutation). The actions run in sequence:

```typescript
{
  name: "Notify on New Session",
  slug: "notify-on-session",
  on: {
    entityType: "session",
    action: "created",
    condition: { "data.status": "scheduled" },
  },
  actions: [
    {
      tool: "entity.get",
      args: { id: "{{trigger.data.teacherId}}" },
      as: "teacher",
    },
    {
      tool: "event.emit",
      args: {
        eventType: "session.notification",
        entityId: "{{trigger.entityId}}",
        payload: { teacherName: "{{steps.teacher.data.name}}" },
      },
    },
  ],
}
```

## Scheduled Automations

Automations can be delayed or scheduled for a specific time using the `schedule` field:

### Delay-Based

Execute after a fixed delay:

```typescript
schedule: {
  delay: 300000,
}
```

This runs the automation 5 minutes after the data mutation.

### Time-Based

Execute at a specific time derived from the data:

```typescript
schedule: {
  at: "{{trigger.data.startTime}}",
  offset: -3600000,
}
```

This schedules the automation for 1 hour before the session's `startTime`. The `at` field supports template expressions that resolve to an ISO timestamp or Unix timestamp.

### Cancel Previous

When a record is updated multiple times, `cancelPrevious` ensures only the latest scheduled run is kept:

```typescript
schedule: {
  at: "{{trigger.data.startTime}}",
  offset: -3600000,
  cancelPrevious: true,
}
```

If a session's start time is changed, the old reminder is cancelled and a new one is scheduled.

## Cron Triggers

Cron triggers fire on a recurring schedule instead of entity events. Use the `on.schedule` field with a standard 5-field cron expression:

```typescript
export default defineTrigger({
  name: "Weekly Report",
  slug: "weekly-report",
  on: {
    schedule: "0 9 * * 1",
    timezone: "America/New_York",
  },
  actions: [
    {
      tool: "agent.chat",
      args: {
        agent: "reporting",
        message: "Generate and send the weekly summary report",
      },
    },
  ],
})
```

### Cron Expression Format

```
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, 0=Sunday)
│ │ │ │ │
* * * * *
```

| Syntax | Meaning | Example |
|--------|---------|---------|
| `*` | Every value | `* * * * *` (every minute) |
| `5` | Specific value | `0 5 * * *` (5am daily) |
| `1-5` | Range | `0 9 * * 1-5` (9am weekdays) |
| `*/5` | Step | `*/5 * * * *` (every 5 minutes) |
| `1,3,5` | List | `0 0 1,15 * *` (1st and 15th of month) |

### Timezone

The `timezone` field accepts any IANA timezone identifier. If omitted, the schedule runs in UTC.

### Cron Execution

- Cron triggers are checked every minute by the platform
- They do not have entity context (`trigger.entityId`, `trigger.data`, etc. are not available)
- The `schedule` field (delay/at) is not applicable to cron triggers
- Successful executions emit `trigger.executed` events
- Failed executions emit `trigger.failed` events

## Automation Runs

Scheduled automations create records in the `triggerRuns` table for status tracking.

### Status Lifecycle

```
pending ──► running ──► completed
                │
                ▼
             failed ──► running (retry) ──► completed
                │
                ▼
              dead (retries exhausted)
```

| Status | Description |
|--------|-------------|
| `pending` | Scheduled but not yet executed |
| `running` | Currently executing actions |
| `completed` | All actions finished successfully |
| `failed` | An action failed (may be retried) |
| `dead` | Failed and exhausted all retry attempts |

### Automation Run Fields

| Field | Type | Description |
|-------|------|-------------|
| `triggerId` | ID | Reference to the automation definition |
| `triggerSlug` | string | Slug of the automation definition |
| `entityId` | string | Record that triggered the run |
| `status` | enum | Current lifecycle status |
| `data` | object | Record data at time of activation |
| `previousData` | object | Record data before the mutation (for updates) |
| `scheduledFor` | number | When the run is scheduled to execute |
| `startedAt` | number | When execution began |
| `completedAt` | number | When execution finished |
| `errorMessage` | string | Error message if failed |
| `attempts` | number | Current retry attempt count |
| `maxAttempts` | number | Maximum retry attempts configured |
| `backoffMs` | number | Base backoff delay in milliseconds |
| `result` | object | Execution result on completion |
| `environment` | enum | Scoped to development or production |

## Retry Configuration

Failed automations can be retried with backoff:

```typescript
retry: {
  maxAttempts: 3,
  backoffMs: 5000,
}
```

| Field | Type | Description |
|-------|------|-------------|
| `maxAttempts` | number | Maximum retry attempts (minimum 1) |
| `backoffMs` | number | Base delay in milliseconds between retries |

When an automation fails:

1. If `attempts < maxAttempts`, the run is rescheduled with exponential backoff: `backoffMs * 2^(attempts-1)`, capped at 1 hour
2. The status transitions back to `pending`
3. On the next attempt, it transitions to `running`
4. If all retries are exhausted, the status becomes `dead` and a `trigger.scheduled.dead` event is emitted

## Template Variable Resolution

Automation action arguments support template variables that are resolved at execution time.

### Automation Context

| Variable | Description |
|----------|-------------|
| `{{trigger.entityId}}` | ID of the record that activated the automation |
| `{{trigger.entityType}}` | Data type slug |
| `{{trigger.action}}` | The action: `"created"`, `"updated"`, or `"deleted"` |
| `{{trigger.data.X}}` | Field `X` from the record's current data |
| `{{trigger.previousData.X}}` | Field `X` from the record's data before an update |

### Step References

| Variable | Description |
|----------|-------------|
| `{{steps.NAME.X}}` | Field `X` from the result of a named step |

Steps are named using the `as` field on an automation action. Later actions can reference the result:

```typescript
actions: [
  {
    tool: "entity.get",
    args: { id: "{{trigger.data.guardianId}}" },
    as: "guardian",
  },
  {
    tool: "event.emit",
    args: {
      eventType: "notification",
      payload: { name: "{{steps.guardian.data.name}}" },
    },
  },
]
```

## Condition Matching

The `on.condition` field filters which mutations activate the automation. All condition fields must match for the automation to fire:

```typescript
on: {
  entityType: "session",
  action: "updated",
  condition: { "data.status": "completed" },
}
```

This automation only fires when a session record is updated and its `data.status` field equals `"completed"`.

Multiple conditions act as AND filters:

```typescript
condition: {
  "data.status": "scheduled",
  "data.subject": "Mathematics",
}
```

### Transition Conditions

For `updated` actions, conditions can also match against `previousData` — the record's data **before** the update. This enables transition-based automations that only fire when a field changes from one value to another:

```typescript
condition: {
  "data.status": "scheduled",
  "previousData.status": "pending_payment",
}
```

This automation fires only when a session transitions **from** `pending_payment` **to** `scheduled` — not when an already-scheduled session is updated (e.g., rescheduled).

#### Example: Separate Confirmation and Reschedule Automations

Fire on initial activation (payment confirmed):

```typescript
on: {
  entityType: "session",
  action: "updated",
  condition: {
    "data.status": "scheduled",
    "previousData.status": "pending_payment",
  },
}
```

Fire only on reschedule (already scheduled, time changed):

```typescript
on: {
  entityType: "session",
  action: "updated",
  condition: {
    "data.status": "scheduled",
    "previousData.status": "scheduled",
  },
}
```

#### Available Condition Paths

| Path prefix | Description | Available for |
|-------------|-------------|---------------|
| `data.*` | Current record data (after mutation) | `created`, `updated`, `deleted` |
| `previousData.*` | Record data before the mutation | `updated` only |

For `created` and `deleted` actions, `previousData` is undefined — any condition referencing `previousData.*` will not match.

### Trigger Cascading

Entity mutations inside automation actions **do not cascade by default** — they will not fire other automations. This prevents infinite loops and unexpected side effects.

To explicitly allow cascading, pass `cascade: true` in the tool args:

```typescript
{
  tool: "entity.create",
  args: {
    type: "notification",
    data: { message: "Session confirmed" },
    cascade: true,
  },
}
```

Without `cascade: true`, entity mutations from automation actions are silent — they modify data but do not activate other automations. This is the safe default for common patterns like writing data back to the triggering entity (e.g., saving a calendar event ID after creation).

`cascade` is supported on `entity.create`, `entity.update`, and `entity.delete` tool calls within automations
```

## Mutation Sources

Automations fire from all mutation sources in the platform:

| Source | Example |
|--------|---------|
| **Dashboard CRUD** | Admin creates a session via the UI |
| **Agent tool calls** | Agent uses `entity.create` to schedule a session |
| **API mutations** | External system calls the HTTP API |
| **Webhooks** | Payment provider confirms a payment via webhook |

This ensures that automated workflows execute regardless of how the mutation originated.

## Payment Automations

Payment state changes from webhooks and reconciliation fire automations. Use `entityType: "payment"` with `action: "updated"` and a `condition` to match specific payment states:

```typescript
export default defineTrigger({
  name: "Notify Payment Received",
  slug: "notify-payment-received",
  on: {
    entityType: "payment",
    action: "updated",
    condition: { "data.status": "paid" },
  },
  actions: [
    {
      tool: "whatsapp.send",
      args: {
        to: "{{trigger.data.customerPhone}}",
        text: "Payment of {{trigger.data.amount}} {{trigger.data.currency}} received.",
      },
    },
  ],
})
```

See [Flow Payments](/integrations/flow-payments) for the full list of payment events and more examples.

## Events

Automations emit events throughout their lifecycle:

| Event | When |
|-------|------|
| `trigger.executed` | Immediate automation completed successfully |
| `trigger.failed` | Immediate automation action failed |
| `trigger.scheduled.completed` | Scheduled automation run completed successfully |
| `trigger.scheduled.dead` | Scheduled automation run exhausted all retry attempts |

## Available Tools

Automations can execute any built-in tool and custom tools. Actions run as the **system actor** with full permissions.

### Core Tools

| Category | Tools |
|----------|-------|
| Data | `entity.create`, `entity.get`, `entity.query`, `entity.update`, `entity.delete` |
| Event | `event.emit`, `event.query` |
| Agent | `agent.chat` |
| Web | `web.search`, `web.fetch` |

### Integration Tools

Require an active integration configured in the dashboard.

| Integration | Tools |
|-------------|-------|
| Google Calendar | `calendar.list`, `calendar.create`, `calendar.update`, `calendar.delete`, `calendar.freeBusy` |
| WhatsApp | `whatsapp.send`, `whatsapp.sendTemplate`, `whatsapp.sendInteractive`, `whatsapp.sendMedia`, `whatsapp.listTemplates`, `whatsapp.getConversation`, `whatsapp.getStatus` |

### Custom Tools

Automations can execute any custom tool defined in the `tools/` directory. Custom tools are org-level resources stored in the `customTools` table — they do not need to be registered on any agent to be used in automations. The automation engine queries the `customTools` table directly by tool name and delegates execution to the tool executor service.

## Firing Triggers Manually

The `triggers fire` command lets you manually fire a trigger for testing without waiting for a real data mutation or cron schedule. The trigger executes its actions exactly as it would in a real activation.

```bash
struere triggers fire <slug>
```

### Options

| Flag | Description |
|------|-------------|
| `--env <environment>` | Environment: `development` or `production`. Default: `development` |
| `--entity <id>` | Entity ID to use as trigger context |
| `--data <json>` | JSON data payload to pass as trigger context |
| `--json` | Output the full JSON execution result |
| `--confirm` | Skip the production confirmation prompt |
| `--verbose` | Show detailed execution output including per-step timing |

### Examples

```bash
# Fire a trigger in development
struere triggers fire notify-on-session

# Fire with entity context
struere triggers fire notify-on-session --entity abc123

# Fire with custom data payload
struere triggers fire weekly-report --data '{"weekStart": "2026-04-13"}'

# Fire against production
struere triggers fire notify-on-session --env production --confirm

# Get full JSON result for debugging
struere triggers fire notify-on-session --entity abc123 --json --verbose
```

The trigger runs as the **system actor** with full permissions, matching the behavior of real trigger activations.

## Debugging Triggers

### CLI Commands

Use the `triggers` CLI command to inspect and debug automations:

```bash
struere triggers list

struere triggers logs [slug]

struere triggers log <slug>

struere triggers log <slug> --env production

struere triggers log <slug> --nth 2 --verbose

struere triggers log <event-id> --verbose
```

### Agent Step Transparency

When a trigger uses `agent.chat`, the execution log shows agent health signals:
- Number of LLM iterations
- Tool call summary (successes and errors)
- Warning when agent self-corrected after tool errors
- Warning when agent hit max iteration limit (10)

Use `--verbose` on `triggers log` or `triggers logs` to see the full tool call timeline within agent steps, including individual tool call durations and error details.

## Dashboard Management

The dashboard provides automation management at `/triggers`:

- View all configured automations
- See automation run history with status
- View scheduled (pending) runs
- Retry failed runs
- Cancel pending runs
