Guides

Source events

A source event is Mobius's record that something happened: an email arrived, a ticket was created, a row changed, a job finished.

You're most likely here because a job didn't start when you expected it to. Skip to when a job doesn't start.

Here's the path an event takes. Something happens, Mobius records it as an event, and then checks two things: does any trigger want to start a job because of this, and is any job currently waiting for exactly this. A trigger match starts a new run. A wait match resumes a suspended one. Some events are internal to Mobius and never do either.

What your triggers and steps actually see is a tidy { event, meta } envelope, not whatever the provider originally sent. event is the thing that happened. meta is the routing detail around it.

Envelope

event:
  # kind-specific public payload
meta:
  id: sevt_01...
  event_type: table.row.inserted
  source_kind: table_row
  source_id: tbl_01...
  received_at: "2026-06-11T13:00:00Z"
  table_id: tbl_01...
  row_id: row_01...

Event triggers start runs with this data available as event.* and meta.* in templates and conditions. Wait steps evaluate condition and payload_mapping against { event, meta }.

Public families

FamilyExamples
Tablestable.row.inserted, table.row.updated, table.row.deleted
Emailemail.received
HTTP triggershttp_trigger.received
Runsrun.completed, run.failed, run.cancelled
Interactionsinteraction.created, interaction.resolved
Signalssignal.deploy_complete
Sessionssession.message.created
Artifactsartifact.created
Integrationsgithub.pull_request.opened, github.issues.opened, linear.issue.updated, jira.issue.created, gmail.message.received, slack.event

The full public and internal lists live in the event catalog.

Every terminal interaction emits interaction.resolved, even when it also wakes a run, agent tool, or HTTP subscriber. Filter event.consumer_kind == "none" for a standalone-only trigger.

Table row events

For table.row.* events, source_id is the table ID, so a trigger scoped with source_id: tbl_01... fires for every row in that table. Row columns live under event.data:

event:
  event: updated            # inserted | updated | deleted
  data:
    priority: urgent        # the row's columns
  version: 3
  updated_at: "2026-06-11T13:00:00Z"
meta:
  table_name: tickets
  table_id: tbl_01...
  row_id: row_01...
  # present when the write came from a running agent:
  agent_id: agt_01...
  run_id: run_01...
  loop_id: loop_01...

Reach columns with event.data.priority in conditions, and with dotted keys in wait match patterns (match: { data.priority: urgent }).

A loop's own table writes never re-trigger that loop: events carry the originating loop in meta.loop_id and the dispatcher suppresses self-fires. Saving a row with values identical to what is stored is a no-op. It does not bump the row version or emit an event.

Match an event trigger

Use kind: event when an event should start a new run:

triggers:
  - key: issue-opened
    name: GitHub issue opened
    kind: event
    enabled: true
    config:
      event_type: github.issues.opened
      source_id: github
      condition: event.repository.full_name == "acme/api"

The condition uses the public envelope. Keep it narrow enough that the loop only starts for the events it can handle.

Wait inside a run

Use wait_for_event when an existing run should pause until an event arrives:

steps:
  - id: wait-for-deploy
    name: Wait for deploy
    kind: wait_for_event
    config:
      event_type: signal.deploy_complete
      source_id: run_01...
      payload_mapping:
        commit: event.commit
    timeout:
      duration: 30m
      on_timeout: fail

When the event arrives, the run resumes and later steps can read ${{ steps.wait-for-deploy.output.commit }}.

Wildcards

Event names are dotted strings. Exact names and trailing subtree wildcards are valid:

github.pull_request.opened
github.pull_request.*
github.*

Wildcards match descendants only. github.pull_request.* matches github.pull_request.opened; it does not match github.pull_request.

When a job doesn't start

Work outward from the provider. The single most common mistake is editing the trigger over and over when the event never reached Mobius in the first place, and no amount of trigger editing fixes that.

  1. Did Mobius receive it at all? Open Library > Integrations, pick the provider, and look at recent events. Nothing there means the problem is on the provider's side: the app isn't installed, a webhook isn't configured, or the account is missing a permission.
  2. Does the event name match your trigger? Exactly, or via a wildcard. A near-miss like github.issue.opened when the real name is github.issues.opened matches nothing.
  3. Does your condition come out true? For this specific event's {event, meta}.
  4. Is the job paused, or is concurrency blocking it? A job that auto-paused after repeated failures refuses new starts until you resume it.

When the event arrived but no run started, open Trigger activity on the loop page. A condition that came out false is a normal non-match and isn't recorded. A condition that errored is recorded, with the missing field or the type mismatch, which usually tells you the answer immediately.

When a waiting run doesn't resume

  1. Open the run's Timeline and find wait.opened.
  2. Check what event_type and source_id it's actually waiting for.
  3. Check that a matching source event exists.
  4. Look for wait.timed_out, which means the deadline passed before the event arrived.

Next