DocsGuides

Source events

How something that happens in another system becomes a record in Mobius, and how an agent waits for one.

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 four things that can be:

  • A routine whose trigger names that event. It runs once for the event, with the event in hand. See scheduled and event routines.
  • An agent turn that called the mobius_event_wait tool and parked until a matching event arrives.
  • A conversation that asked a question with mobius.interaction.open. When the question is answered, dismissed, or expires, its interaction.resolved event comes back to that conversation, whether or not a turn is waiting.
  • 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.

Messages from chat apps are the exception to "available to read". Who can see the chat decides who else gets its messages; see Messages from chat apps.

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 }. A routine's event trigger evaluates its condition against the same envelope, and hands the same envelope to the agent when it runs.

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 reaches the conversation that asked it or an HTTP subscriber. Check event.status before acting on one: cancellation and expiry resolve an interaction too, and neither is approval.

Messages from chat apps

A message to the assistant in Slack, Microsoft Teams, or Telegram is also an event, such as slack.event or microsoft_teams.message.received. What you say to the assistant in a chat stays in that chat, so where the event goes depends on who can see the chat:

Where the message was sentWaits and session subscriptionsRoutinesBody in the integration events list
A public Slack channelYesYesYes
A private shared chat: a private channel, a group DM or group chat, any Teams channel, any Telegram groupNoYes, and the run stays in that chatNo
A DM with the assistantNoNoNo
A chat with people from outside your organizationNoNoNo

A message from a private shared chat starts only a routine whose condition names that chat, such as meta.conversation_id == "C0123ABCD". Every chat message carries its chat's ID in meta.conversation_id. The run belongs to that chat. It runs in the chat's own conversation, uses only company accounts, and posts its result only in the chat and thread that started it. It can read, remember things for the chat, and save files in the chat, and nothing else. It doesn't appear in the routine's runs, threads, or counts for anyone, and it sends no notification. Only the reply in the chat shows that it ran.

Every other event from these chat apps follows the same table, by the chat it came from: Slack button presses, slash commands, edits, deletions, joins, reactions, and the assistant's own replies; Telegram edits and button presses; Teams installs and removals. An event that names no chat, such as Slack's app_uninstalled, reaches nothing. Buttons that Mobius itself posts never become events. See Privacy in chat apps.

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 a worker open or using its active-work time limit.

The tool takes an event_type and, optionally, an integration_id, 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"
}

Provider events require an eligible connection grant for the assistant, the session's audience, and its execution mode. Use integration_id to choose an exact connection. Mobius revalidates that connection, grant, and execution mode before delivering the matching event. Revocation cancels the wait without switching accounts.

Keep the filter narrow enough that the turn only wakes for events it can act on. A condition that can never be true is rejected. An event_type of * requires both an explicit integration_id and a narrowing filter (source_id, match, or condition). For internal events, name the prefix, such as signal.*.

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.

Wait for the answer to a question

An assistant that asked a person something with mobius.interaction.open is not held up by asking; the outcome reaches its conversation on its own. When the assistant must not continue without the answer, it waits on interaction.resolved with source_id set to the interaction ID. It can only wait on questions it asked in the same conversation, the source_id is required, and the event name must be exact rather than a wildcard. Such a wait can last up to seven days rather than 24 hours, because the question stays answerable that long. A message from a person in that conversation interrupts the wait without closing the question, and the outcome still arrives once, whether the wait or the conversation receives it.

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.

Start a routine when an event arrives

Waiting inside a turn suits work that is already underway: the agent opened a pull request and wants to know when it merges. When the event is the start of the work, use a routine with an event trigger instead. It costs nothing while nothing arrives, it survives restarts because there is nothing parked, and each accepted event gets its own run conversation on the routine's ledger.

The trigger takes the same event_type grammar as a wait, exact or wildcard, and an optional condition over { event, meta }:

event:
  event_type: github.issues.opened
  condition: event.repository.full_name == "acme/api"

Only integration events can start a routine. A chat message starts one only from a public Slack channel or a private shared chat, and a run from a private shared chat stays in that chat; see Messages from chat apps. A condition that comes out false records nothing; one that cannot be evaluated records a failed run with the reason, so a typo in a field name is visible rather than silent.

The watched account is not permission to act through that account. An event routine saves exact source bindings for what it watches and separate exact action bindings for tools it may call. If either grant becomes unavailable, Mobius pauses the routine for repair rather than choosing another account.

When an event never arrives

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

  1. Did Mobius receive it at all? Open Build > 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, integration_id, 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.

When an event routine doesn't run

  1. Open the routine's Runs tab. A failed run with trigger_condition_error means the event arrived and the condition could not be evaluated against it; fix the field name.
  2. No run at all means either the event never reached Mobius (check the provider's recent events, as above), the event name does not match the trigger, or the condition came out false. A rejected event records nothing. A chat message from a DM, or from a chat with people outside your organization, never starts a routine. A message from a private shared chat starts only a routine whose condition names that chat, and that run is not listed here; look for its reply in the chat.
  3. A run marked skipped with stale waited more than six hours behind earlier runs of the same routine. Event routines run one at a time.

Next