Skip to content

Call Service

Language: Java 21
Framework: Spring Boot 3.5.5
Source: call-service

Purpose

Call Service manages the complete lifecycle of communication calls (voice, video, chat). It handles call creation, participant routing, LiveKit session management, queue management, and real-time agent availability — all within a single multi-tenant microservice.

Responsibilities

Responsibility Implementation
Call creation CallApiLiveKitCallService → creates LiveKit room + saves Call entity
Participant routing Redis sorted sets + Lua script → LiveKitCallService.inviteParticipant()
Agent availability AvailabilityService (Redis-backed) + contact-service integration
Call state tracking CallDataRecordService + PostgreSQL
Real-time updates Redis Pub/Sub for availability changes; SSE for live queue
Call transfers TransferCallQueueService + transfer-specific routing
LiveKit events LiveKitWebhookApi processes room/participant webhooks
Recordings LiveKitCallAudioService + LiveKit egress API → S3
Multi-tenancy TenantContextHolder + tenant-scoped Redis keys + Flyway schema separation

AI Agent ID Resolution

Call Service resolves agent_id through two paths:

1. Explicit API parameter
Callers pass aiAgentId directly in the request DTO: - DirectCall.calledParty when kind == "AI" - AiParty.aiAgentId in scheduled call requests

2. Channel default
When a channel is configured with aiAgent: true, the system falls back to the team's default AI agent:

// CallApi.java:314–318
if (!routeConfig.isRouteToAiAgent() && channel.isAiAgent()) {
    Optional.of(request.getTeamId())
        .flatMap(contactService::findTeamById)
        .map(TeamDetails::getAiAgent)
        .ifPresent(routeConfig::setAiAgentId);
}

Once resolved, the agent record is fetched from the AI Provisioning Service (Compass) via Feign:

// AiProvisioningService.java
@FeignClient(name = "ai-provisioning-service")
@GetMapping("/tenants/agents/{agentId}")
Optional<AiAgent> findAiAgent(@PathVariable String agentId);

Routing & Queue Management

Routing is implemented entirely within Call Service using Redis sorted sets and atomic Lua scripts — there is no separate routing microservice.

Queue Keys (tenant-scoped)

Key Purpose
{tenantId}:calls Main call queue
{tenantId}:transfer-calls Transfer queue
{tenantId}:channel-calls Channel-specific queue
{tenantId}:calls/{callId}/contacts Eligible contacts for a specific call
{tenantId}:contacts:available Sorted set of available contact IDs
{tenantId}:contacts:inviting:{contactId} 60 s TTL entry during active invite

Routing Strategies

Two strategies are stored per call in {tenantId}:calls-routes:

NEXT_AVAILABLE (default)
Performs an atomic ZINTERSTORE across: available contacts ∩ call-eligible contacts ∩ skill/team/language group sets. Picks the minimum-scored (highest priority) contact not currently being invited.

NEXT_IN_QUEUE
Iterates the call's contact list in score (priority) order. Picks the first contact that is available and not currently being invited.

Both strategies are implemented in a single Redis Lua script:
src/main/resources/scripts/call_queue_script.lua

The script ensures atomicity — no race conditions between concurrent routing decisions.

Queue Classes

Class Queue
CallQueueService calls
TransferCallQueueService transfer-calls
ChannelCallQueueService channel-calls
BaseCallQueueService Abstract base; manages Redis pub/sub listeners for availability changes

LiveKit Integration

Room Creation

Call Service creates a LiveKit room before accepting any call. Room metadata carries all context Atlas needs:

Room name format:
{tenantId}:{channelType}:{channelId}:{callId}

RoomMetadata fields passed to LiveKit:

Field Description
callId Unique call identifier
aiAgentId Resolved AI agent ID (used by Atlas to query Compass)
callingPartyId/Name/Type/Number Caller identity
callRecording, transcription Feature flags
videoEnabled, chatEnabled Media flags
languageCode Language hint for STT
liveTranslate, transferable Feature flags

ParticipantMetadata fields per participant:

Field Description
id Contact/user ID
displayName Display name
kind "AI" or contact type
roleId Team role

Webhook Processing

LiveKit posts events to POST /livekit/webhook. Call Service handles:

Event Action
room_started Mark call as active
room_finished Mark call completed; trigger billing publish
participant_joined Update participant status → JOINED
participant_left Update participant status → LEFT; calculate duration
track_published/unpublished Track media state

REST API

Method Path Purpose
POST /calls Create call (Direct / Dialer / CallToAvailable / Scheduled)
GET /calls Search calls (supports long-polling via waitMs)
GET /calls/{id} Get call details
GET /calls/latest Get latest call by phone number + type
POST /calls/{id}/accept Accept invitation
POST /calls/{id}/decline Decline invitation
POST /calls/{id}/join Join existing call
POST /calls/{id}/participants Invite participant
DELETE /calls/{id}/participants/{participantId} Remove participant
PUT /calls/{id}/hold Put participant on hold
PUT /calls/{id}/unhold Resume from hold
POST /calls/{id}/transfers Queue a transfer request
POST /livekit/webhook Receive LiveKit events
GET /live-queue/snapshot Live dashboard (team-pivoted)
GET /live-queue/snapshot/summary Dashboard summary + agent status
GET /live-queue/snapshot/teams Teams with call counts
GET /live-queue/snapshot/waiting Calls in queue
GET /live-queue/snapshot/agents Agent activity

Request DTOs

Class Purpose
DirectCall Call to a specific contact or AI agent
DialerCall Call from dialler (phone number + contact type)
CallToAvailable Route to an available team member by role + groups
ScheduledCall Pre-scheduled call with AiParty / TeamParty / ContactParty
InviteParticipantRequest Invite a participant to an active call
TransferToContact / TransferToGroup Transfer routing targets

Key Domain Entities

Call

Field Type Description
id UUID Unique call ID
channel FK Associated channel
callingParty / calledParty string Contact or AI agent IDs
status Enum ROUTING, ALERTING, ACTIVE, COMPLETED, FAILED, BUSY, …
routeConfig Embedded See RouteConfig below
startTime / connectedTime / endTime Instant Timestamps
callRecording, transcription, video boolean Feature flags

RouteConfig (embedded)

Field Description
role Role name for skills-based routing
aiAgentId Target AI agent ID
contactId Target contact ID
groups JSON array of skill/team/language groups
lang Language code (BCP-47)

Participant

Field Description
participantId Contact/user identifier
status JOINED, LEFT, MISSED, HOLD, DECLINED, …
kind "AI" or contact type
inviteId / expiry Invitation tracking
joinedAt / leftTime / duration Call timing

Channel

Field Description
identity Channel ID (PK)
type PHONE, VIDEO, WIDGET, …
routeConfig Default route configuration
aiAgent Whether this channel uses AI by default
availabilityConfig Availability hours / rules
transferCx FK to CxConnection for transfer routing

External Dependencies

Service Client Purpose
contact-service Feign Contact lookup, team info, availability
ai-provisioning-service Feign AI agent config (see Compass)
notification-service Redis Pub/Sub + Feign Call invitation delivery
LiveKit io.livekit:livekit-server:0.10.0 Room creation, SIP, egress
PostgreSQL Spring Data JPA + Flyway Persistent call/participant records
Redis / Valkey Spring Data Redis Queues, availability, pub/sub