router and delivery modules provide the external MCP boundaries.
Delivery flow
1
Resolve
Call
meta/routing/resolve with a recipient and optional channel, retry, backoff, and rate-limit values. The runtime calls the router module’s routing.resolve MCP tool, applies any matching runtime-route override, mints a route_id, and retains the resulting RoutingResolution as trusted state.2
Send
Call
meta/delivery/send with the complete trusted resolution, a JSON payload, and an optional idempotency_key.3
Replay or validate
With the delivery module loaded and a valid key, the runtime first checks for a retained idempotent replay. A matching payload and canonical resolution return the existing record without dispatch. Otherwise, a new send requires the supplied resolution to equal the retained issued resolution for its
route_id; unknown or modified resolutions are rejected before dispatch.4
Dispatch and record
For a new send, the runtime applies the resolved rate limit, invokes the delivery module’s
delivery.send MCP tool once, and appends a DeliveryRecord to bounded history after a successful boundary call.Routing contracts
A mutable route override and a resolved route are different types:channel = "notification", retry_max = 1, backoff_ms = 250, and rate_limit_per_minute = 2. Before module and runtime-route overrides, the runtime infers email for recipients containing @, sms for recipients starting with +, and webhook otherwise.
These sink strings are routing defaults, not a promise that Meta ships an SMTP, SMS, or webhook adapter. The router module can override the sink and return a target_module; a matching runtime route can override both again. The current meta/delivery/send contract dispatches only a trusted resolution whose target_module is exactly delivery. Any other target is rejected before the delivery MCP boundary call, so alternate target values are representable for routing inspection but are not dispatchable. The delivery module owns actual dispatch.
Runtime routes can be listed, added, and deleted through:
meta/routing/routes/listmeta/routing/routes/addmeta/routing/routes/delete
* recipient and channel matches. A matching route is applied after the router MCP response.
Mob-member routes
When the unified runtime reconciles its active roster and the router module is loaded, it manages one route per member:Optional idempotency
DeliverySendRequest.idempotency_key is Option<String>:
{route_id}:{idempotency_key}. A repeat with the same payload and canonical resolution returns the existing DeliveryRecord without another boundary call. Reusing the scoped key with a different payload is rejected.
Issued resolutions have a separate cache of at most 512 entries. A new
dispatch on an evicted route must resolve again. A matching keyed replay can
outlive route-cache eviction because its idempotency entry retains the
canonical resolution, but only while the corresponding delivery/idempotency
record remains retained and the delivery module remains loaded. Modified
resolutions are rejected on both replay and new-dispatch paths.
This protection is conditional and bounded:
- No key means no idempotency lookup.
- The index is local to the runtime instance.
- Delivery history retains at most 200 records.
- Evicting a record also removes its idempotency entries, so a later retry can dispatch again if its issued route is still retained; otherwise it must resolve again.
Rate limiting
Each resolution carriesrate_limit_per_minute. The implementation uses fixed 60-second windows keyed by:
route_idrecipientsink- the window start timestamp
DeliverySendError::RateLimited.
Delivery records and adapters
DeliveryRecord contains the delivery and route IDs, recipient, sink, target module, original payload, status, attempt records, first and final attempt timestamps, optional idempotency key, and optional sink_adapter.
After the single MCP boundary call succeeds, Meta synthesizes
retry_max + 1 attempt rows: nonfinal rows are labeled transient_failure,
and the final row carries the record’s status. final_attempt_ms is computed
from first_attempt_ms + backoff_ms * retry_max (with saturating arithmetic),
not measured after repeated sends. These rows are not evidence of actual
retry attempts, observed transient failures, or elapsed backoff. A boundary
error returns immediately instead of entering a retry loop.
The sink comes from routing. The optional sink_adapter is recorded only when the delivery module returns a non-empty adapter string. Meta does not maintain a fixed built-in adapter inventory in this contract.
Delivery dispatch emits a unified module event with:
module: "delivery"event_type: "send"- payload fields
delivery_id,route_id,recipient,sink,status, andattempts
meta/delivery/history. The request supports optional recipient and sink filters plus a capped limit.
Clock ordering
Routing and delivery timestamps use a runtime-relative monotonic floor. Resolving a route also advances the delivery clock to at least the latest merged-event timestamp; subsequent deliveries advance that clock, including the synthetic attempt-time calculation above.send_delivery does not independently incorporate timestamps from live
events appended after route resolution. A send using a retained route can
therefore receive a timestamp earlier than an event already merged since
that resolution. Do not infer insertion order across all event sources from
these timestamps.
See also
- Gating - evaluate policy before dispatch
- Modules - router and delivery MCP boundaries
- Events - exact
resolvedandsendevent names - JSON-RPC API - routing and delivery methods
