Operations
Source events
Source events are Mobius's durable reaction inbox. They record something that happened outside the current run, or somewhere else inside Mobius.
Mobius stores each delivered event as a source-event row, then runs descriptor
matching. A matching event trigger starts a new run. A matching
wait_for_event step resumes an existing run. Internal-only events stay inside
the runtime.
The public contract is the normalized { event, meta } envelope, not the raw
database row.
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.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.
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.
Debug delivery
If an event-backed loop does not start:
- Check the provider's recent events panel in the app.
- Confirm the event type matches the trigger exactly or by wildcard.
- Confirm the trigger condition evaluates true for the event's public
{event, meta}envelope. - Check whether the loop is paused or blocked by concurrency.
Open Trigger activity on the loop page when the source event matched but no
run started. A condition that evaluates to false is an expected non-match and
is not recorded. A condition evaluation error is recorded with the missing
field, type mismatch, or other failure so you can correct the trigger.
If a waiting run does not resume:
- Open the run Timeline and find
wait.opened. - Confirm the wait's
event_typeandsource_id. - Confirm the source event exists.
- Check for
wait.timed_out.
Next
- Choose trigger kinds in triggers.
- Write wait steps in steps.
- Look up names in the event catalog.