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
Rule Action Playbook (SET_TASK, SET_JSON, GET_CONTEXT, GET_SESSION)
These actions execute inside RulesStep.execute(...) and mutate the live EngineSession.
Set ce_rule.phase based on where you want the action to run:
- PIPELINE_RULES: normal RulesStep pass.
- AGENT_POST_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_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
('PIPELINE_RULES', '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
('PIPELINE_RULES', '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
('PIPELINE_RULES', '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
('PIPELINE_RULES', '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_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
('PIPELINE_RULES', '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().