# Automation

> How triggers, scripts and agents make an app react to ticket events, when to use each one, how conditions hold them back, and what stops loops.

Canonical HTML page: https://conclude.io/doc/apps/automation/
Last updated: 2026-11-02

---

An app can act on its own in three ways: with triggers, with scripts and with agents. Most apps only need triggers. This page is for builders who have a working app and now want it to react to what happens on a ticket.

**You don't need this to build an app..** An app with attributes, roles and texts is complete without automation. Come back when a ticket should notify someone or change a field by itself.

## Three ways to automate

![Four ticket events passing through a condition and fanning out to declarative actions, a script or an agent](https://conclude.io/images/doc/figures/f09-trigger-model.svg)

*Figure: Every automation starts from one of four events and passes the same optional conditions. Only what runs afterward differs.*

All three start from the same events: `onCreate`, `onComment`, `onEdit` and `onConclude`. `onCreate` and `onConclude` fire once per ticket. `onComment` and `onEdit` fire on every comment or attribute change. See the [lifecycle](https://conclude.io/doc/apps/lifecycle/#events).

| Style | What decides the outcome | Use it when |
|---|---|---|
| Trigger | Fixed actions written in YAML | The rule is always the same, such as notify, set a field or conclude |
| Script | Starlark code you write | The rule has branches or timing that YAML cannot express |
| Agent | A language model reading the ticket | The response needs judgment or free text |

An app can use any of them, or none. The [triggers reference](https://conclude.io/doc/reference/triggers/#actions) lists the actions and the dispatch order.

## Triggers

A trigger names an event, optional conditions and one or more actions. They all sit on the same YAML object.

```yaml
triggers:
  - onEdit:
      - attribute: severity
        ifMatch: critical
        alert: "#oncall"
  - onConclude:
      - email:
          to: ${initiator}
          subject: "Resolved: ${title}"
          body: ${conclusion}
```

A trigger can also sit on an attribute, or on one option of a select attribute. That is how a button or a status change acts. See [per-attribute triggers](https://conclude.io/doc/reference/triggers/#per-attribute-triggers). The [Attributes tab](https://conclude.io/doc/apps/editor/attributes/) has no control for these triggers. You edit them, and the top-level list, as YAML on the [Expert tab](https://conclude.io/doc/apps/editor/expert/). Triggers on the same event run in the order they appear in the YAML.

## Scripts

A script is Starlark code under `scripts:`. A trigger's `script:` action starts it. Its entry function `onTrigger` receives the ticket, a `conclude` object for side effects, and why it ran.

```yaml
scripts:
  - name: route
    source: |
      def onTrigger(activity, conclude, trigger):
          category = activity.get("category")
          if category == "billing":
              activity.set("routed_to", "finance-team")
          elif category == "outage":
              activity.set("severity", "high")
              conclude.comment("Routed to oncall.")

triggers:
  - onCreate:
      - script: route
```

**Scripting is turned on per organization.** Scripts run only when scripting is turned on for your organization. The Admin console has no toggle for it. While it is off, Conclude drops a script trigger and logs an error.

A script can read and set attributes, post and edit its own comments, and schedule itself to run again. The [scripting reference](https://conclude.io/doc/reference/scripting/#limits) lists the caps.

## Agents

An agent belongs to the organization, not to the app. The app names the agent in a trigger. The [Agents tab](https://conclude.io/doc/apps/editor/agents/) writes the same entries.

```yaml
triggers:
  - onCreate:
      - agent: triage
  - onComment:
      - agent: triage
```

The app sets no mode. The agent answers in the mode it has in the app's channel. That mode only affects `onComment`, because only a comment can address the agent. On the other events, the agent answers unless it is off. See [Modes](https://conclude.io/doc/agents/#modes) and [what the agent sees](https://conclude.io/doc/reference/agents/#what-the-agent-sees).

## Conditions

Three keys hold a trigger back, whatever its action:

| Key | Effect |
|---|---|
| `attribute` | Limits an `onEdit` trigger to this attribute. Without it, the trigger fires on every edit. |
| `ifMatch` | A regular expression the new value must match. On `onComment`, it matches the comment text. |
| `ifNotMatch` | A regular expression the value must not match. With `ifMatch`, both must hold. |

The full table is in the [conditions reference](https://conclude.io/doc/reference/triggers/#conditions).

## Loop protection

Scripts and agents only fire on events that no trigger caused. Anything a trigger changes carries a call-stack marker. The follow-up events it raises don't dispatch scripts or agents. A script that posts a comment therefore doesn't start itself again.

An agent never answers its own comments. Its comments on one ticket are also rate-limited and capped. Declarative actions such as `setAttribute`, `message` and `email` can still chain, but the cascade stops at a fixed depth.

A script that schedules itself again with `conclude.schedule` gets past this guard. Only the length of its chain limits it. The numbers are in the [triggers](https://conclude.io/doc/reference/triggers/#loop-protection), [scripting](https://conclude.io/doc/reference/scripting/#loop-protection) and [agents](https://conclude.io/doc/reference/agents/#loop-protection-and-rate-limits) references.

## Related pages

- [The activity lifecycle](https://conclude.io/doc/apps/lifecycle/) shows when each event fires
- [Example apps](https://conclude.io/doc/apps/patterns/) has worked examples
- The [YAML reference](https://conclude.io/doc/reference/yaml/) lists every key an app accepts

