EventEnvelope<UnifiedEvent>merges projected Meerkat agent events with Meta module events.MobStructuralEventEnvelopeprojects structural events from the Meerkat mob-event ledger.- The WorkGraph wake stream emits bare
UnifiedEvent::Modulevalues atGET /meta/workgraph/facts/streamas lossy notifications.
Unified event envelope
UnifiedEvent::Agent, source must be "agent". For UnifiedEvent::Module, it must be "module". The optional agent payload is part of the wire contract and must not be dropped when matching or forwarding the event.
Agent event names
Agent event names come from Meerkat’s exhaustive event mapper. Current examples include:- Run and turn:
run_started,retrying,run_completed,run_failed,turn_started,turn_completed - Model output:
reasoning_delta,reasoning_complete,text_delta,text_complete - Tools:
tool_call_requested,tool_result_received,tool_execution_started,tool_execution_completed,tool_execution_timed_out - Hooks:
hook_started,hook_completed,hook_failed,hook_denied - Compaction:
compaction_started,compaction_completed,compaction_failed - Interaction:
interaction_complete,interaction_callback_pending,interaction_failed. Only the first and last are terminals;interaction_callback_pendingmeans the interaction is paused at an external callback boundary waiting for tool results, and it resumes and closes under the sameinteraction_id
AgentEvent variants. Meta does not synthesize them as module events.
Run failure payload
run_failed carries Meerkat’s typed failure fact as error_report; since Meerkat 0.7 there is no separately carried error string on the wire. Meta’s projection (console_agent_event_payload, the single site every SSE stream, event-log row and console frame goes through) keeps the report and derives two flat keys from it, so consumers read one shape:
The console
interaction_failed frame Meta projects from run_failed carries the same payload, so wire consumers read frame.payload.error and frame.payload.reason on REST/RPC/SSE timeline frames. Frames Meta mints itself (reason: "superseded_by_later_run", or error set from a delivery failure) carry only the flat keys. Meerkat’s own interaction_failed agent event carries a typed reason object (kind discriminator) whose Display is mirrored into error. The Python SDK exposes the report as RunFailed.error_report (with error_class and reason_type accessors) and derives RunFailed.error from it; the TypeScript SDK exposes RunFailedEvent.errorReport; UnifiedAgentEvent.payload carries it for query_events consumers.
A retried LLM call (a 429, a network timeout) surfaces as retrying events before either terminal; a non-retryable failure (a 401 on a bad key) reaches run_failed in one round trip. The user_input frame of a console send terminates at delivered by design (the send was admitted); the turn’s outcome is the sibling interaction_complete or interaction_failed frame, not a status change on the input frame.
A turn still running when the gateway shuts down gracefully (meta/shutdown, or SIGTERM to the gateway process) on a member that is not durable also gets its terminal frame. The shutdown ends the run, and it drains the run’s terminal into the console log before the gateway exits: interaction_failed when the shutdown cancelled the run, interaction_complete when the run finished first. A durable member’s run (one whose session lives on a persistent session service) is not ended there: the shutdown suspends it without a terminal, and the run continues at the member’s next start, where its terminal frame comes when it ends. A crash or SIGKILL mid-turn can leave the turn without a terminal frame. After a restart Meerkat settles some interrupted runs without publishing a terminal, by design, so the caller’s own timeout is the outcome it sees. Bound any wait on an interaction_id with a timeout, or check the member’s status.
Tool result payload
A tool call’s output travels once, ontool_result_received: its typed content blocks, and a flat result string Meta’s projection derives from their text. tool_execution_completed, published just before it for the same id, is the execution fact: whether the call failed and how long it took, with no content and no result. Both events mirror id into tool_call_id, so consumers join them on it.
ToolResultReceived.result and the TypeScript SDK as ToolResultReceivedEvent.result; neither SDK’s completion type has a result. Console timeline frames rebuilt from session history keep the tool_execution_completed kind and carry the output, because a transcript records only the result.
Merged module event names
Meta itself generates these module event types in the merged envelope log:
Generic modules may supply additional event names through their own valid envelopes. Gating audit entries, memory operations, and supervisor health transitions are not currently generated as unified module events by Meta.
WorkGraph wake events
The separate WorkGraph wake stream uses theUnifiedEvent::Module shape but serializes that value directly as SSE data. It does not wrap the value in EventEnvelope, write it to merged_events, or include it in meta/events/subscribe.
Every connection begins with
workgraph.resync_required and reason = "initial_sync". The stream then sends workgraph.fact wakes and can send workgraph.resync_required with reason = "lagged" after broadcast lag. It has no SSE ID, Last-Event-ID, catch-up, or replay contract. Consumers obtain authoritative graph state through the WorkGraph get, list, ready, and snapshot pull methods.
Ordering and normalization
merge_unified_events orders envelopes by:
timestamp_msevent_idsource
EventEnvelope<UnifiedEvent> JSON and a supported flat event form. Normalization verifies that the source agrees with the UnifiedEvent variant and rejects malformed input. It does not mint missing IDs, enforce ID uniqueness, deduplicate events, or rewrite timestamps.
insert_event_sorted uses the same ordering when runtime operations add later events.
The merged envelopes are a recent-event buffer, not a history: the runtime keeps at least the newest 4,096 and drops older ones in batches as new ones arrive. Every forwarded member event lands there, text deltas included. For history, read the event log (meta/query_events, when one is configured) or the console timeline.
Structural mob events
The structural surface preserves a Meerkat mob-event cursor and the serializedMobEventKind payload:
event_id: "mob-evt-{cursor}", preserves the upstream cursor and timestamp, maps the event kind to its snake-case name, and places the full serialized kind payload in data. Current kinds include mob_created, member_spawned, flow_started, step_dispatched, members_wired, and supervisor_escalation. The enum can grow, so clients should not depend on a fixed variant count.
Member creation facts
Themember_spawned structural payload includes creation, a
MemberCreationRecord from the canonical Meerkat journal. Its creation_id
is a runtime-issued token; its provenance.kind is root, spawn, fork,
successor, unproven or legacy_unknown. Spawn provenance records the exact
source session and member binding. Fork provenance refers to the event’s
existing fork_source endpoint instead of introducing a second endpoint.
Older journals that omit creation deserialize with creation_id: null and
provenance.kind: "legacy_unknown". Projection never fabricates a token or
turns missing ancestry into a proven root. New creations whose ancestry cannot
be proved use unproven with a new token; ordinary execution can still proceed.
These facts establish runtime ancestry and contain no tool policy. Resource
ACLs and current native policy remain separate checks. See Meerkat’s
member creation contract.
Cursor persistence and replay
The structural subscriber reads fromMobEventsView. With a saved cursor it uses subscribe_after(cursor); without one it starts from the latest cursor. It writes the last projected cursor to PersistentMetadataStore after projection.
Cursor persistence is not event persistence:
- If a persistent-state path is configured and no explicit metadata store is supplied, Meta creates
<path>/meta_metadata.sqlite3for projection metadata. - That metadata stores the resume cursor, not the mob-event ledger. On an
identity-first runtime it also keeps the lifecycle projection’s cursor, what
each lifecycle step owes and the side effects that failed: a host that
supplies its own store (
UnifiedRuntimeBuilder::persistent_metadata) keeps them durably, or a restart skips the side effects a stopped process still owed (see Lifecycle side effects across a restart). .persistent_state(path)keeps Mob storage durable inevent_log.sqlite3under the state directory; a build without it keeps Mob storage in memory.- Replaying events written while Meta was down requires an upstream Meerkat mob-event store that retained those events.
Access surfaces
See SSE API for stream parameters and JSON-RPC API for request and response shapes.
Lifecycle events
LifecycleStage has these exact variants:
Each
LifecycleEvent contains a monotonic seq and one stage. Read the retained entries through MetaRuntimeHandle::lifecycle_events(). ModulesStarted is not proof that every module is healthy; inspect SupervisorReport for module transitions.
See also
- Modules - module startup and event origins
- Delivery -
resolvedandsendpayloads - SSE API - unified, WorkGraph wake, and structural streams
- Architecture - event transport and ownership
