Skip to main content
KaireonAI separates business logic from infrastructure through a set of TypeScript interfaces (lib/infra/interfaces.ts) and a dependency injection container (lib/infra/container.ts). Each infrastructure concern — interaction storage, event publishing, search, caching, and logging — has a default implementation that works out of the box and one or more alternatives that you can activate with a single environment variable. No code changes are required to switch backends. Set the env var, restart, and the container instantiates the correct adapter as a singleton.
// Business logic imports are always the same — the container handles the rest
import { getCache, getEventPublisher, getInteractionStore, getSearchIndex } from "@/lib/infra/container";

const cache = getCache();
const events = getEventPublisher();
const interactions = getInteractionStore();
const search = getSearchIndex();
All infrastructure singletons support graceful shutdown. Call shutdownInfra() on SIGTERM to close connections cleanly. You can also call resetInfra() to tear down and re-initialize all backends when configuration changes at runtime.

Interaction Store

The interaction store persists decision history, customer interactions, impressions, outcomes, and conversion data. It powers the Customer Viewer 360, attribution, and analytics.
Adapter: pg-interaction-store.tsThe default. Stores interactions in the same PostgreSQL database as the rest of the platform. Zero additional infrastructure.
Best forDevelopment, small deployments, less than 10K decisions/day
ConfigNo additional config — uses DATABASE_URL
CostIncluded with your existing database
# Default — no env var needed
# INTERACTION_STORE=pg  (implicit)
At high volumes, interaction rows grow fast. Consider moving to DynamoDB or Scylla when you exceed 10K decisions/day or when interaction queries start affecting OLTP performance.

Event Bus

The event bus handles asynchronous event routing for decision events, model update notifications, pipeline triggers, and real-time streaming. All implementations satisfy the EventPublisher interface with publish(), subscribe(), and disconnect() methods.
Adapter: redis-events.tsUses your existing Redis instance for lightweight pub/sub messaging. Simple, no additional infrastructure.
Best forDevelopment, staging, low-to-medium throughput
ConfigUses REDIS_URL — no additional env vars
CostIncluded with your Redis instance
# Default — no env var needed
# EVENT_PUBLISHER=redis  (implicit)
Redis Pub/Sub is fire-and-forget — messages are lost if no subscriber is connected. For production workloads requiring durability and replay, use Kafka, MSK, or Kinesis.

Search Index

The search index powers full-text search across offers, categories, decision flows, and other platform entities. It also drives the global search bar and analytics queries.
Adapter: pg-search.tsUses PostgreSQL’s built-in tsvector full-text search. No additional infrastructure needed.
Best forUnder 1M records, simple search queries
ConfigUses DATABASE_URL — no additional env vars
CostIncluded with your existing database
# Default — no env var needed
# SEARCH_INDEX=pg  (implicit)

Cache

Redis is used for caching enrichment data, sliding-window rate limiting, session storage, circuit breaker state, and BullMQ job queues. The CacheStore interface provides get, set, del, and getOrFetch (cache-aside pattern) methods.
REDIS_URL=redis://localhost:6379
The cache adapter works with any Redis-compatible endpoint:
  • Redis OSS — local development or self-hosted
  • Amazon ElastiCache — managed Redis on AWS
  • Upstash Redis — serverless Redis with per-request pricing
  • Dragonfly — Redis-compatible, multi-threaded drop-in replacement
Redis is optional in development (the platform falls back to in-process defaults), but required for production. Without Redis, rate limiting, enrichment caching, and background job processing will not function correctly.

Logging

KaireonAI uses Winston for structured JSON logging. The default console transport works for development and containerized deployments where log aggregation happens at the orchestrator level (e.g., CloudWatch Container Insights, Datadog Agent).
Structured JSON logs written to stdout/stderr. Works with any log aggregation system that reads container output.
LOG_LEVEL=info  # debug | info | warn | error

Choosing Your Stack

Use these reference architectures as a starting point. Every backend is independently swappable, so you can mix and match based on your requirements.

Development (zero config)

All defaults. No additional services beyond PostgreSQL and optionally Redis.
ConcernBackendConfig
InteractionsPostgreSQLDefault
EventsRedis Pub/SubREDIS_URL
SearchPostgreSQL FTSDefault
CacheRedisREDIS_URL
LoggingConsoleDefault
Estimated cost$0 (local)
Lean AWS deployment using managed services with pay-per-use pricing.
ConcernBackendConfig
InteractionsPostgreSQLDefault
EventsEventBridgeEVENT_PUBLISHER=eventbridge
SearchPostgreSQL FTSDefault
CacheUpstash RedisREDIS_URL=rediss://...
LoggingCloudWatchVia platform settings
Estimated cost$50-100/mo
Higher throughput with dedicated event streaming and search infrastructure.
ConcernBackendConfig
InteractionsDynamoDBINTERACTION_STORE=dynamodb
EventsMSK or KafkaEVENT_PUBLISHER=msk
SearchOpenSearchSEARCH_INDEX=opensearch
CacheElastiCacheREDIS_URL=rediss://...
LoggingCloudWatchVia platform settings
Estimated cost$500-1K/mo
Maximum throughput with dedicated high-performance backends.
ConcernBackendConfig
InteractionsScyllaINTERACTION_STORE=scylla
EventsMSKEVENT_PUBLISHER=msk
SearchOpenSearchSEARCH_INDEX=opensearch
CacheElastiCache (cluster mode)REDIS_URL=rediss://...
LoggingCloudWatchVia platform settings
Estimated cost$2K+/mo

Adding a Custom Backend

Every infrastructure concern is behind a TypeScript interface. To add your own implementation:
1

Implement the interface

Create a new file in src/lib/infra/ that implements one of:
  • InteractionStorerecordEvent(), getSummaries(), disconnect()
  • EventPublisherpublish(), subscribe(), disconnect()
  • SearchIndexsearch(), index(), disconnect()
  • CacheStoreget(), set(), del(), getOrFetch(), ping(), disconnect()
2

Register in the container

Add a new case to the corresponding factory function in container.ts:
case "my_custom_store": {
  const { MyCustomStore } = require("./my-custom-store");
  interactionStoreInstance = new MyCustomStore({ /* config from env */ });
  break;
}
3

Set the environment variable

INTERACTION_STORE=my_custom_store
4

Deploy

Restart the application. The container picks up the new env var and instantiates your implementation. Zero changes in business logic, API routes, or decision flow engine.