Architecture
Layers
API Layer
- ConversationController
- request DTO: ConversationRequest
- response DTO: ConversationResponse
Engine Layer
- DefaultConversationalEngine
- EnginePipelineFactory (DAG sort + wrappers + timing + step hooks)
- EngineSession (per-turn mutable state)
Resolver Layer
- intent: classifier + agent + collision resolver
- rules: type/action factories
- response: response type + output format factories
- MCP: planner + registry + DB executor
Persistence Layer
- JPA repositories on ce_*
- ce_conversation and ce_audit write path
Transport Layer (configurable)
- SSE stream endpoint
- STOMP/WebSocket publisher
Request Path (high-level)
Controller receives request
Builds EngineContext from conversationId, message, inputParams, optional reset flag.
Engine opens session
EngineSessionFactory creates session and history provider injects last turns.
Pipeline runs ordered steps
Each step receives same session instance and may mutate intent/state/context/payload.
Persist + return result
Final payload is persisted and returned as TEXT/JSON response.
Code references
Engine entrypoint
package: com.github.salilvnair.convengine.engine.providerfile: src/main/java/com/github/salilvnair/convengine/engine/provider/DefaultConversationalEngine.java
JAVA
@Override
public EngineResult process(EngineContext engineContext) {
EngineSession session = sessionFactory.open(engineContext);
session.setConversationHistory(historyProvider.lastTurns(session.getConversationId(), 10));
EnginePipeline pipeline = pipelineFactory.create();
return pipeline.execute(session);
}
Pipeline execution loop
package: com.github.salilvnair.convengine.engine.pipelinefile: src/main/java/com/github/salilvnair/convengine/engine/pipeline/EnginePipeline.java
JAVA
for (EngineStep step : steps) {
StepResult r = step.execute(session);
if (r instanceof StepResult.Stop(EngineResult result)) {
return result;
}
}
Intervention point
You can intercept any step via EngineStepHook (beforeStep, afterStep, onStepError) without forking the engine.
DAG-ordering model
Step order is never hardcoded as a list. It is computed from annotations and validated for cycles/missing dependencies.