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
| Family | Examples |
|---|---|
| Tables | table.row.inserted, table.row.updated, table.row.deleted |
email.received | |
| HTTP triggers | http_trigger.received |
| Runs | run.completed, run.failed, run.cancelled |
| Interactions | interaction.created, interaction.resolved |
| Signals | signal.deploy_complete |
| Sessions | session.message.created |
| Artifacts | artifact.created |
| Integrations | github.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: failWhen 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.
- 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.
- Does the event name match your trigger? Exactly, or via a wildcard. A
near-miss like
github.issue.openedwhen the real name isgithub.issues.openedmatches nothing. - Does your condition come out true? For this specific event's
{event, meta}. - 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
- Open the run's Timeline and find
wait.opened. - Check what
event_typeandsource_idit's actually waiting for. - Check that a matching source event exists.
- Look for
wait.timed_out, which means the deadline passed before the event arrived.
Next
- Choose trigger kinds in triggers.
- Write wait steps in steps.
- Look up names in the event catalog.