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

FamilyExamples
Tablestable.row.inserted, table.row.updated, table.row.deleted
Emailemail.received
HTTP triggershttp_trigger.received
Runsrun.completed, run.failed, run.cancelled
Interactionsinteraction.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.

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.

Debug delivery

If an event-backed loop does not start:

  1. Check the provider's recent events panel in the app.
  2. Confirm the event type matches the trigger exactly or by wildcard.
  3. Confirm the trigger condition evaluates true for the event's public {event, meta} envelope.
  4. 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:

  1. Open the run Timeline and find wait.opened.
  2. Confirm the wait's event_type and source_id.
  3. Confirm the source event exists.
  4. Check for wait.timed_out.

Next