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 transitioncron.daily— scheduler event (no domain entity)record.created— auto-CRUD event (collection-agnostic; filter bypayload.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 scoping —
tenantIdis sealed in the envelope at emit time and validated on receive. Subscribers cannot fake a tenant. - Trace propagation —
traceIdflows across module boundaries so distributed tracing shows the full request → event → handler → re-emit chain. - Replay protection — handlers receive an
idempotencyKeyderived 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-Signatureheader (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-Timestampheaders for routing on the receiver side- Replay protection — receiver should verify
idhasn't been processed before; timestamp window prevents replay attacks (default ±5 min)
Where Events Plug In
| Consumer | What 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.