Skip to content

Conversions: code-enforced confirmation cadence before end_call(deal_agreed) #10

Description

@teetangh

Context

Before calling end_call(deal_agreed) the agent must restate exact terms and get a second explicit yes (CFPB-aligned; a bare "okay" is insufficient). Today this cadence is enforced only by prompt text (data/prompts/resolution_voice_streaming/v1.yaml:189-231) — flagged in the repo's own known-limitations as brittle: any prompt edit, model swap, or adversarial borrower phrasing can produce a "deal" that was never explicitly confirmed (this exact failure was engineering-log incidents #11/#23). The chat path already has a code guard (_AFFIRMATION_PATTERNS + enforcement in apps/api/chat_session.py:73-82, 253-273); the streaming path has none.

Implementation plan

  1. Negotiation state tracker: a small per-call state object in apps/voice/streaming.py recording the last validated propose_offer (canonical terms from _validate_offer) and recent user turns (subscribe to final TranscriptionFrames or the user context aggregator).
  2. Gate in handle_end_call (streaming.py:537-548): when outcome == "deal_agreed":
    • require a validated propose_offer to exist,
    • require the assistant's restatement turn to have occurred after that offer (assistant turn containing the canonical numbers),
    • require the latest user turn to match an explicit-affirmation check — extract _AFFIRMATION_PATTERNS from chat_session.py into a shared module (e.g. agents/affirmation.py) and reuse on both paths.
  3. Corrective tool result: if the cadence is unmet, return a structured tool error ("Do not end the call: restate the exact terms — $X total / $Y monthly for Z months — and ask for explicit confirmation") so the LLM self-corrects in-conversation instead of hanging up.
  4. Loop guard: allow max 2 rejections, then permit no_deal end to avoid an unclosable call.
  5. Tests: unit tests on the gate (no offer → reject; offer + bare "okay" → reject; offer + restatement + "yes, I agree" → allow); scripted cooperative scenarios (examples/01-02) still close via simulator; combative/evasive scenarios never yield an ungated deal_agreed.

Acceptance criteria

  • end_call(deal_agreed) is impossible in code without a validated offer + explicit second affirmation — verified by unit tests, independent of prompt wording.
  • Cooperative scripted scenarios still reach deal_agreed (no over-blocking).
  • Shared affirmation module used by both chat and streaming paths (no duplicated pattern lists).

Dependencies

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High priorityconversionsNegotiation outcomes, conversions, and complianceenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions