Bake-off: prose conditionals vs $SWITCH for multichannel agents
One agent, three channels: voice, chat, email. Two prompts encode the same per-channel rules, and both compile clean. The difference only shows up the day someone adds a fourth channel and forgets an arm.
Most CX agents run on more than one channel, and the channel changes the rules. A voice bot has to speak money as words (“nineteen dollars,” not “$19”); a chat or email reply wants digits. It’s a tiny rule, and there are two honest ways to write it. They behave identically today. We ran them through the checker to find out which one still protects you after six months of edits.
§1The two contenders
Contender one writes the branch as prose the model reads and weighs at runtime: a stack of IF the channel is X THEN … lines. Contender two declares the channel as a typed input and hands the branch to the compiler with a $SWITCH. Same behavior, two spellings. Here they are, side by side.
# Role You are a billing-support agent. # Output - IF the channel is voice THEN speak money amounts as words. - IF the channel is chat THEN write money amounts as digits. - IF the channel is email THEN write money amounts as digits.
$REQUIRE variable channel: one of voice, chat, email # Role You are a billing-support agent. # Output $SWITCH ON @{channel} - voice:: Speak money amounts as words. - chat:: Write money amounts as digits. - email:: Write money amounts as digits.
§2Round one: a dead heat
On day one, there’s nothing to choose between them. Both compile with zero errors. Score them and it stays a tie: both land at C (71/100). The prose version is arguably easier to skim; the switch version carries a declaration you had to write. If this were the whole story, you’d pick on taste and move on.
It isn’t the whole story, because a prompt is not a thing you write once. It’s a thing your team edits for a year. The real question a bake-off should ask isn’t “which is nicer today,” it’s “which one fails safely when the requirements change.” So we changed the requirements.
§3Round two: product adds SMS
A quarter later, support turns on SMS. Someone updates the channel list and, being human, updates most of the rules that mention it. The prose prompt gets a new IF line in some edits and not others; the switch prompt gets a new member in its declared domain. Watch what each does when the SMS arm is the line that got missed.
The prose prompt doesn’t blink. Adding SMS to the world doesn’t change any line already in the file, so it compiles exactly as before, silently covering three of four channels. An SMS user hits the agent and gets whatever the model improvises, because no rule speaks for that branch.
$REQUIRE variable channel: one of voice, chat, email, sms # Role You are a billing-support agent. # Output $SWITCH ON @{channel} - voice:: Speak money amounts as words. - chat:: Write money amounts as digits. - email:: Write money amounts as digits.
channels-switch.tg:7:1 error structure/non-exhaustive-switch @{channel} can be sms, but no arm handles it - add a "- <member>::" row for each (or a deliberate "- otherwise::" fallback). ✗ 1 file - 1 error, 0 warning, 0 info
§4The verdict: totality beats vigilance
The prose prompt asks you to be vigilant: every time the channel set changes, remember to audit every branch that reads it. That works right up until the one edit where you don’t. The switch prompt asks nothing of you. It makes coverage a property the compiler proves, so an incomplete branch is a failed build, not a production surprise on a channel nobody tested.
That’s the trade, stated plainly. A prose IF chain is the right tool when the conditions are open-ended, overlap, or lean on thresholds a compiler can’t see (“if the customer sounds frustrated”). A $SWITCH is the right tool the moment your branch is one-per-member of a set you can name: channel, tier, language, region. Those are exactly the axes CX agents fan out on, which is why the switch earns its keep here.
A closed set deserves a closed check. If you can list the cases, let the compiler count them.
Neither prompt is wrong. But only one of them turns “we forgot SMS” from an incident into a red build on the pull request that introduced it. In a bake-off scored on the day you ship, it’s a draw. Scored on the year you maintain, the switch wins going away.
The exhaustiveness proof behind structure/non-exhaustive-switch shipped with the $SWITCH engine in TypeGlish 0.1.0: add a member to a typed domain and every switch over it that lacks an arm fails loudly, all at once.