The Canonical Event Chain — Why Every Message Produces 7 Events

March 2026 · Technical deep dive

Integration styles

Most chat-agent integrations are imperative: receive a payload, branch on errors, call an API, hope the logs are enough when something breaks. Chat Agent Relay is declarative at the protocol layer: every user message is expected to advance through the same ordered chain of event types. Adapters translate platform-specific wire formats in and out; the middleware never needs to know Slack’s payload shape to reason about policy or audit.

The seven-event chain

Each step is a durable, schema-validated fact in the ledger:

  1. message.received — The channel adapter canonicalizes the inbound message (text, thread, user, workspace identifiers) into the shared envelope. Nothing downstream should parse raw Slack or HTTP bodies.
  2. policy.decision.made — The policy engine evaluates rules (keywords, regex, and future richer logic) and records allow or deny before any agent call. Blocked traffic never touches the model; the decision is still auditable.
  3. route.decision.made — Routing selects which backend adapter handles this conversation (model, tenant-specific endpoint, A/B experiment, etc.).
  4. agent.invocation.requested — Dispatch includes conversation context and correlation metadata so backends stay stateless where possible and traces stay coherent.
  5. agent.response.completed — The agent’s reply (or structured failure from the backend) is captured as an event, not only as a return value on the stack.
  6. message.send.requested — Outbound delivery is queued with retry policy; the user-visible “send” is separate from the agent finishing.
  7. message.sent — The channel confirms delivery (or terminal failure after retries), closing the happy path for that turn.

event.blocked

When anything fails policy, routing, invocation, or delivery, Chat Agent Relay emits event.blocked: a first-class failure record with reason and stage. Failures are not only exceptions in logs; they are queryable events with the same envelope as the rest of the system, so dashboards and compliance tools can treat them uniformly.

Why this matters

The event envelope

Events conform to a shared JSON Schema contract. The envelope carries cross-cutting fields so traces survive across services and storage backends, for example:

Normative detail lives in the project’s RFCs and schema artifacts; implementations validate at boundaries so bad data never poisons the ledger silently.

Adapters as boundary translators

Chat Agent Relay splits the world at two interfaces. Channel ingress implements ChannelIngress: turn vendor webhooks or sockets into message.received and apply outbound mapping for message.send.requested / message.sent. Backend adapters implement BackendAdapter: consume invocation events, call models or HTTP services, and emit agent.response.completed. Both sides speak only canonical events plus the envelope — the glue that used to sprawl across your codebase becomes replaceable modules.

Try it and read the specs

Run Chat Agent Relay locally from the documentation, then read the RFCs in the repository for the full contract. The seven-event story is the spine that keeps channels, agents, and audit aligned.

← Back to Blog