Skip to main content
VARTA

L4 Routing Rules

Answer

Routing rules are the deterministic half of L4: `goto` jumps to a real step, `say` speaks a pre-cached phrase at zero latency, `say` with return_to_cursor reassures then resumes, and `say` with end_call closes politely. Global rules apply at every step; step-level rules apply only where you set them.

Audience: workflow authors + reviewers. How to write IF/THEN rules that control what the agent does when a customer says something off the happy path.

TL;DR cheat sheet

goto = jump to a real step in the graph (it has its own prompt, niti rules, sub-flow).
say = speak THIS exact phrase, pre-cached as TTS, deterministic. 0 ms latency.
say + return_to_cursor: true = reassure / explain → resume where customer was.
say + end_call: true = polite close → terminate gracefully.
Workflow-level (Global) = applies at every step (cancel, fraud, interest, off-topic).
Step-level = applies only here (loan-type pick, salary hesitation, branch-specific).
say is pre-built into the TTS cache when you click Apply. Editing the text re-builds on next Apply.

Quick-decide examples

Customer says…UseWhy
"ये fraud तो नहीं?"say + return_to_cursor (Global)One-line reassurance, then continue the flow. Can happen at any step.
"Interest कितना लगेगा?"say + return_to_cursor (Global)Brief explainer, return to where they were. Universal question.
"रहने दीजिए" (cancel)say + end_call (Global)Polite goodbye + terminate. Universal.
"Personal loan" on collect_loan_typegoto → personal_company_name (Step)Whole sub-flow follows — branching into another collection step.
"Home loan" on collect_loan_typegoto → home_property_type (Step)Same — full sub-flow.
"मुझे appointment book करना है"say + end_call (Global)Off-topic — say "we don't handle that" + close.
Hesitates to share salarysay + return_to_cursor (Step on collect_salary)Confidentiality reassurance + re-ask the same step.
"कौन सा बेहतर है?" on loan-type picksay + return_to_cursor (Step on collect_loan_type)Explain options briefly + re-ask the same question.

L4 Prompt vs L4 Routing Rules — the big distinction

Both shape what L4 BUDDHI does at a step, but they answer different questions. Mixing them up is the #1 authoring mistake (the 2026-06-09 + 2026-06-10 Cab Booking traces both had routing intent described in the L4 prompt textarea, where L4 ignores it).

AspectL4 Prompt step.l4.promptL4 Routing Rule l4_routing_rules
Question it answersHOW should L4 reason here?WHERE should L4 go when customer says X?
ShapeFree-text directiveStructured {when, goto|say, flags}
Controls exact wording?No — L4 still generates each turnYes (with say) — exact pre-cached TTS
Can navigate to other steps?NoYes — goto: step_id
Can end the call?NoYes — end_call: true
LatencyL4 LLM call (~2-4 sec)say = 0 ms cached; goto = also 0 ms
Cost~₹1 per turn (LLM)₹0 when rule fires

Mental model

L4 prompt = the agent's personality + rules for this step (tone, what to validate, what to never do).
Routing rules = shortcuts — pre-written decisions for specific customer intents that skip reasoning entirely.

6 worked examples — which goes where?

1. Reject single-digit salary answers

Need On the salary-collect step, accept "5 lakh" but re-ask on bare "5".

Use L4 Prompt.

Why Validation behavior — L4 still reasons each turn, but with a stricter rule.

text
step.l4.prompt:
"On THIS step, accept salary only if customer mentions a unit
(lakh / thousand / k / per month). If they reply with just a bare
number, briefly explain you need the monthly amount in rupees,
then re-ask."

2. Fraud reassurance — exact wording, anywhere in the call

Need Customer asks "ये fraud तो नहीं?" at any step → reply with one EXACT line, then continue.

Use Routing Rule (workflow-level, say + return_to_cursor).

Why Universal intent + exact wording required + must resume current step.

json
{
  "when": "customer doubts the call",
  "say":  "बिल्कुल सही सवाल है। हम कोई OTP नहीं माँगते — सिर्फ़ basic details चाहिए।",
  "return_to_cursor": true
}

3. Loan-type pick — branch into sub-flow

Need On loan-type step, "Personal loan" → jump to personal-loan sub-flow.

Use Routing Rule (step-level, goto).

Why Branching into a multi-step sub-flow with its own collection logic.

json
{
  "when": "customer says Personal loan",
  "goto": "personal_company_name"
}

4. Stop English drift

Need L4 keeps switching to English on Hindi-speaking customers.

Use L4 Prompt.

Why Style/persona constraint — applies to every L4 turn, not a specific intent.

text
step.l4.prompt:
"ALWAYS reply in Hindi using Devanagari script even if customer
mixes English words. Never switch to pure English."

5. Polite cancel — end gracefully

Need Customer says "रहने दीजिए" / "cancel कर दो" → one polite line + terminate.

Use Routing Rule (workflow-level, say + end_call).

Why Universal intent + exact wording + must terminate.

json
{
  "when": "customer wants to cancel mid-flow",
  "say":  "कोई बात नहीं, जब चाहें call कर दीजिए। धन्यवाद!",
  "end_call": true
}

6. Never accept "skip" on a required slot

Need On the date-collect step, never let customer bypass with "skip" / "baad mein".

Use L4 Prompt.

Why Step-specific validation rule. Not navigation — just a what-NOT-to-do directive.

text
step.l4.prompt:
"On THIS step, NEVER accept 'skip', 'pass', 'baad mein', or any
deferral. The date is required for booking. If customer tries to
skip, explain we can't proceed without it and re-ask."

Decision flow (when in doubt)

text
1. Does the customer's utterance match a SPECIFIC intent you can name?
   NO  → it's a general behavior-shaping problem → L4 Prompt
   YES → ↓

2. Should the next agent line be ONE EXACT pre-written phrase?
   NO  → multi-step branch follows → Routing Rule with `goto`
   YES → ↓

3. After saying it, where should the cursor go?
   Same step (resume)  → `say` + return_to_cursor
   End the call        → `say` + end_call
   Next step (advance) → `say` only (rare)

Common mistakes — anti-patterns

Anti-patternWhy it failsFix
Routing logic written in the L4 prompt: "If customer says cancel, end the call"L4 won't act on it — descriptive, not actionableRouting rule with end_call: true
Persona/tone in routing rulesNo field for itPut it in step.l4.prompt
say for templated text with {customer_name}say is pre-cached at Apply time — no runtime interpolationgoto a real speak step that supports templating
Both goto AND say set on the same ruleRuntime prefers say; goto is silently ignoredPick one per rule
Step-specific routing on the workflow panelFires on every step (weird jumps from unrelated steps)Move to the specific step's routing panel

The four places L4 reasoning lives

#WhereFieldAppliesExample
AWorkflow routing rulewf.global_l4_routing_rulesEvery stepCancel mid-flow, fraud doubt, interest rate question
BStep routing rulestep.l4_routing_rulesThis step only"personal loan" → personal_company_name
CStep L4 promptstep.l4.promptThis step's L4 reasoning style"Be empathetic; never accept a range, only a single number"
Dsay text on a rulerule.say + flagsReplaces goto for canned responses"हम कोई OTP नहीं माँगते…"

D is a property of a rule, not a separate storage location. A single rule can use either goto (jump) or say (canned response).

Rule schema — the two flavours

Flavour 1 — goto (jump to a real step)

json
{
  "when": "customer says personal loan",
  "goto": "personal_company_name"
}

Use when the next thing isn't just "say one line" — it's a whole sub-flow with its own collection logic, branches, sub-steps.

Flavour 2 — say (canned response, pre-cached)

json
{
  "when": "customer doubts the call",
  "say":  "बिल्कुल सही सवाल है। हम कोई OTP या payment नहीं माँगते — सिर्फ़ eligibility check के लिए basic details चाहिए।",
  "return_to_cursor": true
}

Use when you want to say one specific phrase and then resume the conversation where the customer was. Pre-cached at apply time, 0 ms latency, identical wording every call.

Flag combinations on a say rule

Flag comboBehaviour
say + return_to_cursor: trueSpeak the phrase, then resume current step (customer will continue answering the original question)
say + end_call: trueSpeak the phrase, then terminate the call gracefully
say only (no flags)Speak the phrase, advance flow normally (rare — usually you want one of the above)

The decision rule (four-way)

For each rule, ask in order:

  1. "Could the customer say this at MORE THAN ONE step?"
    Yes → workflow-level (A)
    No → step-level (B)
  2. "Is the response a one-line reaction or a multi-step branch?"
    One-liner / single sentence → say (D)
    Multi-step (own collection, own branches) → goto
  3. "After speaking, where should the cursor go?"
    Back to the same step where customer was → return_to_cursor: true
    End the call → end_call: true
    Advance normally to next step → leave both off (rare)
  4. "Is this about style/persona, not navigation?"
    Yes → it belongs in step.l4.prompt, not a routing rule.
    Example: "On THIS step, never accept a salary range — only a single number"

Complete example — Loan Eligibility (copy-pasteable)

text
STEP 1: Opening
IF customer agrees to proceed (e.g., "हाँ बताइए"):
JUMP TO: "collect_loan_type"

IF customer declines (e.g., "नहीं चाहिए", "busy हूँ"):
JUMP TO: "धन्यवाद, future में जब चाहें call करें। आपका दिन शुभ हो!"

