Skip to main content
Wasla calls the holder of one overdue payment. Lina checks it is them by their date of birth, says what is overdue, and records one outcome: a promise to pay, a callback, a wrong number, or a concern in the holder’s own words. The code is in examples/wasla.

The account is a required input

An outbound call about nothing must never ring anyone. So the account id is a required start value, typed as WA and four digits.
wasla/agent.py
A refused call never reaches the greeting. The voice stack logs the reason and ends the job on LiveKit, or builds no pipeline on Pipecat. See Start values.

Nothing before the check

The call is about money, so nothing may reach the wrong person. Only the holder’s name is read before the greeting:
wasla/agent.py
The amount, the shop and the dates come back only from a matching date of birth. The identify step saves them from verify_holder’s result:
wasla/agent.py
Until then, every prompt reads those values as empty. A relative who answers cannot hear them, whatever the model does. Why it works this way. A prompt rule like “do not say the amount before the check” can fail. A value the State does not hold cannot be said. The check is a tool: three wrong dates lock the account, and code says the support line.

Every outcome is read back

Each write has a FinishRule with a confirm line. Code says it, and the write runs only when the holder heard it and said yes.
wasla/agent.py
A concern is one tool with four kinds. One confirm line serves them all, and each kind gets its own receipt, picked by the result’s kind:
wasla/agent.py

The window is the tool’s rule

wasla/ledger.py
wasla/agent.py
A day outside the ten-day window is answered by the tool’s precheck, before the promise is read back. Its reply says “I can only note a day up to Sunday the 25th of October”, with no model request. A concern is a Literal of four kinds. The amount is said by code as “four hundred thirty-four dirhams and fifty fils”. The model never turns a number into speech.

Three groups

  1. identify opens with opening: listen and an announce line. Code asks for the date of birth, with no model request.
  2. skip_when_confirmed skips identify when the date of birth is already confirmed in this call.
  3. A group with then: "end" ends the call once its receipt has played.

The parts

Best practice.
  1. Keep sensitive values out of the State until a tool proves who is on the line. Then save them from that tool’s result.
  2. Put business rules in argument types, with an error message that says what to tell the caller.
  3. One tool with a Literal kind and one FinishRule per kind beats one tool per kind. The model picks among fewer tools.

Try it

The project’s .env.example sets UNMUTE_CALL_START to {"account_id": "WA-1001"}: Layla Haddad, born 14 March 1990. Use {"account_id": "WA-1002"} for Omar Khalil, born 2 November 1985.
  1. “Yes, speaking.” Code asks for the date of birth.
  2. “The fourteenth of March, 1990.” Code reads it back.
  3. “Yes.” Then “Yes, go ahead.” Code says the amount.
  4. “Can I pay this Sunday?” Code reads the promise back, and records it on your yes.
  5. Call again and say “He’s not here, call back tomorrow at five.” Code reads the callback back and ends the call.

Troubleshooting

The step that confirms a value is never reached

A step that confirms a value needs a when, even inside a group. Fix: give it a when that says it is only for a correction.

Where to go next

Faro: two languages

One agent per language, and a handoff to switch.

Prefetch

Read values in before the greeting.