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_waittool 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, itsinteraction.resolvedevent 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
| Family | Examples |
|---|---|
| Tables | table.row.inserted, table.row.updated, table.row.deleted |
email.received | |
| Agent memory | memory.entry.created, memory.entry.updated, memory.entry.deleted |
| Interactions | interaction.created, interaction.resolved |
| Sessions | session.message.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
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 sent | Waits and session subscriptions | Routines | Body in the integration events list |
|---|---|---|---|
| A public Slack channel | Yes | Yes | Yes |
| A private shared chat: a private channel, a group DM or group chat, any Teams channel, any Telegram group | No | Yes, and the run stays in that chat | No |
| A DM with the assistant | No | No | No |
| A chat with people from outside your organization | No | No | No |
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.
- 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.
- Does the event name match? Exactly, or via a wildcard. A near-miss like
github.issue.openedwhen the real name isgithub.issues.openedmatches nothing. - Does the condition come out true? For this specific event's
{ event, meta }.
When a waiting turn doesn't resume
- Open the session and find the parked
mobius_event_waittool call. - Check what
event_type,integration_id, andsource_idit is actually waiting for. - Check that a matching source event exists.
- Check whether the wait's
timeoutalready passed. A timed-out wait returns an error to the agent rather than resuming.
When an event routine doesn't run
- Open the routine's Runs tab. A
failedrun withtrigger_condition_errormeans the event arrived and the condition could not be evaluated against it; fix the field name. - 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.
- A run marked
skippedwithstalewaited more than six hours behind earlier runs of the same routine. Event routines run one at a time.
Next
- Look up names in the event catalog.
- See what a provider sends in integrations.
- Read how a turn suspends and resumes in agent sessions.