π§Design Philosophy
Three swappable, industry-standard abstractions: engine-agnostic verdicts / usage metering / notification-channel abstraction.
Aegis's design centers on "swappable, industry-standard." The verdict engine, billing, and notifications are each built on abstractions that don't lock into one implementation and can be swapped or extended later.Each source is a real implementation file. Confirmed values and paths are kept consistent with the SSoT. This page explains the design philosophy β no performance figures or operational guarantees (those are finalized after on-device testing).
π Engine-agnostic (not locked to a vendor)
Which AI engine handled a call is unified into one canonical column, session_provider, so the verdict engine is swappable. The design avoids lock-in to any single vendor.
- Canonical values: retell (Route A) / openai_realtime (Route C) / local_pipeline (Route B, reserved) / none (no AI). canonical_engine() absorbs aliases (openai / default).
- The inbound SIP path (telephony_provider: retell / rhodium, etc.) and the AI engine (session_provider) are separate dimensions. A call routed via Rhodium but processed by Retell is telephony=rhodium Γ engine=retell.
- The verdict logic lives in tools_core (classify_call / transfer_to_human); both the Retell and Realtime routes call the same core, so the branch point is single.
Main implementation: backend/core/engine_canon.py / services/tools_core.pyοΌS1οΌ
π§Ύ Usage metering (industry-standard usage-based)
Each finalized call appends a usage_event. An idempotency key collapses duplicates, and no PII is stored. Usage per client Γ engine is retrievable via an aggregation API β the foundation for standard usage-based billing.
- Append-only: records are only added, never rewritten (strong for audit and reconciliation).
- Idempotency key: the same call Γ same metric collapses to one row (no double counting on webhook re-delivery or retries).
- PII-less: no phone numbers or transcript text β only client_id Γ engine Γ metric Γ quantity plus an allowlist of meta keys.
- Recorded only at finalization: Retell in _finish_call, Realtime in /end; never from pending rows (first line against double counting).
- Aggregation API (GET /api/usage): retrieve by client Γ engine Γ period, with role boundaries (a cross-tenant client_id returns 404).
Main implementation: backend/services/usage_metering.py / models/usage_event.py / api/usage.pyοΌS2/M1οΌ
π’ Notification-channel abstraction (add destinations later)
Notifications are split into a common interface plus per-channel implementations, so destinations (app / Slack / LINE / email) can be swapped or added later. All channels default OFF and are turned on individually as they become ready.
- Common interface: each channel only implements is_enabled() and send(); callers don't know a channel's internals.
- Default OFF: turn on with AEGIS_NOTIFY_<CHANNEL>_ENABLED=true; everything is off by default (no unintended sends).
- App-first: the app notification is the primary channel today; Slack / LINE / email are interface-only for now (real sending once ready).
- Mind the silent regression: since this changes the old Slack-only behavior, the ON flag is called out in the deploy steps (a design caveat).
Main implementation: backend/notifications/channels.py / dispatcher.pyοΌ#44οΌ
Locking into one vendor (e.g. Retell) or one channel (Slack only) means rewriting the core every time you switch or extend. By placing the verdict engine, billing, and notifications each behind an abstraction, the core stays unchanged even when the implementation is swapped. Usage-based billing and PII-less records follow the industry standard for SaaS billing and auditing.