Validation Loop Pattern
Purpose
Ensure quality by verifying results and retrying if they don’t meet criteria. Prevents low-quality outputs from proceeding.
Structure
[action] → [check] → success → [next] ↓ failure → [fix] → [increment-iteration] → [action]Implementation
Action Node
{ "type": "agent-directive", "id": "do-work", "directive": "Complete the task. Iteration: {{current_iteration}}", "completionCondition": "Task completed with quality standards met", "inputSchema": { "type": "object", "properties": { "result": { "type": "string" }, "quality_check_passed": { "type": "string", "enum": ["yes", "no"] } }, "required": ["result", "quality_check_passed"] }, "connections": { "success": "check-quality" }}Check Node
{ "type": "condition", "id": "check-quality", "condition": { "operator": "eq", "left": { "contextPath": "quality_check_passed" }, "right": "yes" }, "connections": { "true": "next-step", "false": "fix-issues" }}Fix Node
{ "type": "agent-directive", "id": "fix-issues", "directive": "Fix issues found in iteration {{current_iteration}}. Previous result: {{result}}", "connections": { "success": "increment-iteration" }}Iteration Counter
Using expression node:
{ "type": "expression", "id": "increment-iteration", "expressions": ["current_iteration = current_iteration + 1"], "connections": { "default": "do-work" }}Always include iteration counters to prevent infinite loops. Consider adding a max iteration check.
With Max Iterations
Add check before retry:
{ "type": "condition", "id": "check-max-iterations", "condition": { "operator": "lt", "left": { "contextPath": "current_iteration" }, "right": 5 }, "connections": { "true": "do-work", "false": "escalate-to-user" }}Bounded Re-Validation Loop
A re-validation or re-review loop revisits the SAME validate node on each round. Without a cue in the loop-entry directive, the agent can misread seeing the same node twice as the flow being stuck and report a loop to the user. Two requirements prevent this.
Counter is an Expression Node
The round counter MUST be incremented by an expression node (flow-automatic), declared in variableRegistry with a numeric default. An agent-incremented counter is the anti-pattern — the agent is not asked to do arithmetic, and a missing or non-numeric value breaks the bound check.
{ "variableRegistry": { "validation_round": { "type": "number", "description": "Re-validation pass counter", "default": 0 }, "max_validation_rounds": { "type": "number", "description": "Re-validation bound", "default": 5 } }}{ "type": "expression", "id": "increment-validation-round", "expressions": ["validation_round = validation_round + 1"], "connections": { "default": "re-validate" }}Loop-Entry Cue
The loop-entry directive renders the round counter and states that the repetition is expected:
{ "type": "agent-directive", "id": "re-validate", "directive": "Re-validation pass {{validation_round}} of {{max_validation_rounds}} — a normal quality loop, expected to converge. This is NOT a bug and NOT a stuck flow; do not report a loop to the user.\n\nRe-check the work against the criteria and report whether all issues are resolved.", "completionCondition": "Work re-checked against criteria", "inputSchema": { "type": "object", "properties": { "all_resolved": { "type": "string", "enum": ["yes", "no"] } }, "required": ["all_resolved"] }, "connections": { "success": "check-resolved" }}The cue text is exact. Re-validation passing through the same node is expected, not a bug — the directive must say so, or the agent may stop and report a false loop.
Numeric Quality Check
For a property a machine can decide, route on the machine’s own answer — a count of reproducible findings, an exit status, a coverage gap:
{ "inputSchema": { "properties": { "issues_count": { "type": "number", "minimum": 0 } }, "required": ["issues_count"] }}{ "condition": { "operator": "eq", "left": { "contextPath": "issues_count" }, "right": 0 }}Do not route on a quality score. A number a reviewer assigns to coherence, depth or architecture does not distinguish two states of the result — seven against eight says nothing a reader could verify — and the threshold is met by adjusting the number. Where the property is qualitative, the gate is the reviewer’s reasoned verdict and the count of findings that verdict produced. See Fixed Semantic Quality Score.
Real Example
From development-flow.json:
{ "id": "verify-step-implementation", "directive": "Verify step {{current_step_name}} implementation:\n- Expected: {{expected_outcome}}\n- Check actual matches expected", "inputSchema": { "properties": { "step_verified": { "type": "string", "enum": ["yes", "no"] }, "verification_evidence": { "type": "string" } }, "required": ["step_verified", "verification_evidence"] }}Agent Behavior at Validation Gates
When a workflow includes validation gates (nodes that ask user for approval), the agent must follow strict rules:
Validation Gate Directive Pattern
A gate directive says what its answer records and where each answer leads, so the agent can see that answering honestly is the only way the run proceeds correctly:
`approval` records the user's own answer. Anything other than an unqualified yes is a no, and thefeedback belongs in `user_feedback` — the flow routes a no to the responsibility that owns therepair, so a correction made here instead is a change nobody reviewed.Why This Matters
A gate exists to move a decision to whoever owns it. An agent that reads the answer as “minor
corrections I can apply myself” delivers work the flow never routed through its repair and review
path, and an approval = "yes" recorded over feedback loses the feedback entirely. Naming the
consequence — where a no goes, what a yes closes — is what makes the honest answer the useful one.
Example Validation Gate
{ "type": "agent-directive", "id": "approve-plan", "directive": "Present the plan and ask whether it is approved.\n\nPlan: {{plan_summary}}\n\n`approval` records the user's own answer: anything other than an unqualified yes is a no, with the exact words in `user_feedback`. A no routes to the responsibility that owns the revision, so a correction made here instead is a change nobody reviewed.", "completionCondition": "User confirmed or rejected plan", "inputSchema": { "type": "object", "properties": { "plan_approved": { "type": "string", "enum": ["yes", "no"] }, "user_feedback": { "type": "string" } }, "required": ["plan_approved"] }, "connections": { "success": "route-plan-approval" }}Related Patterns
- Step Verification - Verify specific steps
- Escalation - Handle repeated failures