# A later yes must not erase a hardship disclosure

Source: https://rasa.community/library/casebook/banking-collections/
Author: Rod Rivera
Published: 2026-10-12T09:00:00.000Z

The customer’s test inputs say, in Spanish, that they cannot cover rent, then ask for the six-payment plan. The later request does not erase the earlier hardship statement.

> **Customer, authored test input:** No me alcanza para la renta.
>
> **Customer, next authored test input:** Bueno, anóteme en el de seis pagos.

The first line means “I don’t have enough for rent.” The next asks to be put on the six-payment plan. These are supplied text inputs, not transcribed speech. The fictional Northgate Bank tool returns:

```text
{"memory": null, "staging": "hardship_declared"}
{"detail": "hardship_declared", "effects": 0, "reason": "choice_not_confirmed"}
```

## Decide which earlier turns remain relevant

A conversation designer cannot treat every new request as a clean slate. In this teaching policy, difficulty meeting basic needs stops automatic repayment-plan selection. The later plan request supplies intent, but it does not establish that the difficulty was resolved.

The first refusal happens while staging the offer. The proof then inserts staged state directly and attempts the final write. Recording is refused too, with no effect. That second part catches a guard placed only at the start of the conversation.

::::checkpoint{id="banking-collections-decision" question="A customer disclosed hardship, then said yes to a plan. What should this fixture record?" options="The plan because the latest answer wins|No plan; preserve the hardship route|A payment because the plan was selected" answer="1"}

The earlier disclosure remains relevant to the action. A new yes does not establish that hardship was resolved.

::::

## Put the hardship stop inside confirmation

The confirmation helper reads the customer history and open referrals before it checks the staged offer and read-back. The hardship stop is the first branch in this excerpt.

[Source excerpt](https://github.com/RasaHQ/rasa-community-resources/blob/4aa0c4419dc193fef7a969c12d59edcf720f2606/examples/mantle-voice-banking-collections-gemini/lib/repayment.py#L556-L565):

```python
if hardship_declared(conversation.user_texts) or state.open_referral():
    return False, "hardship_declared"
staged = state.staged
if offer is None or not staged or staged["offer_id"] != offer["offer_id"] or memory_tag != staged["tag"]:
    return False, "not_staged"
question = fold(conversation.confirmation_question or "")
if fold(_offer_view(offer)["terms_spoken"]) not in question or fold(account["label"]) not in question:
    return False, "not_read_back"
if conversation.confirmation_answer is None:
    return False, "not_answered"
```

This detector reads a bounded set of authored Spanish phrases in the companion source. The checks cover those inputs. They do not establish recall across indirect disclosures, dialects or speech-recognition mistakes. Have the collections owner supply the disclosures that matter for the intended release.

::::callout{type="info" title="Do not turn a pause into a payment"}

The receipt test records a plan choice and explicitly says that no payment was received. Keep agreement, recording a plan and taking a payment separate in both the interface and product metrics.

::::

## Give an unavailable team a different reply

The loan hardship fixture cannot accept a referral. The callback test preserves the request and returns a callback reference. That is a different state from an accepted referral, and neither is a recorded repayment plan.

| Service state                | Permissible next step                         | Claim to avoid                      |
| ---------------------------- | --------------------------------------------- | ----------------------------------- |
| Hardship referral accepted   | Give the referral reference                   | Saying a plan was agreed            |
| Hardship team unavailable    | Preserve request and callback reference       | Saying the team accepted a referral |
| Customer withdraws agreement | Clear staged choice or withdraw recorded plan | Treating an earlier yes as current  |

::::diagram{title="The conversation can leave the offer path before any plan is recorded"}

```dot
history [label="Read customer conversation"]
hardship [label="Hardship disclosed?"]
exit [label="Preserve hardship request or callback"]
offer [label="No hardship: check current offer"]
record [label="Confirm this offer before recording"]
history -> hardship
hardship -> exit
hardship -> offer
offer -> record
```

::::

The support team needs to receive the preserved request. The fixture creates a record; it does not place a phone call. Agree who owns unfulfilled callbacks and what the customer is told when a team remains unavailable.

## Test the final write, not just the first reply

Run these checks from the pinned companion project. They use Python's standard library and make no model calls. The outputs below were recorded on 7 October 2026; elapsed times can differ.

```bash
git clone https://github.com/RasaHQ/rasa-community-resources.git
cd rasa-community-resources
git checkout 4aa0c4419dc193fef7a969c12d59edcf720f2606
cd examples/mantle-voice-banking-collections-gemini/tests
```

::::run{cmd="python3 -m unittest test_guard.SelectAndRecordTests test_guard.WithdrawReferralCallbackTests -q"}
::::

```text
----------------------------------------------------------------------
Ran 19 tests in 0.001s

OK
```

The [companion tests](https://github.com/RasaHQ/rasa-community-resources/blob/4aa0c4419dc193fef7a969c12d59edcf720f2606/examples/mantle-voice-banking-collections-gemini/tests/test_guard.py) contain the assertions for these cases. To print the first-screen trace yourself, run this script from the same directory:

::::solution{title="Show the script that printed the fixture states"}

Paste it into a file such as `trace.py`, then run `python3 trace.py`. It prints selected fields from the actual tool results; it does not invent replies.

```python
"""Run from the pinned companion project tests directory. Stdlib only."""
import json
import test_guard as t

case = t.SelectAndRecordTests()
case.setUp()
users = ("No me alcanza para la renta.", "Bueno, anóteme en el de seis pagos.")
staged, memory = case.stage("OFR-4471-6M", users=users)
print(json.dumps({"staging": staged["status"], "memory": memory}, sort_keys=True))
case.state.staged = {"account_ending": "4471", "offer_id": "OFR-4471-6M", "tag": "OFR-4471-6M/1"}
result = case.record("OFR-4471-6M", {"plan_tag": "OFR-4471-6M/1"}, users=users)
print(json.dumps({"reason": result["reason"], "detail": result["detail"], "effects": result["effects"]}, sort_keys=True))
```

::::

For conversation review, replay the two authored turns before checking a normal yes. Then attempt the final write with staged state and inspect the result. [Design the handoff summary](/library/guides/design-the-handoff-summary/) so it carries the disclosure and unresolved request without inventing a resolved outcome.

::::cta{href="/library/guides/design-the-handoff-summary/" label="Carry the disclosure into the handoff"}
::::

The [complete fixture implementation](https://github.com/RasaHQ/rasa-community-resources/blob/4aa0c4419dc193fef7a969c12d59edcf720f2606/examples/mantle-voice-banking-collections-gemini/lib/repayment.py#L1-L768) defines the service state and remaining branches cited here.