Skip to main content
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.
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 history.

Trigger kinds

Pass one of these to --on. 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.

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

To check a trigger without starting a run, pass it a sample event:
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: 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.
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. --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.
--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.
A routine fires once per due time. routines run-now fires once per call, and --wait polls that run to completion.
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.
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.

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.
Genuinely repeated actions still run each time. Reopening the same issue twice is two events, because each reopen carries its own timestamp.