0152 — A front-door voice agent carries a "back to English" workflow node
- Status: Accepted 2026-11-01 (Gera: "Willows first, then all"; amends ADR-0109). Rolled out by the PR that flips this line.
- Date: 2026-10-30
- Deciders: Gera (route chosen 2026-10-30), Fede (ADR-0109 owner)
- Related: ADR-0109 (the sibling topology this amends), PR #23551 (the measured bug), #23872 and #23974 (the staging bench tooling that made it measurable)
Context
A caller we already know speaks English asks "¿hablas español?". Clara calls the ElevenLabs language_detection system tool with es, and the call switches to Spanish (speech recognition and voice). The caller then speaks plain English. Clara answers in Spanish and stays in Spanish for the rest of the call.
What the bench (Willows staging fleet, robot line +17206371205 → +18442853526 only) and the ElevenLabs records show, 2026-10-30:
- The tool is never blocked; the model never calls it. In 20 test calls the only
language_detectioncall is the switch to Spanish, every one returned success, and no switch back was ever attempted. The live config allows it:only_at_conversation_start: false,agent.language: en, anespreset that overrides only the voice. - Instruction wording does not move it. 0 of 5 on main; 0 of 5 with the tool description saying one English sentence comes back; 0 of 5 with that plus the prompt wording; 0 of 3 clean with the description also saying a quoted turn means another language was heard.
- The way back works when the caller says the word. 3 of 3 calls where the caller says "Sorry, actually I only speak English" came back to English (
conv_0601m67pc98fewmv435tdh8vscnj,conv_8101m67pm7ebf53bkqfq7gvacy04,conv_3601m67pvpa2f05bx160a7h305d0). - English arrives in quotation marks while the call is in Spanish. The robot's own record of the same call has no quotes (
conv_3901m67es44kf1n9h20yhpky752xagainstconv_0501m67es4j0e6jbaxpb0enzpyxt), and English turns stop being quoted once the call is back in English. - No supported way to force the language from outside the model (ElevenLabs docs, 2026-10-30): overrides apply only at conversation start, client events carry no configuration update, and
POST /v1/convai/toolsrefuses a standalone system tool ("System tools are not allowed in tools collection"), so a workflow tool node cannot forcelanguage_detection(en).
Decision
Every front-door voice agent that enables language_detection (Triage, Leasing, Leasing many-homes, Maintenance (Tenant), Lease & Billing) carries the same small ElevenLabs workflow, declared in config-as-code beside its LANGUAGE block and synced by the on-merge language lane:
start→main(anoverride_agentnode with no overrides: today's agent, unchanged).main→englishon an LLM edge: "You have been speaking Spanish on this call, and the caller's most recent turn is a whole sentence in English (it may arrive wrapped in quotation marks)."englishis anoverride_agentnode withconversation_config.agent.language = enand an additional prompt that fixes English for the rest of the call, including any line said before a hand-off. It changes nothing else.
Because each sibling is a separate ElevenLabs agent and the conversation language is carried across transfer_to_agent, the node has to exist on every front door, not on Triage alone.
Evidence (prototype on Willows staging Triage only, 2026-10-30, since undone)
- The model takes the edge on the English turn 10 of 10 (
notify_condition_1_met), where it calledlanguage_detection(en)on 0 of 20. - With the strengthened additional prompt, Triage's own reply on that turn is English 5 of 5 (
conv_3701m67zacwxfz3tezkhp774p0db,conv_1001m67znn9seg8v7c7mpgpvnbz5,conv_9001m6803c3qehzsmbc91d14d4gp,conv_3001m680gp2wf5m8na854n8w34q0,conv_0501m680xpbaetcrtj1am446f2ej). - And 4 of 5 still fail end to end: after
transfer_to_agent, Maintenance (Tenant), which had no node, answered in Spanish, and the caller's English kept arriving quoted. The node does not change the conversation language the next agent inherits. That is why this decision puts it on every front door. The full conversion has not been benched yet.
Rollout evidence (Willows staging fleet, robot line only, 2026-10-31 to 11-01)
Written to all five front doors through the on-merge language lane (back_to_english_workflow: true on each LANGUAGE block), pushed to staging with voice:push-to-staging, which runs the same function.
Node alone on all five agents: 3 of 5 end to end. The specialist sometimes stayed Spanish, because one agent's node does not change the call language the next agent inherits.
The English node calls
language_detection(en)first. The call is now English across the hand-off on every call that followed (15 of 15), and the caller's English stops arriving quoted.Final run (this head): back in English across the hand-off 5 of 5. In 20 of 20 calls over the last four runs the call came back to English and stayed English after the hand-off.
The double firing is fixed. The edge reads the agent's own most recent words, so an agent already speaking English never takes it again.
Residual, tracked as a follow-up: on 4 of 5 calls Triage says one word in the old language ("¡Entendido!") just before the edge's transition. It survived:
- a static filler and a prompt-keyed filler;
- an instruction to say nothing on that turn;
pre_tool_speech: offonlanguage_detection.
It looks like ElevenLabs' own speech before a workflow transition, so it is a question in the support draft. Gera, 2026-11-01: ship now, polish after. Getting stuck in Spanish is far worse than one Spanish word.
The word, found and removed (#24346, 2026-11-02). The ElevenLabs record shows the word is the model's own text in the same reply as the edge, which ElevenLabs runs as a tool call (
notify_condition_1_met). The prompt's handoff-line rule ("one short line in the caller's language, then the tool") was being applied to it. Anexpressionedge with an LLM value is not a way round it: it compiles to the same tool call, and the word came back (conv_1901m6db0ryhft1a0k9bgnvtj6fd). The main node now says the switch is not a handoff and that turn has no words, the edge condition says the call is the whole reply, and neither quotes a filler word any more. Bench: no old-language word before the switch on 6 of 6: three on the Willows (conv_4401m6dbtakhenh8jm75xbxf1bys,conv_3601m6dc1m3gf35akxfbmna4tp5b,conv_6201m6dc6f5ver2t626jewaky21c) and three on the Fairhaven TEST line (conv_7701m6f1k7ydf5ksdsrhk7hk4kft,conv_7301m6f1qmzre1zaeqdp1m7sara8,conv_8501m6f1tr7nezw9v0m8r4527077). The call stayed in English across the hand-off to Lease & Billing (2 of 2), and asking for Spanish again by name switched back (the round trip). The three variants tried before it that night all spoke Spanish first: the expression edge, an "Okay." line (conv_5001m6db9pc4ebjv7m2w6hmq983y), and the edge instruction alone (conv_6101m6dbjkm1encay9m2aryf7crg).
Decision amended by the rollout:
- The
englishnode's prompt callslanguage_detection(en)before anything else. - The main node carries one line telling Clara to stay silent on that turn.
- The edge condition keys on the agent's own most recent words.
language_detectionis owned withpre_tool_speech: off.
Consequences
- The sibling agents stop being "prompt + tools" only: each also owns a workflow graph.
sync-specialist-language.ts(or a sibling script on the same merge-only lane) must own theworkflowkey, and the live-equals-main drift checks must compare it. Today no sync writesworkflow, which is also why the prototype survived the automatic syncs. - An LLM edge is still a model decision. It is a much easier one than calling
language_detection(en)(10 of 10 against 0 of 20), but it is not deterministic. The bench gate before rollout: the robot scenariorobot-tenant-spanish-then-one-english-sentence-switches-backpasses end to end, across the Triage → specialist hand-off, on the Willows staging fleet. - The node must not fire on a Spanish caller's single English word or name. The edge condition says "a whole sentence", and the bench gate includes the existing Spanish-caller scenarios.
- The ElevenLabs support question (why English is quoted after a switch, and the supported way to force a language mid-call) is drafted for Gera. A supported switch would replace this decision.
Alternatives considered
- More instruction wording (prompt and tool description): measured 0 of 18, rejected.
- A workflow tool node forcing
language_detection(en)(route B): not possible, because ElevenLabs refuses system tools in the tools collection, so the node has no tool id. - Never switching speech recognition on a Spanish ask from a known-English caller (Fable's first-order answer): rejected by Gera in favour of this route. It would recreate the defect of Spanish speech heard through English recognition.
- Waiting on ElevenLabs support: running in parallel, not a blocker.