Validation and Invariants
The Quality module provides multi-agent parallel validation (`ParallelValidationExecutor`), runtime invariant checking (`InvariantChecker`), conditional rule evaluation (`RuleEngine`), and input validation (`ValidationService`).
ParallelValidationExecutor
The ParallelValidationExecutor sends the same prompt to multiple agents (or LLM providers) simultaneously and compares their responses. This is useful for high-stakes tasks where you want cross-validation: if multiple independent models agree on an answer, you can be more confident it is correct.
ParallelValidationExecutor executor = ParallelValidationExecutor.builder()
.addAgent(claudeAgent)
.addAgent(geminiAgent)
.addAgent(ollamaAgent)
.aggregationStrategy(AggregationStrategy.CONSENSUS)
.timeout(Duration.ofSeconds(60))
.synthesisLLM(claudeClient) // Required for CONSENSUS and ENSEMBLE
.similarityThreshold(0.5) // Jaccard similarity for response comparison
.build();
ValidationResult result = executor.execute("Review this code for security issues");
result.getBestSolution(); // Best response based on strategy
result.getConsensusPoints(); // Points all agents agree on
result.getDivergences(); // Points where agents disagree
result.getMetrics(); // Execution metrics (timing, agreement ratio)
result.getResponses(); // Individual agent responses with confidence scores
// Async execution
CompletableFuture<ValidationResult> future = executor.executeAsync(prompt);You can also add raw LLMClient instances instead of full agents:
ParallelValidationExecutor executor = ParallelValidationExecutor.builder()
.addLLM(openaiClient)
.addLLM(anthropicClient)
.aggregationStrategy(AggregationStrategy.BEST_OF)
.build();AggregationStrategy Enum
After collecting responses from all agents, the executor combines them using one of four strategies. Choose based on your use case -- voting for simple decisions, consensus for nuanced analysis.
| Strategy | Description | Requires Synthesis LLM | Best For |
|---|---|---|---|
VOTING | Selects the most common response (Jaccard similarity) | No | Classification, yes/no decisions |
BEST_OF | Selects the response with highest confidence score | No | Quality-critical tasks |
CONSENSUS | Extracts and combines common points from all responses | Yes | Code review, fact extraction |
ENSEMBLE | Combines all unique insights from every response | Yes | Brainstorming, comprehensive analysis |
Auto-select a strategy based on task type:
AggregationStrategy strategy = AggregationStrategy.forTaskType("code review");
// Returns CONSENSUS
AggregationStrategy strategy = AggregationStrategy.forTaskType("critical security audit");
// Returns BEST_OFConfidence Estimation
Each agent response receives an automatically estimated confidence score (0.0-1.0). The score is based on heuristics about the response content, not a self-reported confidence from the LLM.
- Base: 0.5
- Length bonus: +0.1 for \>500 chars, +0.1 for \>1000 chars
- Uncertainty penalty: -0.1 for phrases like "I think", "possibly"
- Certainty bonus: +0.1 for "definitely", "certainly"
- Structure bonus: +0.1 for bulleted/numbered lists
InvariantChecker
The InvariantChecker enforces correctness rules on your agents at runtime. You define preconditions, postconditions, and invariants using annotations on your role classes, and the checker verifies them before and after each action. This catches bugs early by ensuring your agent's state remains valid throughout execution.
Supported Annotations
Invariants are defined through these annotations on your action methods and state fields.
@ActionSpec(precondition = "health > 0")-- checked before action execution@ActionSpec(postcondition = "state == COMPLETED")-- checked after execution@ActionSpec(invariants = {"energy >= 0", "DEAD if health <= 0"})-- checked before and after@State(invariants = {"value >= 0"})-- checked after any state change
Usage
You can check invariants manually around action execution, or use the batch convenience methods that handle all checks in a single call.
InvariantChecker checker = new InvariantChecker(roleObject);
// Before action
Method method = role.getClass().getMethod("attack");
checker.checkPrecondition(method);
checker.checkActionInvariants(method);
// Execute the action...
// After action
checker.checkPostcondition(method);
checker.checkActionInvariants(method);
checker.checkStateInvariants();
// Or use batch methods
checker.checkBeforeAction(method); // precondition + invariants
checker.checkAfterAction(method); // postcondition + invariants + state invariants
// Non-strict mode (logs warnings instead of throwing)
InvariantChecker lenient = new InvariantChecker(roleObject, false);
// Collect all violations without throwing
List<String> violations = checker.collectViolations(method);
// Static convenience methods
InvariantChecker.check(roleObject, "attack"); // check before
InvariantChecker.checkAfter(roleObject, "attack"); // check afterExpression Support
The invariant parser understands comparison operators, boolean logic, and conditional rules. These are written as plain strings in annotation values.
- Comparisons:
health > 0,state == ALIVE,energy != 0 - Boolean operators:
health > 0 && energy > 0,state == DEAD || health <= 0 - Conditional rules:
DEAD if neighbors < 2 (underpopulation)-- see RuleEngine below
Natural language invariants (more than 3 words with no operators) are silently skipped.
RuleEngine
The RuleEngine evaluates conditional state transition rules at runtime. It is useful for game-like scenarios or state machines where you want to verify that an object's state matches what the rules predict. For example, in Conway's Game of Life, you can define rules like "a cell should be DEAD if it has fewer than 2 neighbors" and check whether the actual state matches.
RuleEngine engine = new RuleEngine(cellRole);
engine.setStateField("state"); // Which field to check effects against
engine.addRule("DEAD if neighbors < 2 (underpopulation)");
engine.addRule("ALIVE if neighbors == 3 (reproduction)");
engine.addRule("unchanged if neighbors == 2 (survival)");
// Evaluate all rules
List<RuleEngine.RuleViolation> violations = engine.evaluateAllRules();
for (RuleEngine.RuleViolation v : violations) {
System.out.printf("Expected %s=%s when %s, but actual=%s%n",
"state", v.getExpectedValue(), v.getRule().getCondition(), v.getActualValue());
}Rule Evaluation Semantics
Each rule follows the format "EXPECTED_STATE if CONDITION". Here is how the engine evaluates a rule.
- Evaluate the condition:
neighbors < 2 - If condition is false, the rule does not apply -- skip
- If condition is true, the expected effect is
DEAD - Read the actual value of the state field via reflection
- If actual value does not match
DEAD, report a violation
Rules with unchanged as the effect are documentation-only and never produce violations.
Static Factory Methods
For quick setup, you can create a rule engine and populate it from an array of invariant strings in one call. The enforce method additionally throws if any violations are found, making it convenient for test assertions.
// Create and populate from an array of invariant strings
RuleEngine engine = RuleEngine.from(targetObject, new String[]{
"DEAD if health <= 0",
"ALIVE if health > 0"
});
// Evaluate and throw if any violations
RuleEngine.enforce(targetObject, invariants, "state");
// Throws RuleViolationException with all violationsValidationService
See the Security documentation for the full ValidationService reference, including:
maxStringLength/maxNestingDepth/maxParameterCountconfiguration- SQL injection, command injection, path traversal, and XSS pattern detection
blockDangerousPatternstogglesanitize()for HTML entity escaping and null byte removalvalidateOrThrow()for fail-fast validation
Security
The Quality module provides a layered security framework: annotation-driven access control and encryption (`SecurityEnforcer`), content moderation (`PatternBasedModerator`), prompt injection detection (`PromptInjectionDetector`), sandboxed execution, audit logging, and input validation (`ValidationService`).
RAG Pipeline
The server provides a per-session Retrieval-Augmented Generation pipeline that indexes local codebases, chunks source files by language boundaries, and retrieves relevant context using hybrid BM25 + vector search with Reciprocal Rank Fusion.