Jev call copilot CurrencyTransfer research and architecture built 2026-09-25

Rate objections: from the PDF to policy ids

Source: docs/source/ct-rate-objection.pdf (text in docs/source/ct-rate-objection.txt), Stevan's training playbook. Method: never defend the rate; change what is being compared.

Approval. Stevan approved every quoted rep line in the PDF as final wording on 2026-09-25: the "Say" lines, the three no-quote beats, the four qualifying questions and the closing quote. They are in policy/src/playbook-*.json as status: "approved", approved_by: "stevan@currencytransfer.com", approved_at: "2026-09-25", source: "ct-rate-objection.pdf", locked: true (verbatim; never rewritten). The word "Say" is a label the UI shows once on the block, never part of a line.

Where each part of the PDF lives

Situations sit under a move's situations array. Ids are unique within a playbook. Point text_ids are <situation>.<n> and are stable. core_text_id names the line that shows the situation was handled.

PDF section Situation Onboarding move CS move qualify_first Core line Do-not flags
Qualify the quote before you respond qualify_the_quote handle_rate_objection handle_rate_complaint – qualify_the_quote.4 rep_rubbished_competitor
No competing quote in hand no_quote_in_hand handle_rate_objection handle_rate_complaint false no_quote_in_hand.2 rep_improved_rate_unasked, rep_explained_spread, rep_said_standard_for_volume
Competitor named, number quoted competitor_named handle_rate_objection handle_rate_complaint true competitor_named.2 rep_rubbished_competitor
Same call, every trade serial_shopper handle_alternative_provider handle_rate_complaint false serial_shopper.1 rep_improved_rate_unasked
Comparing against a number nobody can trade mid_market_comparison show_the_rate_against_mid_market handle_rate_complaint false mid_market_comparison.2 rep_explained_spread
The incumbent has reacted bank_matched handle_alternative_provider handle_rate_complaint false bank_matched.1 rep_went_under_bank
If you do move on price concession_terms handle_rate_objection handle_rate_complaint (+ situation_refs on offer_forward_for_known_future_payment) – concession_terms.5 rep_improved_rate_unasked

