Why it exists
Historically inbound comms did not wake an agent. The email and SMS webhook handlers only fired outbound subscriber webhooks (webhook_subscriptions) — an HMAC POST to the tenant’s own SaaS — and nothing
dispatched an agent run. Cron push-through recorded no binding row, and there
were three incompatible event shapes. The router closes that gap.
The envelope
Every source normalizes into aNaiveEvent:
This is the inbound envelope — what wakes an agent. It is not the same shape as the
LiveEvent your app reads from GET /v1/events, which carries { id, companyId, type, createdAt, payload }.Subscriptions (the binding registry)
Atrigger_subscriptions row binds a source (+ optional filter) to a
target (how to build the agent run). Tenant scope is nullable: a company-wide
subscription (tenant_user_id = null) matches every operator; a scoped
subscription matches only its tenant.
filter— matched against the event.event_types/event_typematch the dotted type; any other key matches the same-named field in the payload (e.g.{ "to": "sales@acme.com" },{ "e164": "+1555…" }).target—{ prompt }or{ promptTemplate }(with{{type}},{{source}},{{payload}}substitutions) and an optionalprofileName.
Delivery
The matched event is dispatched withsidecarFetch("/ceo/run"), which wakes the
agent’s legacy orchestration runtime — the previously-missing dispatch.
delivery on the subscription is auto (default) or sidecar; both resolve to the
same cloud sidecar. There is one lane, not two: auto is not a router that might pick
something else, and no subscription can currently be delivered to the durable runtime.
Delivery status
Every attempt writes atrigger_deliveries row and advances through
received → queued → delivered → processed | failed. An event that matched no
subscription records a single no_match row, so a silent drop (“why didn’t my
agent wake?”) is always observable. Query it at GET /v1/triggers/deliveries.
Behaviour-preserving
The router is additive. Existing outboundwebhook_subscriptions continue
to fire exactly as before — the inbound webhook handlers simply also call
routeEvent. Existing cron and webhook ingress keep their shape.
Loops
A Loop is just akind='loop' subscription whose
source is cron: the binding row is the registry, and the executor is the cloud
sidecar cron, kept in sync on create.