Skip to main content
v1

Examples (ReactFlow + DML + Audit)

This page is the practical E2E companion to deep dive. Each example shows:

  • conversation UX
  • runtime flow graph
  • DML seed rows
  • expected audit stages

Canonical Runtime Step Set

Step loop behavior

Step loop invokes each execute(session). If a step returns Stop, response returns immediately; otherwise loop continues.

1LoadOrCreateConversationStep
2ResetConversationStep
3PersistConversationBootstrapStep
4AuditUserInputStep
5PolicyEnforcementStep
6IntentResolutionStep
7ResetResolvedIntentStep
8FallbackIntentStateStep
9AddContainerDataStep
10McpToolStep
11SchemaExtractionStep
12AutoAdvanceStep
13RulesStep
14ResponseResolutionStep
15PersistConversationStep
16PipelineEndGuardStep
Open full step traversal with snippets
ConversationController.message
file: src/main/java/com/github/salilvnair/convengine/api/controller/ConversationController.java
JAVA
@PostMapping("/message")
public ConversationResponse message(@RequestBody ConversationRequest request) {
UUID conversationId = request.getConversationId() != null
? request.getConversationId() : UUID.randomUUID();

Map<String, Object> inputParams = new LinkedHashMap<>();
if (request.getInputParams() != null) {
inputParams.putAll(request.getInputParams());
}

EngineContext engineContext = EngineContext.builder()
.conversationId(conversationId.toString())
.userText(request.getMessage())
.inputParams(inputParams)
.build();

EngineResult result = engine.process(engineContext);
return mapToResponse(result);
}
DefaultConversationalEngine.process
file: 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);
}
EnginePipeline.execute
file: src/main/java/com/github/salilvnair/convengine/engine/pipeline/EnginePipeline.java
JAVA
public EngineResult execute(EngineSession session) {
for (EngineStep step : steps) {
StepResult result = step.execute(session);
if (result instanceof StepResult.Stop(EngineResult finalResult)) {
return finalResult;
}
}
if (session.getFinalResult() == null) {
throw new ConversationEngineException(ConversationEngineErrorCode.PIPELINE_NO_FINAL_RESULT);
}
return session.getFinalResult();
}
LoadOrCreateConversationStep.execute
file: src/main/java/com/github/salilvnair/convengine/engine/steps/LoadOrCreateConversationStep.java
JAVA
public StepResult execute(EngineSession session) {
CeConversation persisted = conversationRepo.findByConversationId(session.getConversationId())
.orElseGet(() -> conversationRepo.save(createConversation(session.getConversationId())));

session.setConversation(persisted);
session.setIntent(persisted.getIntentCode());
session.setState(persisted.getStateCode());
session.mergeContextJson(persisted.getContextJson());
return new StepResult.Continue();
}
IntentResolutionStep.execute
file: src/main/java/com/github/salilvnair/convengine/engine/steps/IntentResolutionStep.java
JAVA
public StepResult execute(EngineSession session) {
if (session.isSchemaLocked() && session.hasIntent()) {
audit.audit("INTENT_SKIP_LOCKED", session.getConversationId(), Map.of("intent", session.getIntent()));
return new StepResult.Continue();
}

IntentResolutionTrace trace = resolver.resolveWithTrace(session.getUserText(), session.contextDict());
session.setIntent(trace.getIntent());
session.setState(trace.getState());

audit.audit("INTENT_RESOLVED", session.getConversationId(), Map.of(
"intent", trace.getIntent(),
"confidence", trace.getConfidence(),
"source", trace.getSource()
));
return new StepResult.Continue();
}
SchemaExtractionStep.execute
file: src/main/java/com/github/salilvnair/convengine/engine/steps/SchemaExtractionStep.java
JAVA
public StepResult execute(EngineSession session) {
Optional<CeOutputSchema> schemaOpt = outputSchemaRepo.findEnabled(session.getIntent(), session.getState());
if (schemaOpt.isEmpty()) {
return new StepResult.Continue();
}

CeOutputSchema schema = schemaOpt.get();
String prompt = promptRenderer.renderSchemaPrompt(session, schema);
String extractedJson = llmClient.generateJson(prompt, schema.getSchemaJson(), session.getContextJsonOrEmpty());

session.mergeContextJson(extractedJson);
MissingFieldResult missing = missingFieldEvaluator.evaluate(schema.getSchemaJson(), session.contextDict());
session.setMissingFields(missing.names());
session.setSchemaLocked(!missing.isComplete());

audit.audit("SCHEMA_EVALUATED", session.getConversationId(), Map.of("missing", missing.names()));
return new StepResult.Continue();
}
RulesStep.execute
file: src/main/java/com/github/salilvnair/convengine/engine/steps/RulesStep.java
JAVA
public StepResult execute(EngineSession session) {
int pass = 0;
boolean changed;
do {
changed = false;
pass++;
for (CeRule rule : ruleRepo.findEnabled(session.getIntent(), session.getState())) {
if (!ruleMatcher.matches(rule, session)) {
continue;
}
RuleOutcome outcome = actionExecutor.apply(rule, session);
changed = changed || outcome.mutatedIntentOrState();
audit.audit("RULE_MATCH", session.getConversationId(), Map.of("ruleId", rule.getId(), "action", rule.getAction()));
}
} while (changed && pass < 4);

return new StepResult.Continue();
}
ResponseResolutionStep.execute
file: src/main/java/com/github/salilvnair/convengine/engine/steps/ResponseResolutionStep.java
JAVA
public StepResult execute(EngineSession session) {
CeResponse row = responseRepo.resolve(session.getIntent(), session.getState())
.orElseThrow(() -> new ConversationEngineException(RESPONSE_NOT_FOUND));

EngineResult result = "EXACT".equalsIgnoreCase(row.getResponseType())
? exactResolver.resolve(row, session)
: derivedResolver.resolve(row, session);

session.setFinalResult(result);
audit.audit("ASSISTANT_OUTPUT", session.getConversationId(), Map.of(
"responseType", row.getResponseType(),
"outputFormat", row.getOutputFormat()
));
return new StepResult.Continue();
}
PersistConversationStep.execute
file: src/main/java/com/github/salilvnair/convengine/engine/steps/PersistConversationStep.java
JAVA
public StepResult execute(EngineSession session) {
CeConversation c = session.getConversation();
c.setIntentCode(session.getIntent());
c.setStateCode(session.getState());
c.setContextJson(session.getContextJsonOrEmpty());
c.setLastAssistantJson(session.getFinalResultJson());
conversationRepo.save(c);
return new StepResult.Continue();
}

FAQ

FAQ
User
Can I move my connection within Zapper?
ConvEngine
Yes. You can request relocation from the support portal.

FAQ runtime flow

Classifier-first deterministic path

React Flow mini map

DISCONNECT_ELECTRICITY

DISCONNECT_ELECTRICITY
User
Disconnect electricity for account 10004567.
ConvEngine
Please share disconnect date and reason.
User
Date is 2026-03-31, reason moving out.
ConvEngine
Done. Request submitted.

Disconnect flow

Incomplete schema -> follow-up -> complete -> finalize

React Flow mini map

LOG_ANALYSIS

LOG_ANALYSIS
User
Analyze timeout logs and tell root cause.
ConvEngine
Root cause is DB connection pool saturation from 11:02 to 11:07 UTC.

Log analysis flow

GET_SCHEMA_JSON rule and DERIVED output

React Flow mini map

REQUEST_TRACKER

REQUEST_TRACKER
User
Track request REQ-778102.
ConvEngine
REQ-778102 is IN_REVIEW and assigned to Queue-3.

Tracker flow

SET_TASK driven state path

React Flow mini map
Fast validation

For each example run, inspect both /api/v1/conversation/audit/{conversationId} and /api/v1/conversation/audit/{conversationId}/trace.