Extension Points and Intervention Scenarios
EngineStepHook - intervene between any steps
Real scenario: For LOG_ANALYSIS, inject additional retrieval hint before schema extraction.
@Component
public class LogAnalysisHintHook implements EngineStepHook {
@Override
public boolean supports(EngineStep.Name stepName, EngineSession session) {
return EngineStep.Name.SchemaExtractionStep == stepName
&& "LOG_ANALYSIS".equalsIgnoreCase(session.getIntent());
}
@Override
public void beforeStep(EngineStep.Name stepName, EngineSession session) {
session.putInputParam("log_source_priority", "APM_FIRST");
}
}
Use hooks when you need low-friction runtime intervention without forking framework steps.
ContainerDataTransformer - reshape container response
Real scenario: CCF returns nested payload; you flatten to schema-friendly map.
@Component
@ContainerDataTransformer(intent = "REQUEST_TRACKER", state = "IDLE")
public class RequestTrackerContainerTransformer implements ContainerDataTransformerHandler {
@Override
public Map<String, Object> transform(ContainerComponentResponse response,
EngineSession session,
Map<String, Object> inputParams) {
Map<String, Object> out = new LinkedHashMap<>();
out.put("ticket_id", inputParams.get("ticketId"));
out.put("status", "IN_REVIEW");
return out;
}
}
ResponseTransformer - post-process final payload
Real scenario: Add support-team escalation footer for high-severity disconnect failures.
@Component
@ResponseTransformer(intent = "DISCONNECT_ELECTRICITY", state = "FAILED")
public class DisconnectFailureResponseTransformer implements ResponseTransformerHandler {
@Override
public OutputPayload transform(OutputPayload responsePayload,
EngineSession session,
Map<String, Object> inputParams) {
if (responsePayload instanceof TextPayload(String text)) {
return new TextPayload(text + "\nIf this is urgent, call support at +1-800-000-0000.");
}
return responsePayload;
}
}
ContainerDataInterceptor - intercept request/response around CCF
Real scenario: add tenant metadata and redact a field before persistence.
Before Execute:
- inject tenantId/requestId
- attach observability headers
After Execute:
- redact sensitive node from raw container payload
- enrich session input params for downstream rule checks
ConvEngineVerboseAdapter - publish UI verbose messages from consumer code
Use this when hooks, transformers, or custom Spring beans need to emit progress/error messages to the UI without embedding transport code.
@Component
@RequiredArgsConstructor
public class LoanConfirmationHook implements EngineStepHook {
private final ConvEngineVerboseAdapter verboseAdapter;
@Override
public void beforeStep(EngineStep.Name stepName, EngineSession session) {
verboseAdapter.publish(session, this, "PRECHECK_STARTED");
verboseAdapter.publishText(
session,
this,
"PRECHECK_NOTE",
"Reviewing customer [[${context.customerId}]] in state [[${state}]]."
);
}
}
What it gives you:
publish(session, this, "TOKEN"): DB-backedce_verboselookup usingthis.getClass().getSimpleName()publish(session, this, "TOKEN", metadata): same, with extra metadata for resolver matching and renderingpublishError(...): same path, error branchpublishText(...): bypassesce_verboserow lookup and sends direct text, still rendered through Thymeleaf
Recommended pattern:
- use
publish(...)when the wording should be controlled byce_verbose - use
publishText(...)when the wording should come directly from consumer Java
Rule Action Playbook (SET_TASK, SET_JSON, GET_CONTEXT, GET_SESSION, SET_INPUT_PARAM, SET_DIALOGUE_ACT)
These actions execute inside RulesStep.execute(...) and mutate the live EngineSession.
Set ce_rule.phase based on where you want the action to run:
- POST_DIALOGUE_ACT: immediately after DialogueActStep, before InteractionPolicyStep.
- PRE_RESPONSE_RESOLUTION: normal RulesStep pass.
- POST_AGENT_INTENT: post-intent pass inside AgentIntentResolver.
Action value format (exact runtime behavior)
| Action | action_value format | Engine behavior |
|---|---|---|
| SET_TASK | beanName:methodName or beanName:methodA,methodB | Invokes Spring bean methods via CeRuleTaskExecutor |
| SET_JSON | targetKey:jsonPath | Extracts JSONPath from session eject and stores into inputParams[targetKey] |
| GET_CONTEXT | targetKey (optional) | Stores session.contextDict() into inputParams[targetKey] (default key=context) |
| GET_SESSION | targetKey (optional) | Stores session.sessionDict() into inputParams[targetKey] (default key=session) |
| SET_INPUT_PARAM | {"key":value,...} | Writes literal runtime flags directly into inputParams |
| SET_DIALOGUE_ACT | "EDIT" or {"dialogueAct":"EDIT",...} | Overrides final dialogue_act / confidence / source and can sync standalone_query |
SET_TASK - execute consumer Java methods from rule
Use this when a rule match must trigger consumer-side business logic (incident raise, tracker lookup, eligibility fetch, etc).
Create ce_rule row
Set ce_rule.action to SET_TASK and provide bean/method mapping in action_value.
INSERT INTO ce_rule
(phase, intent_code, state_code, rule_type, match_pattern, action, action_value, priority, enabled, description)
VALUES
('PRE_RESPONSE_RESOLUTION', 'REQUEST_TRACKER', 'ANY', 'REGEX', '(?i).*track.*request.*', 'SET_TASK', 'requestTrackerTask:loadStatus,attachEta', 10, true,
'Load tracker status + ETA from consumer service');
Implement task bean
Bean must be a Spring bean with the configured bean name and implement CeRuleTask. Methods are invoked with (EngineSession session, CeRule rule).
@Component("requestTrackerTask")
public class RequestTrackerTask implements CeRuleTask {
public void loadStatus(EngineSession session, CeRule rule) {
String requestId = String.valueOf(session.getInputParams().getOrDefault("requestId", ""));
// fetch from your DB/service
session.putInputParam("requestStatus", "APPROVAL_PENDING");
session.putInputParam("lastUpdated", "2026-02-10T11:40:00Z");
}
public void attachEta(EngineSession session, CeRule rule) {
session.putInputParam("eta_hours", 12);
}
}
Use in response generation
Downstream prompt/response can read new context/inputParams (for example requestStatus, lastUpdated, eta_hours).
SET_JSON - move JSONPath value into input params
Use this to extract one value from runtime session JSON into a flat prompt var.
INSERT INTO ce_rule
(phase, intent_code, state_code, rule_type, match_pattern, action, action_value, priority, enabled, description)
VALUES
('PRE_RESPONSE_RESOLUTION', 'LOG_ANALYSIS', 'ANY', 'JSON_PATH', '$.schemaJson.errorCode != null', 'SET_JSON', 'error_code:$.schemaJson.errorCode', 20, true,
'Expose extracted errorCode as prompt var');
SET_JSON writes to session.putInputParam("error_code", value).
It does not change session.contextJson unless your subsequent task/hook updates context explicitly.
GET_CONTEXT - snapshot context into input params
Use this when prompt templates or tasks need full context as a single variable.
INSERT INTO ce_rule
(phase, intent_code, state_code, rule_type, match_pattern, action, action_value, priority, enabled, description)
VALUES
('PRE_RESPONSE_RESOLUTION', 'REQUEST_TRACKER', 'ANY', 'REGEX', '(?i).*status.*', 'GET_CONTEXT', 'ctx_snapshot', 30, true,
'Expose full context to prompt/task layer');
If action_value is blank, engine uses default key context.
GET_SESSION - snapshot session facts into input params
Use this for advanced derived responses that need runtime flags (schemaComplete, intentLocked, missingRequiredFields, etc).
INSERT INTO ce_rule
(phase, intent_code, state_code, rule_type, match_pattern, action, action_value, priority, enabled, description)
VALUES
('PRE_RESPONSE_RESOLUTION', 'DISCONNECT_ELECTRICITY', 'ANY', 'JSON_PATH', '$.schemaComplete == false', 'GET_SESSION', 'session_snapshot', 40, true,
'Expose full runtime session facts for follow-up prompt decisions');
If action_value is blank, engine uses default key session.
SET_INPUT_PARAM - write runtime flags directly
Use this when you need lightweight runtime variables for later rules/steps without creating a custom task bean.
INSERT INTO ce_rule
(phase, intent_code, state_code, rule_type, match_pattern, action, action_value, priority, enabled, description)
VALUES
('POST_SCHEMA_EXTRACTION', 'LOAN_APPLICATION', 'ELIGIBILITY_GATE', 'JSON_PATH', '$[?(@.schemaComplete == true)]',
'SET_INPUT_PARAM', '{"awaiting_confirmation":true,"confirmation_key":"LOAN_APPLICATION_CONFIRM"}', 11, true,
'Mark confirmation flags after schema becomes complete');
Typical uses:
awaiting_confirmationconfirmation_keyskip_schema_extractionrouting_decisioncorrection_applied
This is the clean way to make later ce_rule phases react to:
- confirmation answers (
AFFIRM/NEGATE) - correction routing
- post-schema, pre-MCP gating
SET_DIALOGUE_ACT - override final dialogue-act after classification
Use this when DialogueActStep correctly preserves an LLM candidate (for example dialogue_act_llm_candidate=EDIT) but the final guarded result should be overridden before InteractionPolicyStep.
This is especially useful in POST_DIALOGUE_ACT.
INSERT INTO ce_rule
(phase, intent_code, state_code, rule_type, match_pattern, action, action_value, priority, enabled, description)
VALUES
('POST_DIALOGUE_ACT', 'LOAN_APPLICATION', 'ANY', 'JSON_PATH',
'$[?(@.inputParams.dialogue_act == ''NEW_REQUEST'' && @.inputParams.dialogue_act_source == ''REGEX_GUARD'' && @.inputParams.dialogue_act_llm_candidate == ''EDIT'')]',
'SET_DIALOGUE_ACT', '{"dialogueAct":"EDIT","source":"POST_DIALOGUE_ACT_RULE"}', 5, true,
'Promote guarded LLM EDIT back to EDIT before interaction policy');
Accepted action_value formats:
- simple string:
EDIT - structured JSON:
{"dialogueAct":"EDIT","confidence":0.99,"source":"POST_DIALOGUE_ACT_RULE","standaloneQuery":"..."}
SET_DIALOGUE_ACT updates:
dialogue_actdialogue_act_confidencedialogue_act_source- optional
standalone_query(which also refreshesresolved_user_input)
SET_TASK methods run during rule execution. Keep methods deterministic and idempotent.
For side-effecting calls (ticket creation, webhook dispatch), guard with strict rule conditions and add idempotency keys from conversationId.
Custom action resolver (RuleActionResolver)
Consumer can define a brand-new rule action without changing framework core.
Create custom resolver bean
Implement RuleActionResolver and return your action name from action().
@Component
public class EnrichCustomerTierActionResolver implements RuleActionResolver {
@Override
public String action() {
return "ENRICH_TIER";
}
@Override
public void resolve(EngineSession session, CeRule rule) {
// action_value example: customerTier:PREMIUM
String raw = rule.getActionValue() == null ? "" : rule.getActionValue();
String[] parts = raw.split(":", 2);
String key = parts.length > 0 && !parts[0].isBlank() ? parts[0] : "customerTier";
String value = parts.length > 1 ? parts[1] : "STANDARD";
session.putInputParam(key, value);
}
}
Use action in ce_rule
Set ce_rule.action to your custom token (case-insensitive lookup), for example ENRICH_TIER.
INSERT INTO ce_rule
(phase, intent_code, rule_type, match_pattern, action, action_value, priority, enabled, description)
VALUES
('PRE_RESPONSE_RESOLUTION', 'REQUEST_TRACKER', 'REGEX', '(?i).*vip.*', 'ENRICH_TIER', 'customerTier:PREMIUM', 5, true,
'Mark VIP tier for downstream response logic');
No manual factory config is needed. RuleActionResolverFactory auto-registration behavior auto-discovers all Spring beans implementing RuleActionResolver and maps by action().toUpperCase().