Playbooks & workflows~8 min read

How to add fallback logic

A playbook that only has a happy path isn't finished. It's a demo. Real traffic produces malformed extractions, ambiguous classifications, and responses that fail validation for reasons a first draft never anticipated. Fallback logic is what turns "this playbook works on the examples I tried" into "this playbook handles the inputs I didn't anticipate, gracefully."

This tutorial covers the three main fallback patterns available in ValGuard playbooks: routing a failed step to an alternate path, building a bounded repair loop that gives the model another attempt with explicit feedback, and escalating to a human when neither of those is appropriate. By the end, you'll know:

  • How to identify which failures in your playbook actually need a fallback path, versus which are fine to leave as a hard block
  • How to add an alternate branch triggered by validation failure
  • How to build a bounded repair loop that retries a step with feedback about what was missing
  • How reask (agent-level retry) and repair loops (playbook-level retry) differ, and when to use each
  • How to test every fallback path before trusting it in production
UI note: In current UI you may see both labels: Playbooks and Orchestration (same section, depending on plan/version). Agent creation is in Dashboard -> Agents -> Create agent. If a guide mentions Validation logs, open Dashboard -> Logs and apply filters.
  1. 1How It Works

    Fallback logic in a playbook is built from the same primitives as any other routing: a step's validators run, and a route out of that step decides what happens next based on the result. What makes it "fallback" rather than just "the next step" is the intent. You're deliberately routing a failure case somewhere productive, rather than letting it fall through to a hard block with no recourse.

    Three patterns cover most real cases:

    Alternate branch. The failing step routes (via on_validation_block) to a different next step than the success path does, commonly a stricter, more conservative agent, or a human-handoff step. This is the simplest fallback: one extra route, one extra destination.

    Repair loop. The failing step routes back to itself (or an earlier step) with feedback about what went wrong, bounded by a max_visits limit so the loop can't run forever. The on_rule_failed route condition is what makes this possible: routing specifically on which named rule failed, not just "validation failed" in general, so the retry can be given targeted feedback ("the required_fields check failed, you're missing total_amount") rather than a generic "try again."

    Human handoff. The failing step routes to a terminal (or near-terminal) step that hands the case to a person. It's often paired with a human_handoff_json_agent-style structured output so the handoff carries a clean, structured summary of what needs review rather than raw model text.

    The key distinction between a repair loop and agent-level reask (covered in its own tutorial) is what retries. reask retries the exact same step with the exact same input, hoping the model self-corrects on a second attempt. A repair loop retries with additional, targeted feedback about what specifically failed. It's a more powerful tool for failures where the model didn't have enough information the first time, not just cases where it made an incidental mistake.

  2. 2Prerequisites

    • A working playbook with at least one step whose validators can plausibly fail (see the first-playbook and chain-agents tutorials if you don't have one yet)
    • Familiarity with route condition types (on_validation_pass, on_validation_block, on_rule_failed) from earlier tutorials
    • A view of your validation logs for the target playbook, if it's already live (real failure patterns are a much better guide for fallback design than guessing)
  3. 3Step-by-Step Setup

    Step 1: Identify real failure points from validation logs

    If the playbook is already live, review Dashboard → Validation logs filtered to that playbook and look at which steps actually fail, and why. If it's not live yet, list the failure modes you expect each step's validators to catch, then use Simulate to declare a Block outcome for each one and confirm the graph's fallback routes actually exist and go where you intend. Simulate previews routing, not whether your validators will genuinely catch those inputs, so plan on confirming the real failure modes with Live once a first version is published.

    Validation logs filtered by playbook, grouped by step_key and validator_name, showing failure counts

    Step 2: Decide which failures deserve which fallback pattern

    For each recurring failure: is it something a retry with feedback could plausibly fix (repair loop), something that needs a fundamentally different approach (alternate branch), or something that genuinely needs a human's judgment (handoff)? Not every failure needs an elaborate fallback. Some are fine left as a hard block, especially safety/compliance failures.

    Step 3: Add an alternate branch for the simplest case

    In the Storyboard or Map editor, add a new step (a stricter agent, or a handoff step) and a route from the failing step to it with condition_type: on_validation_block.

    Route editor adding a new route from 'extract' to 'review_handoff' with condition_type 'on_validation_block'

    Step 4: Build a repair loop for a structurally correctable failure

    For a failure like a missing required field, plausibly fixable by giving the model another shot with explicit feedback, add a route back to the same step (or a dedicated repair variant of it) with condition_type: on_rule_failed scoped to the specific rule name.

    Route editor with condition_type 'on_rule_failed' and a rule name field set to 'required_fields'

    Step 5: Set a max_visits bound on the repair loop

    Every repair loop needs a hard limit, or a persistently failing input loops forever. Set max_visits (commonly 2–3) on the repair step.

    Step settings panel showing 'Max visits' field set to 3

    Step 6: Pass failure feedback into the retry

    If your template supports it, include the specific validator failure reason in the retry step's context (via custom_template input mode) so the model gets targeted feedback ("you're missing total_amount") rather than just being asked the same question again with no new information.

    Step 7: Add the exhausted-repair-loop exit route

    Once max_visits is reached, the loop needs somewhere to go rather than dead-ending. Add a final route from the repair step to a human-handoff terminal, conditioned on having exhausted the retry budget.

    Map view showing the repair loop with a clear exit arrow to 'review_handoff' once max_visits is reached

    Step 8: Configure validator-level on_fail as a complementary layer

    Independent of playbook-level fallback routing, individual rules within a step can still use reask for structurally correctable single-attempt retries. The two mechanisms aren't mutually exclusive, and a step can use agent-level reask for minor self-correction while the playbook's repair loop handles the case where even that isn't enough.

    Step 9: Test every fallback path's routing in Simulate, then confirm with Live

    For each fallback pattern you've added, declare the outcome that should trigger it (typically block, repeated across attempts for a repair loop) and confirm the graph takes the path you expect, including confirming the repair loop actually exits after max_visits rather than looping indefinitely. This proves the routing; run at least one real Live request that you know should fail to confirm the actual validator produces that block in the first place.

    Simulate timeline showing a declared-block outcome repeated twice on the repair step before routing to review_handoff

    Step 10: Publish and monitor fallback-path frequency

    After going live, watch how often each fallback path actually triggers in Dashboard → Analytics → Orchestration. A repair loop that never succeeds on retry, or an alternate branch that fires constantly, is a signal to revisit the underlying agent or prompt, not just the fallback logic itself.

  4. 4Diagram / Flow

  5. 5Configuration Examples

    Repair loop route configuration:

    steps:
      - step_key: extract
        node_type: agent
        agent_slug: invoice-extract
        input_mode: pass_through
        max_visits: 3
      - step_key: review_handoff
        node_type: agent
        agent_slug: human-handoff-summary
        input_mode: append_previous
    routes:
      - from_step_key: extract
        to_step_key: extract
        condition_type: on_rule_failed
        rule_name: required_fields
        priority: 10
        is_fallback: false
      - from_step_key: extract
        to_step_key: review_handoff
        condition_type: on_validation_block
        priority: 20
        is_fallback: true
    ```
    
    **Example curl call with a message likely to trigger the repair loop:**
    
    ```bash
    curl -si "$VG_PROXY/v1/chat/completions" \
      -H "Authorization: Bearer $VG_API_KEY" \
      -H "X-VG-Flow: extract-repair-loop" \
      -H "Content-Type: application/json" \
      -d '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"Extract invoice fields from this partial scan: vendor Acme, rest illegible"}]}'
    ```
    
    ```
    X-VG-Orchestration-Path: extract > extract (repair, visit 2) > extract (repair, visit 3) > review_handoff
  6. 6Testing and Verification

    1. Trigger each fallback path with a purpose-built input. For the alternate branch, an input guaranteed to hard-fail. For the repair loop, an input plausibly fixable on retry. For handoff, an input designed to exhaust the repair loop entirely.
    2. Confirm the repair loop actually terminates. Send an input guaranteed to fail on every attempt and confirm the graph exits to handoff at exactly max_visits, not before and not after.
    3. Check retry feedback is actually reaching the model. Simulate only tracks visit counts for a repair loop. It doesn't construct the retry feedback message a real repair attempt sends. Run a real Live request through the repair loop and inspect the second attempt's actual input in Dashboard → Requests to confirm it includes the specific failure reason, not just a repeat of the original prompt.
    Request detail for a repair attempt showing the injected feedback text alongside the original input
  7. 7Troubleshooting

    "My repair loop runs forever." Check that max_visits is actually set on the step. A repair route with on_rule_failed and no visit limit will retry indefinitely on a persistently failing input.

    "The repair loop exits to handoff immediately, without ever retrying." Check the route priorities. If the on_validation_block route to handoff has a lower priority number (evaluated first) than the on_rule_failed repair route, it will win before the repair path gets a chance.

    "Retries don't seem to include feedback about what failed." Confirm the repair step's input_mode is actually assembling the failure reason into context. pass_through or a plain append_previous won't automatically include it; you typically need custom_template to inject the specific validator message.

    "I want different fallback behavior for different failed rules on the same step." Use multiple on_rule_failed routes with different rule_name values and different priorities. A required_fields failure might go to a repair loop, while a pii_detection failure on the same step might go straight to handoff, since retrying rarely fixes a genuine PII leak.

    "A fallback branch fires far more often than I expected." Treat this as a signal about the primary path, not just the fallback. A repair loop or alternate branch firing on a large fraction of traffic usually means the main step's prompt or agent needs improvement, not that the fallback needs to be more elaborate.

  8. 8Best Practices

    • Reserve repair loops for failures that plausibly self-correct with feedback. A rule that fails for reasons outside the model's control (missing source data, an impossible request) won't be fixed by retrying. Route those straight to handoff instead.
    • Always bound a repair loop with max_visits. There is no such thing as a safe unbounded retry loop in a production playbook.
    • Design the exhausted-loop exit deliberately, not as an afterthought. A repair loop that dead-ends with no route when max_visits is reached will fail in a confusing way.
    • Monitor fallback frequency as an ongoing metric, not just at launch. A fallback path's usage rate over time tells you whether the primary path is improving, stable, or degrading.
    • Keep repair-loop feedback specific. "The response is missing total_amount" corrects behavior far more reliably than a generic "please try again."
  9. 9Advanced Options

    Combining repair loops with fan-out. If one branch of a fan-out has a repair loop and others don't, the merge node still waits for the repair loop to either succeed or exhaust its retries. Plan for this added latency on the branch with retry logic when reasoning about overall playbook latency. For partial success (some branches pass, others block), use merge_policy: min_passing_branches with an on_partial_pass route from the merge step and the branch_pass_count validator on the post-merge agent. See fan-out-merge.

    Escalation tiers. For high-stakes workflows, consider more than one fallback tier: a repair loop first, then an alternate stricter agent if repair fails, and only then human handoff, rather than jumping straight from failure to a person for every case.

    Correlating fallback paths with cost. A repair loop that runs its full max_visits budget on a large fraction of traffic is also a cost signal. Each retry is a full additional LLM call, the same cost consideration covered in depth in the reask-tuning tutorial.

  10. 10Summary and Next Steps

    Fallback logic is what makes a playbook production-grade rather than a demo: alternate branches for failures that need a different approach, bounded repair loops for failures a retry with feedback can plausibly fix, and human handoff for everything else. The mechanics reuse the same routing primitives as the rest of the graph. The design work is in matching the right fallback pattern to each real failure mode you actually observe.

    From here, tuning reask loops covers the complementary agent-level retry mechanism for single-attempt self-correction, and running parallel steps with fan-out and merge is worth revisiting once you have fallback logic in place on individual branches, since the two patterns compose naturally in more ambitious playbooks.

What's next?