Each situation carries headline (the client's words), what / not_for, hint (the PDF's delivery guidance), points, do_not (the PDF's Do-not text, verbatim) and do_not_flags. All five rate moves above are rewrite_allowed: false.

Questions (all in policy/src/shared.json, routed in both rules files)

Id Type, role Fires on
rate_objection_type Choice, concern_type (after the scenario Choice) which of the five, or none
rate_pressure_raised noul, window_fact (persist any) the client pushed back on the rate; gates the checklist item
client_asked_for_better_rate noul, client_turn "Can you do better than that?"
client_named_competitor_rate noul, client_turn "Company X gives me a better rate."
client_cited_mid_market noul, client_turn "Google says 1.2740. Why am I getting 1.2690?"
client_says_bank_matched noul, client_turn "I spoke to my bank and they've matched you."
client_shops_every_trade noul, client_turn "I'll just check what the others are doing."
rep_qualified_the_quote noul, rep_turn (persist rep) the rep asked one of the four questions
rep_closed_with_live_quote noul, rep_turn (persist rep) "it's live, do you want it or shall I requote"
rep_obtained_something_for_concession noul, rep_turn (persist rep) a price move tied to volume, consolidation, forward cover or an introduction
rate_pressure_handled noul, must_say (persist rep) ticks the checklist item
5 do-not nouls (below) noul, risk (lock_on rep) latch a risk flag

Rewrite verifiers (policy/src/verify.json, threshold 0.30 in both rules files): improves_rate_or_offers_discount (any price move, even conditional: only an approved line may offer one) and explains_spread.

Risk flags (the do-nots) and their live notes

Flag PDF Do-not live_note
rep_improved_rate_unasked Improve the rate to close a question that was not about the rate; a concession given for nothing Careful: that moved the price for nothing. Get something on the call for it.
rep_explained_spread Do not explain the pricing / how the spread is built Careful: that explained our margin. Compare what lands instead.
rep_said_standard_for_volume Do not say it is standard for their volume Careful: 'standard for your volume' says somebody gets a better price.
rep_rubbished_competitor Suggest they are wrong or lying; rubbish the competitor Careful: running them down makes us sound like the expensive option.
rep_went_under_bank Go under them Careful: don't go under the bank. Test whether their price survives.

Live notes are drafts for Stevan (risk_flags[].live_note, the field DESIGN.md names).

Checklist and gating

Lines (verbatim; approved 2026-09-25 unless marked)

text_id Line
qualify_the_quote.1 When did you get that rate?
qualify_the_quote.2 Was that a live quote on your amount, or a headline off a website?
qualify_the_quote.3 What were the fees on top, including the receiving bank's cut?
qualify_the_quote.4 What actually landed in the beneficiary account last time, against what you were quoted?
no_quote_in_hand.1 Better than what?
no_quote_in_hand.2 No. That's the price.
no_quote_in_hand.3 And it's live, so it's moving while we talk. Do you want it, or shall I requote in ten?
competitor_named.1 They probably do on the first one. What matters is what you're getting on trade twenty, when nobody's watching.
competitor_named.2 Pull your last three confirmations from them and let's compare what landed, not what was quoted.
serial_shopper.1 Happy to quote you every time. If you want a better number without the ring-around, give me the flow for the quarter and I'll price it properly.
mid_market_comparison.1 That's the mid-market rate. It's the midpoint between buying and selling, so it's a reference number rather than a price. Nobody trades at it, banks included.
mid_market_comparison.2 The number worth comparing is what actually lands from us against what actually lands from your bank, same day, same amount.
bank_matched.1 Good. That's the first time they've sharpened up for you. Ask them to hold it in writing for the next twelve months and see what comes back.
bank_matched.2 Worth looking at what they were giving you before we started quoting. That's their real price, the one that applies when nobody's pushing.
concession_terms.1 (draft) Volume. A monthly or quarterly commitment that puts them in the band you are pricing them at.
concession_terms.2 (draft) Consolidation. The corridors they currently run elsewhere come to you.
concession_terms.3 (draft) Forward cover. The next tranche booked forward rather than left to shop.
concession_terms.4 (draft) Introduction. The finance contact at the group company, the supplier, the sister office.
concession_terms.5 The rate is one number on one day. What you're paying for is knowing what it'll be worth when it lands and when it'll get there. If you press send and then spend two days chasing, the half pip you saved cost you more than it was worth.

The same ids appear in both playbooks with identical text. That is 15 approved and 4 draft points per playbook.

Drafted lines retired (status: "retired", each with a note naming the rule broken)

Onboarding: show_the_rate_against_mid_market.1, .2, .3; handle_rate_objection.1, .2, .3; handle_alternative_provider.1. CS: handle_rate_complaint.1, .2, .3. Kept as draft, because they fit the framework: handle_alternative_provider.2 (an open question) and .3 ("compare the settled amounts"). A retired line is still refused by the loader like any unapproved line. Delete it once you agree.

Needs Stevan's ruling

  1. said_rate_transparency vs the PDF. The onboarding checklist item applies on every call: mid-market is shown next to your rate, there is no transfer fee, and the margin is inside the rate. The PDF says never explain the spread and never make mid-market the benchmark. I retired all three lines that deliver the item. Do you want to drop or reword it, or keep it for neutral fee questions only (and un-retire .3)?
  2. Core lines. Are the core_text_ids in the table above right? The biggest choice is no_quote_in_hand.2 ("No. That's the price.") over .1.
  3. qualify_first. The PDF sets it only for a named competitor. Should bank_matched (a matched rate is also a competing number) be qualified first too?
  4. Headlines. The mid-market headline drops the PDF's numbers, because a concrete rate is refused in approved text. The bank_matched headline is 9 words, against DESIGN.md's 8-word cap.
  5. Concession terms 1-4 are descriptions, not quoted lines, so they are still draft. Approve them as points?
  6. Alerts. Does a market order count as "forward cover"? Only the CS forward move is linked for now. offer_alert_or_market_order.2 ("What level would you be happy at?") is shown during CS rate concerns, and it could sound like "name your price".
  7. Judgement retirements. Check handle_alternative_provider.1 ("fine for small amounts") and show_the_rate_against_mid_market.2 (a bank quote against mid-market).
  8. rep_improved_rate_unasked is read as "moved on price without getting something back", not "moved on price without the client asking". Is that the right reading?
  9. Authority. The PDF does not say who may move on price, or by how much ("I'll price it properly").

Stevan's rulings (2026-09-25)

  1. Rate transparency stays a must-say, narrowed to "the rate you see is the rate you get, no transfer fee"; the mid-market correction line handles comparisons; no line may explain how the spread is built.
  2. The four concession terms are approved as points.
  3. "My bank has matched you" runs without the qualifying sub-checklist.
  4. Forward cover means a forward contract only; the alert line is never shown while a rate concern is open (blocked_by: rate_pressure_raised on the alert moves).
  5. No guidance yet on who may move on price or by how much; the concession flag only reports a concession that obtained nothing. To be revisited.

Live probe findings (COPILOT-086, 2026-09-25, jev-1.13.0)

After the rep quote "Today's rate on your 20,000 is 1.2690.", each of the five client lines picked its own situation with confidence ≥ 0.95. For the red-pen:

  1. Two lines do not open a concern on their own. client_objecting scored 0.41 on "I'll just check what the others are doing." and 0.17 on "I spoke to my bank and they've matched you.", both under concern_open (0.60). The rate question group is asked only while a rate-type concern is open, so on these two lines the episode waits for a turn that does open one. The other three scored 0.84 to 0.96.
  2. rep_improved_rate_unasked fires on any price move. "Let me see if I can take a few pips off that for you." scored 0.86 after the quote alone and 0.80 after "Can you do better than that?" (the client did ask). "No. That's the price." scored 0.02. So Jev reads the flag as any price move, asked for or not, and without checking whether something was obtained (question 8 above asks for the second reading). The engine treats it as a price move on the table (concession).

L1 rate cases (COPILOT-092, 2026-09-25, jev-1.13.0)

eval/labelled/rate.json holds 25 cases (13 onboarding, 12 CS). node scripts/probe-questions.mjs --cases eval/labelled/rate.json passed 24/25 cases and 46/47 checks. For the red-pen:

  1. rep_rubbished_competitor reads the mid-market correction as a put-down. The approved line mid_market_comparison.1 ("That's the mid-market rate. … Nobody trades at it, banks included.") scored 0.50, above the 0.40 negative bar. Criteria repair: add "explains that the mid-market rate is a reference number nobody trades at" to the false criterion, as rep_explained_spread already does.
  2. Near the line: competitor_named.1 ("They probably do on the first one. …") scored 0.33 on rep_rubbished_competitor. It passes, but the margin is thin. The false criterion already quotes it.
  3. The two client lines that open no concern on their own (serial shopper, bank matched; see above) still pick their situation (0.96 to 0.98) and their client noul (0.85 to 0.94) when asked directly.
2,158 words · Internal working documents. Do not share outside CurrencyTransfer.