Validation
Overview
The Validation step cross-checks narrative claims against source data and assembles the final workflow result. This is critical for preventing hallucinations.
File: steps/validation.step.ts
What It Does
- Cross-checks narrative claims against findings
- Identifies issues (overstated claims, unsupported conclusions)
- Assembles final workflow result
- Applies output formatter if configured
Validation Rules
The agent validates claims against these rules:
| Rule | Description |
|---|---|
| Temporal Accuracy | Narrative claims must match temporal stats |
| Correlation Accuracy | Interpretations must use correct thresholds (r > 0.7 = strong) |
| Pattern Alignment | Pattern references must align with detected patterns |
| Confidence Bounds | Conclusions shouldn't exceed data confidence |
Input
{
temporal?: TemporalOutput,
pattern?: PatternOutput,
correlation?: CorrelationOutput,
explanation?: ExplanationOutput,
outputProfile?: string, // Formatter to apply
errors?: string[]
}
Output
{
validation: {
valid: boolean, // Overall validity
issues: string[] // List of identified issues
},
result: string, // Final narrative
formattedOutput?: unknown // Formatter output (if profile set)
}
Agent Payload
const payload = {
claims: [
explanationNarrative,
...keyPoints,
...correlationInsights
],
findings: {
temporal: { summary, stats, insights },
pattern: { matches, detectedPatterns, confidence },
correlation: { pairs, summary }
},
rules: [
'Verify narrative claims match temporal stats',
'Check correlation interpretations (r > 0.7 strong)',
'Ensure pattern references align with detected patterns',
'Flag conclusions too strong for available data'
]
};
Fallback Validation
Without an agent, basic validation checks:
const issues: string[] = [];
if (!inputData.objective?.trim()) {
issues.push('objective is required');
}
const hasSeries = Array.isArray(inputData.series) && inputData.series.length > 0;
const hasDatasets = Array.isArray(inputData.datasets) && inputData.datasets.length > 0;
if (!hasSeries && !hasDatasets) {
issues.push('no series or datasets provided');
}
if (inputData.errors?.length) {
issues.push(...inputData.errors.map(e => `error: ${e}`));
}
const valid = !!inputData.objective?.trim() && (hasSeries || hasDatasets);
Output Formatting
When outputProfile is set, applies formatter:
if (inputData.outputProfile && await hasFormatterAsync(inputData.outputProfile)) {
const canonical: CanonicalResult = {
taskId,
objective: inputData.objective,
temporal: assembled.temporal,
pattern: assembled.pattern,
correlation: assembled.correlation,
explanation: assembled.explanation,
validation: validationOutput,
result
};
const formatResult = await applyFormatterSafeAsync(
inputData.outputProfile,
canonical
);
if (formatResult.success) {
formattedOutput = formatResult.data;
}
}
Example Output
{
"validation": {
"valid": true,
"issues": []
},
"result": "Over the past 30 days, your stress levels have shown a notable upward trend...",
"formattedOutput": {
"summary": "...",
"insights": [...],
"recommendations": [...]
}
}
Workflow Result Storage
The step saves the complete result to DB:
await stepDataService.save({
taskId,
stepName: 'workflow-result',
stepOrder: 8,
output: {
...assembled,
result,
completedSteps
}
});
Skip Conditions
Validation is rarely skipped (critical for result integrity), but can be if:
stepPlanexists and doesn't include'validation'