STEP 2: Collect Loan Type
IF customer says "Personal loan":
JUMP TO: "personal_company_name"

IF customer says "Home loan":
JUMP TO: "home_property_type"

IF customer says "Car loan":
JUMP TO: "car_model"

IF customer says "Business loan":
JUMP TO: "business_sector"

IF customer is confused (e.g., "कौन सा बेहतर है?"):
JUMP TO: "कोई बात नहीं — personal अपने ख़र्च के लिए, home घर ख़रीदने के लिए, car गाड़ी के लिए, business अपने काम बढ़ाने के लिए। बताइए कौन सा उपयुक्त लगेगा?"

STEP 3A: Personal salary
IF customer hesitates to share salary (e.g., "ये बताना ज़रूरी है?"):
JUMP TO: "जी हाँ, ये सिर्फ़ eligibility check के लिए है। आपकी जानकारी पूरी तरह गोपनीय रहेगी।"

GLOBAL EXCEPTIONS
IF customer wants to cancel mid-flow (e.g., "रहने दीजिए"):
JUMP TO: "कोई बात नहीं, जब भी सुविधाजनक हो हमें call कर दीजिए। धन्यवाद!"

IF customer doubts the call (e.g., "ये fraud तो नहीं?"):
JUMP TO: "बिल्कुल सही सवाल है। हम कोई OTP या payment नहीं माँगते — सिर्फ़ eligibility check के लिए basic details चाहिए।"

IF customer asks about interest rate (e.g., "Interest कितना लगेगा?"):
JUMP TO: "जी, interest rate आपकी profile और loan amount पर निर्भर करता है। हमारा loan officer call पर सटीक details देगा।"

How the parser interprets it

JUMP TO contentTreated asWhy
"collect_loan_type"goto: "collect_loan_type"Matches an existing step_id
"personal_company_name"goto: "personal_company_name"Matches existing step
"धन्यवाद, future में... शुभ हो!"say + end_call: trueLong sentence + closing phrase ("शुभ हो") auto-detected
"कोई बात नहीं — personal..."say + return_to_cursor: trueLong sentence, no closing marker — default
"बिल्कुल सही सवाल है..."say + return_to_cursor: trueLong sentence — canned reassurance
"जी हाँ, ये सिर्फ़..."say + return_to_cursor: trueLong sentence — confidentiality explainer

How the pre-caching works

When you click Apply on the Designer panel:

  1. The parser splits the script into global + per-step buckets and writes them to the workflow.
  2. For every rule with a say field, the runtime:
    Computes a stable intent_key: routing_global_<idx>__<sha> or routing_step_<sid>_<idx>__<sha>
    Calls the workflow's TTS provider (Sarvam / ElevenLabs / etc.) with the workflow's voice + language
    Stores the resulting clip as a sample with step_role: "routing_response"
  3. The Designer's response shows precache: {jobs_total: N, synthesised: N} so you can confirm.

At runtime — what L4 sees

text
GLOBAL ROUTING HINTS (apply at ANY step):
- when "customer wants to cancel mid-flow" → action:play_cache, intent_key:routing_global_0__a1b2c3d4ef
  (canned: "कोई बात नहीं, जब भी सुविधाजनक..."), end_call:true
- when "customer doubts the call" → action:play_cache, intent_key:routing_global_1__f5e4d3c2b1
  (canned: "बिल्कुल सही सवाल है..."), return_to_cursor:true
- when "customer asks about interest rate" → action:play_cache, intent_key:routing_global_2__9876543210
  (canned: "जी, interest rate आपकी..."), return_to_cursor:true

STEP-SPECIFIC ROUTING HINTS (for THIS step):
- when "customer says Personal loan" → goto_step:personal_company_name
- when "customer says Home loan" → goto_step:home_property_type
- when "customer is confused" → action:play_cache, intent_key:routing_step_collect_loan_type_4__abcdef0123
  (canned: "कोई बात नहीं — personal..."), return_to_cursor:true

Hints are advisory — apply only if the customer's utterance actually matches the 'when' description.

Override pattern (rare but important)

When a global rule needs a DIFFERENT target on a specific step. Example: globally, "ask about interest rate" → generic explainer. But on collect_loan_type, asking about interest rate is suspicious (no loan type picked yet) — redirect back into the funnel.

text
GLOBAL EXCEPTIONS
IF customer asks about interest rate:
JUMP TO: "जी, interest rate आपकी profile पर निर्भर करता है। हमारा loan officer call पर बताएगा।"

STEP 2: Collect Loan Type
IF customer asks about interest rate:          ← same `when`, overrides global
JUMP TO: "पहले loan type चुन लीजिए — फिर interest rate के बारे में बता दूँगी।"

