Engine Application

Services

Engine services live in engine/service/ and are picked up by the component scan. They are grouped below by the concern they serve.

Conversation Runtime

Service Responsibility

PipelineService

Builds pipeline configurations and pipeline instances, and runs the session state machine — see Interaction Pipeline.

PipelineSocketService

The Socket.IO side of the conversation transport — see Socket Handlers.

SessionService

Reads and writes Session and Turn documents: resolving or creating the session of a connection, persisting turns, querying a user’s session history, finding sessions that should be ended or abandoned, and purging a user’s data.

SpeechService

Wraps the speech recognizer and synthesizer components with normalization, chunking and the audio cache.

A2XService

Converts speech items into animation events (visemes and blend shapes) for avatar rendering.

RealtimeConfigurationService

Resolves a stored realtime configuration and its input modalities by ID.

ProfanityService

Validates user-chosen names against a profanity word list.

SensitiveInformationMaskerService

Masks personal and sensitive data in tokens and output elements before they are logged.

Agents, Projects and Prompts

Service Responsibility

AgentService

The central service for agents and projects: lookup by ref, key or ID with revision resolution, mapping to the API DTOs, agent grouping, starring, and the consumer authorization rules (see Security Implementation).

AgentContextService

Builds the AgentContext for a turn — agent, project, session, memory, active skills, shared agents and evaluation state. Saves the session once the project is resolved, so its user, agent and project association are persisted from the start of the conversation.

BasicContextService

Builds the context for the agent-less pipeline setups.

AgentPromptService

Assembles the prompts used for an agent: the system prompt and the per-extractor prompts, including prompt invocation tracking.

TextTemplateService, TextTemplateRenderer

Load named text templates and render them with Pebble, substituting context variables.

BasicUserService

Resolves and creates users from a JWT, a name or a device, qualifying usernames with the deployment’s domain.

Memory and Knowledge

Service Responsibility

MemoryService

User memory lifecycle: snapshot, restore and delete, backing the memory endpoints.

EmbeddingModelFactory

Creates the reactive embedding model for the configured provider, used by the scoped memories.

EntityIndexerService

Indexes entities (technical reports, bug reports) into Elasticsearch for later retrieval.

ToolBoxService

Assembles the tool box available to an agent in a turn — see AI Layer.

Evaluation

Service Responsibility

EvaluationService

Runs the evaluations configured for an agent, per turn and at the end of a conversation.

EvaluationResponseService

Builds the evaluation status and insight responses (sections, groups, featured result) returned by the evaluation endpoints.

Feedback

Service Responsibility

FeedbackService

Chooses which feedback definition a user is shown and builds its specification, creates the feedback that records the request, and fills it in from the user’s submission, skip or postponement. See Feedback Scheduling.

Codebook and Development Profile

Both run after a conversation ends, on the back of the evaluations it produced — see Codebook and Development Profile for how they work together.

Service Responsibility

CodebookClassificationService

Turns a session’s user-facing evaluation results into coded occurrences: it collects the insight and featured subjects, asks one LLM call to assign each an existing code, and stores the occurrences. Subjects already classified are skipped, so a re-run costs nothing.

CodebookGrowthService

Grows the project’s codebook once enough occurrences have piled up unclassified. Claims a batch atomically, asks the model to propose new codes for it, and assigns every text in the batch.

DevelopmentProfileService

Owns the per-user profile: gathers the evidence, decides whether anything changed since the last run, drives the generator, computes the deterministic advisor block, and persists the result.

DevelopmentProfileGenerator

Runs the LLM tool-loop that edits the profile through DevelopmentProfileToolBox. No persistence of its own.

CodebookReplayService

Replays both mechanisms over a user’s existing sessions, oldest to newest, snapshotting the state after each one. A dry run deletes only the rows it created and restores the previous profile.

Language Models

Service Responsibility

SpringAiLLMService

The SpringAiLLMFactory implementation: constructs the Spring AI chat models for the configured providers and enforces streaming usage.

LLMConfigurationService

Resolves the effective model configuration for a component — model name, reasoning mode and overrides.

LLMLoggerService

Logs every model invocation with its prompt, response, tool calls and usage, applying sensitive information masking.

GenericJsonParser

Coerces loosely structured model output into typed DTOs, which is what makes extractors resilient to formatting drift.

LinkHallucinationService

Tracks the URLs that legitimately appeared in tool results and planning, so links the model invents can be detected.

LinkValidationService

Checks that a URL is well-formed and reachable before it is shown to a user.

Integrations

Service Responsibility

McpConnectionService

