Guides

Source events

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

Recording is the whole of what Mobius does with an event on its own. An event does not start work by itself. Something has to be waiting for it, and there are two things that can be:

  • An agent turn that called the mobius_event_wait tool and parked until a matching event arrives.
  • A messaging binding, which turns an inbound provider message into a message for the agent that owns that channel.

Everything else that arrives is recorded and left there, available to read.

What a waiting agent sees 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...

A wait evaluates its match, condition, and payload_mapping against { event, meta }.

Public families

FamilyExamples
Tablestable.row.inserted, table.row.updated, table.row.deleted
Emailemail.received
Agent memorymemory.entry.created, memory.entry.updated, memory.entry.deleted
Interactionsinteraction.created, interaction.resolved
Sessionssession.message.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 an agent tool or an HTTP subscriber. Check event.status before acting on one: cancellation and expiry resolve an interaction too, and neither is approval.

Table row events

For table.row.* events, source_id is the table ID, so a wait scoped with source_id: tbl_01... can match any 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...

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

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.

Wait for an event during a turn

An agent waits by calling mobius_event_wait. The turn suspends, and it resumes when a matching event arrives. Waits survive a restart, so a turn can sit parked for hours without holding anything open.

The tool takes an event_type and, optionally, a source_id, exact-match match filters, an expr condition, a payload_mapping, and a timeout:

{
  "event_type": "github.pull_request.opened",
  "condition": "event.repository.full_name == \"acme/api\"",
  "payload_mapping": { "pr_url": "${event.pull_request.html_url}" },
  "timeout": "6h"
}

Keep the filter narrow enough that the turn only wakes for events it can actually act on. Two shapes are rejected outright, because neither can end well: a condition that can never be true would park the turn until the 24-hour backstop, and an event_type of * with no other filter would wake on every event in the organization.

timeout is a duration string and is capped at 24 hours. When it passes with no match, the tool returns an error and the agent decides what to do next.

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 an event never arrives

Work outward from the provider. The most common mistake is editing the wait over and over when the event never reached Mobius in the first place, and no amount of editing the wait 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? Exactly, or via a wildcard. A near-miss like github.issue.opened when the real name is github.issues.opened matches nothing.
  3. Does the condition come out true? For this specific event's { event, meta }.

When a waiting turn doesn't resume

  1. Open the session and find the parked mobius_event_wait tool call.
  2. Check what event_type and source_id it is actually waiting for.
  3. Check that a matching source event exists.
  4. Check whether the wait's timeout already passed. A timed-out wait returns an error to the agent rather than resuming.

Next