> ## 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` | The number of traces Mutagent received for this workspace (sent by [Claude Code hooks](/cli/commands/project-setup), over OTLP, or from cloud sandboxes) reaching `--threshold` within `--window`. Traces Helix reads from Langfuse or local files are not counted. |

`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 GitHub remote of the directory you run the command in, linked or not (a repository the app cannot reach is refused). 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`, your
directory's GitHub remote, 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 1. 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 webhook URL with its own secret. You create the webhook
source in the web app and choose its secret there (16 to 512 visible ASCII characters). Mutagent
stores the secret encrypted and never shows it again, so keep your own copy.

Sign each delivery: 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.

```bash theme={null}
WEBHOOK_URL="<your webhook URL>"
SECRET="<your webhook secret>"
BODY='{"hello":"world"}'
ID="$(uuidgen)"
SIG="$(printf 'v1:%s:%s' "$ID" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')"
curl -X POST "$WEBHOOK_URL" \
  -H "x-gateway-delivery-id: $ID" \
  -H "x-gateway-signature: sha256=$SIG" \
  -H 'content-type: application/json' \
  --data "$BODY"
```

### 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.