Skip to main content

Allow cookies so we can count page views, see which pages and campaigns bring people here, and measure our ads on Meta, Google and LinkedIn. Cookie policy

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