DocsDevelop

Event catalog

The durable source events Mobius records, and the live frames a session stream sends.

Mobius has two event planes.

Source events are durable organization events. Mobius records one whenever something notable happens in the organization or in a connected provider.

Session-stream frames are the live and durable records of a conversation. Chat UIs and embedded clients read that stream. If you are building an interface, this is the plane you want; skip to session-stream frames.

Source event envelope

Source events use one normalized envelope:

{
  "event_type": "table.row.inserted",
  "source_kind": "table_row",
  "source_id": "tbl_01...",
  "event": {
    "table_id": "tbl_01...",
    "row_id": "row_01..."
  },
  "meta": {
    "event_type": "table.row.inserted",
    "table_id": "tbl_01...",
    "row_id": "row_01..."
  }
}

The normalized payload is at event.*. Routing fields are at meta.*.

The Meta fields column below lists fields copied to meta.* for routing. It is not the complete event.* payload. Read the live catalog schema when you need fields specific to one event.

Public source events

EventSource kindRecorded whenMeta fields
email.receivedemailEmail arrives at a Mobius agent address.agent_id
interaction.createdinteractionA Mobius interaction opens.interaction_id, kind, target_user_ids
interaction.resolvedinteractionAn interaction completes, is cancelled, or expires.interaction_id, kind, status, consumer_kind, responder_id, resolving_response_id
memory.entry.createdagent_memoryAn agent memory entry is created.agent_id, user_id, memory_entry_id, memory_key, version
memory.entry.updatedagent_memoryAn agent memory entry is updated.agent_id, user_id, memory_entry_id, memory_key, version
memory.entry.deletedagent_memoryAn agent memory entry is deleted.agent_id, user_id, memory_entry_id, memory_key, version
session.message.createdsessionA new message is posted in an agent session.session_id, agent_id, message_id, role
table.row.insertedtable_rowA table row is inserted.table_name, table_id, row_id
table.row.updatedtable_rowA table row is updated.table_name, table_id, row_id
table.row.deletedtable_rowA table row is deleted.table_name, table_id, row_id

interaction.resolved fires for every terminal interaction, including ones bound to 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. Filter event.consumer_kind == "none" to handle only standalone requests.

Integration source events

Integration events are provider-scoped and organization-aware. The event catalog in the app and API is the source of truth for which provider events are active in your organization.

Provider events follow this pattern:

<provider>.<resource>.<verb>

Examples:

github.pull_request.opened
github.pull_request.closed
linear.issue.created
slack.event
gmail.message.received
jira.issue.updated

An event-triggered routine stores both the event's source connection and any accounts its actions may use. Matching the event does not authorize a provider action, and a revoked source binding pauses the routine for repair. See Source events for the setup and failure contract.

Internal source events

These event types exist in the source_events table for runtime processing, but they are internal. They derive no public envelope.

EventWhy it exists
interaction.http_subscriber.dispatchDispatches an interaction callback to an HTTP subscriber.

Session-stream frames

The v2 transcript stream (GET /sessions/{session_id}/transcript/stream) is the canonical protocol for embedded chat. It bootstraps authoritative messages, turns, and pending human interactions, then tails state changes as idempotent upserts. Treat the SSE id: and resume_cursor as opaque watermarks. Reconnect with ?cursor=... or Last-Event-ID; never parse or increment a cursor yourself.

FrameFold into statePayload to expect
message.upsertReplace the message by id.The complete transcript message and its content blocks.
message.blockReplace one content block by message_id and content_index.A complete text, thinking, tool-use, or tool-result block.
message.block.patchMerge fields into one content block.Tool status, free-form progress, or resolved_action.
message.deltaAppend live text or thinking to one block.text or thinking; keep the two buffers separate.
turn.upsertReplace the turn by id.Turn phase, errors, usage, and optional wait.
interaction.upsertReplace the interaction by id.The full interaction record. pending, in_review, completed, expired, and cancelled states are pushed on the same session stream.
stream.readyMark bootstrap/replay complete.session_id and the current resume_cursor. Derive live phase only after this frame.
stream.endApply the close policy.idle means the session settled; rotate means reconnect immediately with the same cursor.

The JSON snapshot at GET /sessions/{session_id}/transcript returns the same state model: messages, turns, pending interactions, and a resume cursor. Fold all snapshot pages before attaching the stream. A final snapshot removes stale pending interactions that are no longer present; terminal interaction upserts may remain in local history.

message.delta is a live preview. message.upsert, message.block, turn.upsert, and interaction.upsert are durable state you can rely on. The turn.* pulses and tool telemetry on the older v1 stream are live-only.

When an assistant asks a question with mobius.interaction.open, the turn does not wait for it. An interaction.upsert carries the question set to render and the answer contract to validate against. Respond with POST /interactions/{interaction_id}/respond, or dismiss the question with POST /interactions/{interaction_id}/cancel. Continue folding the stream: a terminal interaction upsert clears the prompt. The outcome then arrives as a user-role message.upsert whose metadata.event_type is interaction.resolved. Render its event_projection (title is Request answered, Request dismissed, or Request expired, with an optional summary and excerpt), not its text, which is an event record meant for the model.

Tool calls and results remain ordinary transcript content blocks, paired by tool call ID. There is no separate presentation contract for tools or custom actions: render the tool-use input and tool-result output, shaped by the action's output_schema.

The older GET /sessions/{session_id}/stream endpoint remains available for existing v1 consumers. It uses numeric after_sequence cursors and mixes durable message rows with best-effort turn.*, preview, generation, and tool telemetry. New embedded-chat integrations should use the v2 transcript stream; v1 does not carry interaction.upsert frames.

Follow a session turn

  1. Invoke the agent or start the session turn, then store the returned opaque resume_cursor before acknowledging upstream work.
  2. Fetch GET /sessions/{session_id}/transcript from that cursor, following next_page_token until has_more is false.
  3. Fold the snapshot, then open GET /sessions/{session_id}/transcript/stream?cursor=....
  4. Fold every known frame by its record or block key. Ignore unknown frame types so additive protocol changes remain compatible.
  5. Persist each delivered SSE id: as the new opaque cursor.
  6. On a disconnect or stream.end {reason:"rotate"}, reconnect with the same cursor. On stream.end {reason:"idle"}, stop for request/response use or reopen after a courteous delay for a long-lived follower.
  7. Prefer the TypeScript SDK's SessionChat or transcript watcher when you do not need to own cursor, paging, and reconnect behavior directly.

Next