> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mutagent.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Triggers and routines

> The event kinds a trigger can listen for, what each one fires on, and how routines run an instruction on a schedule.

<Note>
  **Early access.** GitHub and Slack runs start in a cloud sandbox operated by Mutagent, and cloud
  sandboxes are not open to every account yet: we are letting accounts in gradually while we test.
  There is nothing to host, and nobody has to be at a terminal.
</Note>

A **trigger** listens for an event and runs an instruction. A **routine** runs an instruction on a
schedule. Both belong to a workspace, both usually work on a repository, and both appear in the same
[runs](/cli/commands/gateway#mutagent-gateway-runs) history.

## Trigger kinds

Pass one of these to `--on`.

| Kind | Fires on |
| - | - |
| `github.issue.opened` | An issue opened on the repository. No mention needed. |
| `slack.mention` | An `@` mention of the app in the Slack channels you name with `--channel <id...>`. `--channel` is required. |
| `webhook` | A signed HTTP delivery to the workspace's webhook route. See [Webhook deliveries](#webhook-deliveries). |
| `emit:<kind>` | A `mutagent gateway emit <kind>` call naming that kind. |
| `github.ci-failed` | A check or workflow run concluding in failure on the trigger's repository. |
| `trace.threshold` | A trace count reaching `--threshold` within `--window` for the workspace's traces. |

`trace.threshold` requires both `--threshold` (1 to 1,000,000) and `--window` (a duration such as
`60m`, `24h` or `7d`). Omitting either is refused before the request is sent. Narrow it further with
`--service <name>` or `--session <id>`, or count only the traces of some agents, skills or services
with `--subject`, for example `--subject agent:support-bot`.

```bash theme={null}
mutagent gateway triggers create \
  --on trace.threshold --threshold 100 --window 60m \
  --repo owner/name \
  --instruction "Investigate the volume spike"
```

### Built-in mention triggers

A connected workspace answers mentions of the app without any trigger being created:

* On GitHub, a mention of the app in an issue or an issue comment, on every repository the app can
  reach. Mentions on pull requests do not start a run.
* In Slack, a mention of the app in a channel it is in. The run's repository is picked from the
  message, the thread or the channel. See
  [Which repository a mention runs on](/integrations/slack#which-repository-a-mention-runs-on).

`mutagent gateway triggers` lists these built-in triggers alongside yours. A built-in trigger is
always on and cannot be changed or replaced:

* `triggers create --on github.mention` is refused with `trigger_kind_builtin_only`. Use another kind,
  such as `github.issue.opened`.
* A `slack.mention` trigger of your own answers only in the channels you give it with `--channel`.
  The built-in keeps answering everywhere else. A channel that another enabled trigger already holds
  is refused with `trigger_conflict`, and a `slack.mention` trigger with no channel is refused too.

### Creating a trigger

```bash theme={null}
mutagent gateway triggers create --on github.issue.opened --repo owner/name --instruction "…"
```

| Flag | What it does |
| - | - |
| `--on <kind>` | Required. One of the kinds above, or `emit:<kind>`. |
| `--repo <owner/name>` | The repository the run checks out. See [Choosing the repository](#choosing-the-repository). |
| `--no-repo` | No repository: the run starts without a checkout. `webhook`, `emit:<kind>` and `trace.threshold` only. |
| `--instruction <text>` | The instruction Helix runs. |
| `--instruction-file <path>` | Read the instruction from a file instead. |
| `--name <name>` | A name for the trigger. Defaults to one derived from the kind and repository. |
| `--key <key>` | Idempotency key. A retry with the same key returns the same trigger instead of creating a second. |
| `--channel <id...>` | Required with `slack.mention`. The Slack channel IDs this trigger answers in. |
| `--threshold <n>` | `trace.threshold` only. Fire at or above this many traces. |
| `--window <duration>` | `trace.threshold` only. The counting window. |
| `--service <name>` | `trace.threshold` only. Limit to this service name. |
| `--session <id>` | `trace.threshold` only. Limit to this session id. |
| `--subject <kind:names>` | `trace.threshold` only. Count only the traces of these agents, skills or services. |
| `--stage <stage>` | `evaluate`, `diagnose` or `optimize`: labels which Helix step the trigger serves. It does not change the instruction. |
| `--env <name>` | A workspace [Environment](/cli/commands/env) every run loads, with its variables and secrets. |

To check a trigger without starting a run, pass it a sample event:

```bash theme={null}
mutagent gateway triggers test <id> --event @sample.json
```

It reports whether the sample would match, and why not if it would not. It has no side effects.

`triggers update <id>` changes the instruction, name, channels, threshold, stage or Environment of a
trigger you created. The kind and the repository cannot be changed: create a new trigger for those,
then delete this one.

### Choosing the repository

There is no default repository. A trigger or routine chooses its repository when you create it:

| You pass | The run works on |
| - | - |
| `--repo owner/name` | That repository. Any repository the Mutagent GitHub App can reach works; one not linked yet is linked to the workspace when you create the trigger or routine. |
| Neither flag | The git remote of the directory you run the command in, when it is a linked repository. The CLI prints which one it chose. |
| `--no-repo` | No repository: the run starts without any code. For routines and `webhook`, `emit:<kind>` and `trace.threshold` triggers. |

`github.issue.opened`, `github.ci-failed` and `slack.mention` triggers always run on a repository.
When no repository is chosen, the command is refused with `repository_required`, and the error lists
the linked repositories you can name. A repository the app cannot reach is refused, with the
instruction to give the app access to it on GitHub first (`mutagent gateway repos grant-more` prints
the page).

## Routines

A routine is a schedule, a timezone, a repository and an instruction.

```bash theme={null}
mutagent gateway routines create \
  --every "weekdays 09:00" --tz Europe/Berlin \
  --repo owner/name \
  --instruction "Check main's failing CI and open an issue for each new failure"
```

A routine chooses its repository the same way a trigger does: `--repo owner/name`, the linked
repository your directory's git remote names, or `--no-repo` for a routine that needs no code. See
[Choosing the repository](#choosing-the-repository). `--env <name>` loads a workspace Environment
into every run, and `--key <key>` makes a retry return the same routine instead of creating a second.

Give exactly one of `--every` or `--cron`. `--every` takes a plain-language schedule; `--cron` takes a
cron expression. Either way, the output shows the normalized cron, the timezone, and the next three
fire times.

```bash theme={null}
mutagent gateway routines create --cron "0 9 * * 1-5" --tz America/New_York --repo owner/name --instruction "…"
```

<Warning>
  `--tz` is required, and it must be an IANA timezone such as `Europe/Berlin`. Omitting it is refused
  locally, before any request is sent, with `timezone_required` and exit code 3. There is no default:
  the cloud has no "local time", so daylight-saving behaviour has to be stated rather than guessed.
</Warning>

A routine fires once per due time. `routines run-now` fires once per call, and `--wait` polls that run
to completion.

```bash theme={null}
mutagent gateway routines run-now <id> --wait
```

`routines update <id>` changes the schedule, timezone, instruction, name, stage or Environment. The
repository cannot be changed.

`routines pause` stops a routine firing without deleting it; `routines resume` starts it again. A
routine whose repository you unlink is paused visibly, with the reason shown in `routines show`.

## Webhook deliveries

A `webhook` trigger listens on a per-workspace route with its own secret. Compute an HMAC-SHA256 with
that secret over `v1:<delivery-id>:` followed by the raw body, and send the result as
`x-gateway-signature: sha256=<hex>`. `<delivery-id>` is the value of your `x-gateway-delivery-id`
header, or empty (`v1::`) when you send no such header. A delivery id the signature was not computed
over is refused.

### Two identical deliveries are one event

A delivery is recognised by **what it contains**, not by when it arrived. Two deliveries carrying
identical bytes and no `x-gateway-delivery-id` header are **one event**, however far apart they
arrive — hours or days later makes no difference. Nothing in the identity comes from a clock.

To have a second occurrence start a second run, say so in the request. There are two ways:

* Send `x-gateway-delivery-id` with a fresh value. A new id is a new event. The value may be up to 256
  characters of letters, digits, `-` and `_`. This is how a caller states "this is a new occurrence,
  not a retry".
* Put something distinguishing in the body, such as a timestamp or a sequence number.

<Note>
  The reason is cost. A trigger starts a real agent run in a sandbox. When a request carries neither a
  delivery id nor anything distinguishing in its body, nothing in it tells a retry from a fresh
  occurrence — so it is treated as a retry.
</Note>

### Pressing Redeliver does not start a second run

GitHub mints a new delivery id every time you press **Redeliver** in the App's *Advanced → Recent
Deliveries* page. That new id does not make it a new event: the gateway recognises GitHub deliveries
by their content, which a redelivery replays byte for byte, so the redelivered event is collapsed into
the first one and no second run starts.

This is deliberate, and for the same reason as above — a redelivery of the same comment would otherwise
start a second paid run in a second sandbox.

<Tip>
  Genuinely repeated actions still run each time. Reopening the same issue twice is two events, because
  each reopen carries its own timestamp.
</Tip>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.