Every Action, a Typed Event

Aeion OS emits 900+ unique events across 30+ module prefixes — every order, ticket, alarm, sentiment score, AI inference, file upload, badge swipe, license sale. Subscribe from any other module, automate via Blueprint workflows, or wire into Neural Bus for AI remediation. Tenant-isolated envelopes, cryptographically signed, distributed via Redis Streams.

The Event Envelope

Every event on the Aeion bus carries the same envelope shape so subscribers can rely on it without per-event boilerplate:

typescript interface EventEnvelope { id: string; // ULID — sortable, globally unique name: string; // module.entity.action — e.g. "commerce.order.created" tenantId: string; // Auto-injected from request context; subscribers filter on this traceId: string; // For distributed tracing — flows across module boundaries ts: number; // Unix ms epoch version: number; // Schema version of the payload payload: unknown; // Typed per-event payload — module-specific contracts }

Naming convention: module.entity.action (snake_case).

  • commerce.order.created — domain event (something happened)
  • commerce.order.payment_failed — sub-state transition
  • cron.daily — scheduler event (no domain entity)
  • record.created — auto-CRUD event (collection-agnostic; filter by payload.collection)

Emit Pattern

From a route handler the tenant context is implicit — aeion.events.emit auto-injects tenantId and traceId:

typescript // Inside a route handler await aeion.events.emit("commerce.order.created", { orderId: order.id, customerId: order.customerId, total: order.total, }); // → tenantId + traceId + ts + ULID id auto-attached

From a module setup function (no request context), pass tenantId explicitly:

typescript await api.events.emit("commerce.order.created", payload, { tenantId });

Subscribe Pattern

Subscribers register at module setup. The handler receives the typed payload + envelope so it can branch on tenant, trace, or version:

typescript await api.events.subscribe( "commerce.order.created", async (payload, envelope) => { const { orderId, customerId } = payload; const tenantId = envelope.tenantId; const aeion = await api.createContext(tenantId); // … do work as the tenant }, );

Filtering on auto-CRUD events: the platform emits record.created / record.updated / record.deleted for every collection write. Subscribe once + filter by collection slug — the cheapest auto-trigger when no module-specific event exists:

typescript await api.events.subscribe("record.created", async (payload, env) => { if (payload.collection !== "abandoned_carts") return; // … abandoned-cart recovery flow });

Wildcard subscriptions (Redis Streams glob pattern):

typescript await api.events.subscribe("commerce.*", handler); // any commerce event await api.events.subscribe("*.payment_failed", handler); // any payment failure

Event Catalog by Module — Top Prefixes

`spatial.*` — 162 events

scene.created · asset.uploaded · ai_job.completed · multiplayer.lock_acquired · meeting.started · smart_object.verb_invoked

`sentinel.*` — 72 events

incident.created · alarm.tripped · access.denied · ai_threat.detected · anomaly.flagged · device.firmware_pushed

`workspace.*` — 56 events

page.created · record.updated · ai_agent.completed · embedding.generated · health.degraded · export.completed

`smart-spaces.*` — 53 events

occupancy.changed · iot.reading_received · booking.created · energy.threshold_exceeded · device.online · device.offline

`dining.*` — 52 events

order.fired · kds_ticket.bumped · table.seated · loyalty.tier_upgraded · waste.logged · reservation.confirmed

`recruit.*` — 41 events

candidate.applied · interview.scheduled · offer.sent · screening.completed · pipeline.advanced

`events.*` — 35 events

registration.created · ticket.scanned · session.started · attendee.checked_in · matchmaking.suggested

`finance.*` — 32 events

invoice.paid · journal_entry.posted · reconciliation.completed · expense.flagged · runway.updated

`connect.*` — 29 events

call.connected · sms.delivered · ivr.transition · campaign.launched · transcription.ready

`billing.*` — 27 events

subscription.created · payment.completed · payment.failed · plan.changed · invoice.due

`fleet.*` — 26 events

trip.started · alert.geofence_violation · maintenance.due · driver.scored · fuel.threshold_low

`legal.*` — 25 events

contract.signed · obligation.due · matter.opened · conflict_check.flagged · sol.calculated

`hr.*` — 24 events

employee.hired · timeoff.requested · review.completed · shift.swapped · payroll.run

`sports.*` — 21 events

game.scored · player.injured · league.standings_updated · ticket.scanned · stat.recorded

`platform.*` — 20 events

tenant.created · backup.completed · aegis.restore.completed · provisioning.finished · usage.threshold_crossed

`helpdesk.*` — 19 events

ticket.created · ticket.assigned · sla.breached · csat.scored · ai_routing.completed

`appointments.*` — 16 events

booking.confirmed · noshow.detected · reminder.sent · availability.updated

`crm.*` — 15 events

contact.created · deal.stage_changed · activity.logged · enrichment.completed · churn.predicted

`commerce.*` — 13 events

order.created · order.paid · cart.abandoned · refund.processed · inventory.low · fraud.flagged

`auth.*` — 13 events

login.failed · mfa.enrolled · session.created · anomaly.detected · password.reset_requested

`exec.*` — 12 events

kpi.threshold_crossed · briefing.generated · defcon.changed · resolution.passed · vault.accessed

`scm.*` — 11 events

po.received · stockout.predicted · supplier.scored · reorder.triggered

`forms.*` — 11 events

submission.created · submission.spam_detected · campaign.completed · resume_link.requested

`production.*` — 9 events

shoot.scheduled · asset.delivered · rights.cleared · vfx_job.completed

`lms.*` — 9 events

enrollment.created · course.completed · cohort.started · cert.issued

`hospitality.*` — 9 events

reservation.confirmed · checkin.completed · channel.synced · night_audit.completed

`files.*` — 9 events

upload.completed · scan.flagged · archive.created · retention.purged

`content.*` — 9 events

page.published · ai_seo.generated · translation.completed · embargo.released

`forum.*` — 8 events

thread.created · post.flagged · badge.awarded · karma.updated

Cross-Module Composition — How Events Stitch the Platform Together

Events are how Aeion's modules talk without coupling. Examples already in production:

``` commerce.order.paid (Commerce) ↓ subscribed by: • aeionica → trigger license delivery + forensic-watermark embed • crm → log activity against the contact + bump LTV • finance → post journal entry • neural-bus → check fraud-anomaly model • notifications → send order-confirmation email

sentinel.alarm.tripped (Sentinel) ↓ subscribed by: • helpdesk → create high-priority incident ticket • fleet → broadcast "facility_lockdown" alert • smart-spaces → elevator-control restricted access • notifications → push + SMS to security director • exec.crisis → DEFCON level evaluator

record.created where collection="abandoned_carts" (auto-CRUD) ↓ subscribed by: • commerce.recover_cart agent → AI-drafted recovery email • marketing → enroll in abandoned-cart drip campaign ```

Every subscriber receives the same envelope. Cross-tenant data isolation is enforced at the bus layer — a subscriber for tenant A never sees an event from tenant B even when both run on the same Redis instance.

Signed Envelopes + Replay Protection

The bus delivers events through Redis Streams with consumer-group semantics — at-least-once delivery, ordered per stream, replayable from any point.

  • Tenant scopingtenantId is sealed in the envelope at emit time and validated on receive. Subscribers cannot fake a tenant.
  • Trace propagationtraceId flows across module boundaries so distributed tracing shows the full request → event → handler → re-emit chain.
  • Replay protection — handlers receive an idempotencyKey derived from {eventName}:{tenantId}:{sourceEventId} so re-deliveries (after consumer restart, after explicit replay) don't double-process.
  • Consumer groups — each subscriber name owns its own offset; one slow consumer doesn't block another. Restart-safe.

Webhooks — The Customer-Facing Surface

Internal subscribers consume events directly off the bus. Customers (your tenants) configure outbound webhooks on a tenant-scoped allowlist:

typescript // Configure via Admin UI or webhooks API: { url: "https://customer-system.example/webhooks/aeion", events: ["commerce.order.created", "commerce.order.paid", "commerce.refund.processed"], secret: "whsec_...", // For HMAC-SHA256 signature verification }

Delivery semantics:

  • HMAC-SHA256 signature in X-Aeion-Signature header (computed over the raw body)
  • Retry with exponential backoff: 1s → 5s → 30s → 5m → 30m → 4h (max 6 attempts)
  • Dead-letter queue after final attempt; admins replay manually from /admin/platform/webhooks/dlq
  • Per-endpoint rate limit (1000 deliveries/min default) prevents accidental DoS of customer endpoints
  • X-Aeion-Event, X-Aeion-Tenant, X-Aeion-Trace, X-Aeion-Timestamp headers for routing on the receiver side
  • Replay protection — receiver should verify id hasn't been processed before; timestamp window prevents replay attacks (default ±5 min)

Where Events Plug In

ConsumerWhat it does with events
**Neural Bus**Pattern-detection feeds; ~90% rule-correlated, ~10% routed to SLM for novel-pattern analysis. Triggers Remediation Generator on detected anomalies.
**Blueprint workflows**Subscribe to any event to start a multi-step automation. Drag-and-drop authoring; no code.
**AeionClaw reactions**Per-tenant reaction rules — "when `helpdesk.ticket.assigned` fires, call the helpdesk-agent skill to draft a reply."
**Webhooks**Outbound HTTP delivery to customer-controlled endpoints. HMAC-signed, retry-with-backoff.
**Forge**Diary entries and session-timeline events come from this same bus — your AI agent's activity is a first-class event stream.
**Compliance + Aegis**Backup completions, restore jobs, retention purges, verification runs all emit so the platform compliance report writes itself.

Frequently Asked Questions

Redis Streams persist them. Each consumer group owns its own offset, so a consumer that's offline for a while can replay everything it missed when it reconnects. Retention is configurable per stream (default 30 days for module events, 90 days for compliance-relevant `auth.*` / `platform.*` / `aegis.*`).

Yes. From a Workflow step or a custom MCP tool, call `emit_event` with name + payload — same envelope shape applies. Tenant-scoped automatically. Custom event names should use a namespace prefix you control (`acme.*`) to avoid collision with built-ins.

The `describe_events` MCP action returns the full catalog with payload schemas per event name. Or grep `events.emit(` for the source-of-truth list.

Standard at-least-once retry: 3 attempts with exponential backoff (1s → 5s → 25s). After final failure, the event lands in the dead-letter queue with the original envelope + the error trace. Admins can replay from `/admin/platform/events/dlq`. The bus does not block on a failed subscriber — other subscribers continue processing.

Yes — each subscriber registers its own consumer group, so events fan out to every subscriber independently. A slow handler in one consumer group does not slow down another.

Yes within a stream — events with the same key (typically `tenantId`) deliver in order. Cross-stream ordering isn't guaranteed; if you need strict causal ordering across modules, use the `traceId` to reconstruct the chain.

Subscribe-to-deliver latency is < 10 ms on local Redis, < 50 ms over the public network for cross-region tenants. Async handlers run on the BullMQ worker fleet so emit is non-blocking; the route handler returns before the event has been processed by downstream subscribers.

Composes with the rest of the OS