Skip to main content
v2

Extension Points and Intervention Scenarios

EngineStepHook - intervene between any steps

Real scenario: For LOG_ANALYSIS, inject additional retrieval hint before schema extraction.

EngineStepHook example
package: com.zapper.convengine.hooks
JAVA
@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");
}
}
Where to use this

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.

ContainerDataTransformer example
package: com.zapper.convengine.transformers
JAVA
@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.

ResponseTransformer example
package: com.zapper.convengine.transformers
JAVA
@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.

Interceptor scenario (concept)
TEXT
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.

Verbose adapter example
package: com.zapper.convengine.hooks
JAVA
@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-backed ce_verbose lookup using this.getClass().getSimpleName()
  • publish(session, this, "TOKEN", metadata): same, with extra metadata for resolver matching and rendering
  • publishError(...): same path, error branch
  • publishText(...): bypasses ce_verbose row lookup and sends direct text, still rendered through Thymeleaf

Recommended pattern:

  • use publish(...) when the wording should be controlled by ce_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)

Actionaction_value formatEngine behavior
SET_TASKbeanName:methodName or beanName:methodA,methodBInvokes Spring bean methods via CeRuleTaskExecutor
SET_JSONtargetKey:jsonPathExtracts JSONPath from session eject and stores into inputParams[targetKey]
GET_CONTEXTtargetKey (optional)Stores session.contextDict() into inputParams[targetKey] (default key=context)
GET_SESSIONtargetKey (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).

1

Create ce_rule row

Set ce_rule.action to SET_TASK and provide bean/method mapping in action_value.

SET_TASK rule example (SQL)
SQL
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');
2

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).

Consumer task bean (Java)
package: com.zapper.convengine.tasksfile: src/main/java/com/acme/convengine/tasks/RequestTrackerTask.java
JAVA
@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);
}
}
3

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.

SET_JSON rule example (SQL)
SQL
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');
What exactly gets updated

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.

GET_CONTEXT rule example (SQL)
SQL
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).

GET_SESSION rule example (SQL)
SQL
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.

SET_INPUT_PARAM rule example (SQL)
SQL
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_confirmation
  • confirmation_key
  • skip_schema_extraction
  • routing_decision
  • correction_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.

SET_DIALOGUE_ACT rule example (SQL)
SQL
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_act
  • dialogue_act_confidence
  • dialogue_act_source
  • optional standalone_query (which also refreshes resolved_user_input)
Operational guardrails

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.

Where to trace this in code
Resolvers
engine/rule/type/provider/SetTaskActionResolver.javaengine/rule/type/provider/SetJsonActionResolver.javaengine/rule/type/provider/GetContextActionResolver.javaengine/rule/type/provider/GetSessionActionResolver.javaengine/rule/type/provider/SetDialogueActActionResolver.java
Task invocation
engine/rule/task/CeRuleTaskExecutor.java
Execution loop
engine/steps/RulesStep.java

Custom action resolver (RuleActionResolver)

Consumer can define a brand-new rule action without changing framework core.

1

Create custom resolver bean

Implement RuleActionResolver and return your action name from action().

Custom RuleActionResolver example
package: com.zapper.convengine.rulesfile: src/main/java/com/acme/convengine/rules/EnrichCustomerTierActionResolver.java
JAVA
@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);
}
}
2

Use action in ce_rule

Set ce_rule.action to your custom token (case-insensitive lookup), for example ENRICH_TIER.

ce_rule row for custom action
SQL
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');
Auto-registration behavior

No manual factory config is needed. RuleActionResolverFactory auto-registration behavior auto-discovers all Spring beans implementing RuleActionResolver and maps by action().toUpperCase().