Skip to main content
Naive normalizes every event source — cron ticks, inbound webhooks, inbound SMS/email, internal events, manual fires — into a single envelope and routes it through one trigger router. The router matches the event against subscriptions (the binding registry) and delivers it to the bound agent, recording a per-event status trail.

Why it exists

Historically inbound comms did not wake an agent. The Resend and Surge 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 a NaiveEvent:

Subscriptions (the binding registry)

A trigger_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_type match 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 optional profileName.

Delivery

For each matched subscription the router resolves a lane:
  • cloud-resident agent → sidecarFetch("/ceo/run") — the previously-missing dispatch that wakes the agent’s Hermes runtime.
  • box-resident agent → the device relay — the event is enqueued as a trigger.wake on the device’s durable device_events queue; the box’s device-agent replays it and runs the agent locally.
delivery on the subscription is auto (resolve at delivery), sidecar, or device. In auto, a Tower box always uses its local runtime; in cloud, a provisioned device bound to the company routes to the box, otherwise the cloud sidecar.

Delivery status

Every attempt writes a trigger_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 outbound webhook_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 a kind='loop' subscription whose source is cron: the binding row is the registry, and the executor is the box’s Hermes gateway cron (or the cloud sidecar cron), kept in sync on create.