Skip to main content

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

  1. Cross-checks narrative claims against findings
  2. Identifies issues (overstated claims, unsupported conclusions)
  3. Assembles final workflow result
  4. Applies output formatter if configured

Validation Rules

The agent validates claims against these rules:

RuleDescription
Temporal AccuracyNarrative claims must match temporal stats
Correlation AccuracyInterpretations must use correct thresholds (r > 0.7 = strong)
Pattern AlignmentPattern references must align with detected patterns
Confidence BoundsConclusions 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:

  • stepPlan exists and doesn't include 'validation'