Loads, lists and deletes Model Context Protocol connector configurations for a project.

OAuthTokenService, OAuthCodeStateStore

Third-party OAuth: the authorization code exchange with PKCE, token storage and refresh, and the short-lived state store for an in-flight flow.

TwilioCallService

Implements CallService for Twilio: builds the TwiML that connects a call to the engine’s Twilio socket and places the outbound call.

WebhookService

Executes the pre-session and post-session webhooks configured on an agent or project.

MessageService, FirebaseService

Send messages to a user, to an agent’s users or to a project’s users; Firebase Cloud Messaging is the delivery channel.

ClickUpService, BugReportService, BlankBugReportService

User-submitted bug reports and issues as tasks in ClickUp. BlankBugReportService is the no-op fallback when the integration is not configured.

WebSocketClientService

Outbound WebSocket client factory used by components that talk to external realtime services, tracking the active sessions per host.

SecretService

Reads and decrypts the stored secrets (provider API keys, credential files) scoped to a tenant and project.

Billing and Usage

Service Responsibility

SubscriptionService

Checks the tenant’s billing status, seat allowance and remaining minutes before a conversation is allowed to start.

EDRService

Records event detail records — active users and active conversation duration — which is the basis for usage metering.

NavigationStatusService

Resolves the per-user state of every navigation entry — the recorded state, else the entry’s default — and writes it: unconditionally for the session-end judge, and for an evaluation through applyEvaluations (featured results keyed by the evaluation’s ref, so a condition holds for every revision of its evaluation), which moves an entry up only — HIDDEN to LOCKED when its revealCondition matches, then LOCKED to UNLOCKED when its condition matches — with a write guarded by the state it read, so a concurrent change wins. resolveStates only reads the stored states. refreshFromHistory catches up the active entries an evaluation could still raise against all of the user’s earlier results in the project (EvaluationQueryService.insightValuesByRef / featuredValuesByRef) and writes only the entries that move. This also covers an entry added, or a condition changed, after the user’s evaluations. A relational agent session refreshes once, at session start (also on a reconnect); its turns and the session-end judge then only read. The API reads (getStatus and the agent and project reads of AgentService) refresh before every read. The catch-up notifies nothing, and when the history cannot be read the stored states are used as they are. refreshForAllUsers does the same for one entry and every user with evaluation values in the project, for the manual POST /api/navigation/{ref}/refresh. It reads only the distinct user IDs up front (EvaluationQueryService.userIdsWithValues, a $group aggregation on each result collection) and then loads each user’s history and stored state on its own, four users at a time, so memory stays bounded to a few users however large the project is.

NavigationDecisionService

Reviews, when a session ends, the state of the agent-controlled navigation entries that are global or mentioned in the session’s agent as @navigation:<ref>;. An entry with an evaluation condition or revealCondition is never agent-controlled (canAgentChangeState): it belongs to its evaluations, so the judge and the evaluations never change the same entry. The agent has no tool for them: the relational planner gets a short "Home-screen tiles" note after the business process (PlanningPromptTemplateService.navigationHint) listing each tile with what the user sees now (the stored state, else the default) and telling it to say its recommendations by name and not to claim a change has happened, and @navigation:<ref>; mentions reach it as the tile’s name. Runs from the interaction manager’s onSessionDisconnect, alongside the session aggregator, so it also covers sessions that end by a client disconnect, and it is done before the end-of-conversation evaluations start. One call to the session’s model with the prompt template judges.navigation_state (engine/templates/prompts/judges/, variant selectable per agent through its prompt overrides), filled with the agent’s purpose, business process and guardrails (which say when entries change), the conversation from the session’s memory and, per entry, its toolDescription (else description) as the criteria, its current state and the three states it may set. The agent’s instructions win over an entry’s criteria when they conflict. An entry the conversation does not mention keeps its state, unless the agent’s instructions explicitly say what happens to entries that were not recommended; the judge is told for each entry whether its current state is the default or was set earlier. Answers that name an unknown entry, a disallowed state or the current state are dropped. An entry it unlocks is notified at once and its ref recorded on the session (Session.unlockedNavigationRefs), from which unlockedElements lets PipelineService emit it as a NavigationSpecificationElement with the session-end output (a disconnected session has no output left). Failures are logged and never fail the session end.

Debugger

LLMDebuggerToolCallService computes the tool call statistics of a project from its LLM logs and runs relevance classification, ToolCallCriteriaService manages the versioned criteria it classifies against, and JudgeModelSelector picks the judge model for both this classification and replay judging — see Tool Call Analysis.