The step-level rule wins on label collision (case + whitespace insensitive). L4's prompt for collect_loan_type shows only the step-level version. Other steps see the global rule normally.

Where does this rule belong? — full cheat table

Customer intent / questionLayerRule typeWhy
"मुझे cancel करना है" (cancel mid-flow)Workflowsay + end_callCan happen at any step; one-line close
"Interest कितना है?"Workflowsay + return_to_cursorUniversal question; brief explainer
"ये fraud तो नहीं?"Workflowsay + return_to_cursorUniversal anxiety; reassurance
"कौन बोल रहा है?" (who's calling)Workflowsay + return_to_cursorBrief identity reminder
5+ seconds of silenceWorkflowsay + return_to_cursorCheck if customer is still there
"Personal loan" on collect_loan_typeStepgotoOnly meaningful here; whole sub-flow
"Salary बताना ज़रूरी है?" on collect_salaryStepsay + return_to_cursorSlot-specific objection; one-line reassurance
"नहीं decide किया कौन सी car" on car_modelStepsay + return_to_cursorBranch-specific hesitation; brief explainer
"Same number use करो" on phone_collectStepsay + return_to_cursorSlot-specific request; confirm + re-ask
"Be more empathetic" general notestep.l4.prompt(style)Persona/style directive, not routing
"Never accept a range — only a number"step.l4.prompt(style)Validation behavior, not routing

Common mistakes

  1. Putting universal rules per-step — copying "interest rate question" onto every step's rules will drift. Edit on step 1, forget on step 7. Use workflow-level.
  2. Putting step-specific rules at workflow level"Personal loan" at workflow level would fire on the salary step too, causing weird jumps. Use step-level.
  3. Using L4 prompt for routing decisions — the L4 prompt is for how to talk, not where to go. Use routing rules for navigation.
  4. Missing the goto target — make sure the target step exists in the workflow. The parser surfaces unmatched ones in "⚠ Unmatched headings" — fix those before testing.
  5. Vague when clauses"customer is unhappy" → too vague, L4 won't classify reliably. "customer expresses frustration about long wait time" → much sharper. Be specific.
  6. Forgetting return_to_cursor on canned responses — without it, the agent says the canned line and then the cursor advances. Most reassurance / explainer flows want return_to_cursor: true.
  7. Editing say text but not clicking Apply — the rule field saves on edit, but the new audio isn't synthesised until Apply runs. Stale audio plays until then.
  8. Mixing end_call and return_to_cursor — contradictory. Designer clears one when the other is set; parser drops return_to_cursor when it auto-detects an end_call closing phrase like "शुभ हो" / "have a good day".

Performance + cost reference

PathLatency at runtimeCost per turnWording variance
goto to a real speak step (pre-cached audio)0 ms TTS₹0Identical ✅
say rule (pre-cached at apply time)0 ms TTS₹0Identical ✅
L4 falls back to generate (no matching rule)~4–6 sec~₹1Varies every call ⚠

The say field is what makes "I want the agent to say EXACTLY this" achievable without bloating the step graph with one-line reaction steps.

Appendix — Pure JSON examples

For reviewers reading the parsed result rather than the IF/THEN script.

Workflow-level rules (wf.global_l4_routing_rules)

json
[
  {
    "when": "customer wants to cancel mid-flow",
    "say":  "कोई बात नहीं, जब भी सुविधाजनक हो हमें call कर दीजिए। धन्यवाद!",
    "end_call": true
  },
  {
    "when": "customer doubts the call",
    "say":  "बिल्कुल सही सवाल है। हम कोई OTP या payment नहीं माँगते — सिर्फ़ eligibility check के लिए basic details चाहिए।",
    "return_to_cursor": true
  },
  {
    "when": "customer asks about interest rate",
    "say":  "जी, interest rate आपकी profile और loan amount पर निर्भर करता है। हमारा loan officer call पर सटीक details देगा।",
    "return_to_cursor": true
  }
]

Step-level rules on collect_loan_type (step.l4_routing_rules)

json
[
  { "when": "customer says Personal loan",  "goto": "personal_company_name" },
  { "when": "customer says Home loan",      "goto": "home_property_type" },
  { "when": "customer says Car loan",       "goto": "car_model" },
  { "when": "customer says Business loan",  "goto": "business_sector" },
  {
    "when": "customer is confused about loan type options",
    "say":  "कोई बात नहीं — personal अपने ख़र्च के लिए, home घर ख़रीदने के लिए, car गाड़ी के लिए, business अपने काम बढ़ाने के लिए। बताइए कौन सा उपयुक्त लगेगा?",
    "return_to_cursor": true
  }
]

Last reviewed