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

M1b: live granularity and the adaptive plan

Status: engineering proposal, 2026-09-27, after Stevan's replay review (docs/design/FEEDBACK-2026-09-27.md). Feeds an otto-plan PRD. Everything here comes from the four investigations run that day on the live D1 data, the code at 9cb85fc and the reference copilot, or from the files cited. Where two investigations disagreed, §9 says which figure was kept and why.

A note on the name: the M1 PRD used "M1b" for the secondary M1 deliverables (uploads, the L1 target, L5), all landed in the M1 run. This proposal takes the name for the next milestone. If that confuses the PRD tooling, call it M1.5.

Glossary, once: an utterance is one line of the transcript; a fragment is one raw piece from Aircall; a decision point is a line after which Jev is asked; a noul is a yes/no question Jev scores 0 to 1; a situation is a named pattern in the client's words with approved lines under it; a slot is one client fact (residency, amount); the presenter is the page code that decides when the rep sees what the engine decided; dwell is the minimum time something stays on screen; the Durable Object (DO) is the per-call server process; a subrequest is one outbound call the Worker makes.

Line numbers are the current transcript's (transcript_rev 1) throughout; COPILOT-104b renumbers them and carries the citations over. §10 lists what two independent reviews changed on 2026-09-27.

1. Summary

  1. The transcript and Jev both run once per stitched turn, and this call's turns run to 44.5 s and 112 words. That, not Jev's speed (p50 304 ms), is why nothing felt live. Two changes fix replay: the transcript reveals words as they are spoken (browser only, no cost), and long turns are split at sentence ends under a time rule (96 to 133 decision points, longest wait 45.6 s to 13.5 s on evenly spread word times, $0.034 to about $0.047 per call before the pre-judge questions).
  2. Feedback (ticks, chips, cautions, a new point on the open card) moves at sentence ends. The card itself switches at the end of the client's run of lines, behind one flag so Stevan can flip it.
  3. True partial-then-final decisions are not needed: sentence-sized units give the cadence, and in live audio (M3) the interim words are display-only while the finished sentences are the units, so the same model carries over.
  4. Suggestions vanished for two reasons: the situation gate is re-judged from scratch every client turn and the 20 s dwell protects only the card's title; and a point born on a rep turn can be the thing the rep just said. Four presenter rules (a 12 s point floor, stacking to 3, a sticky last suggestion, a grace on rep turns until the next rep decision) and two engine rules (survive one uncertain miss, pre-judge the next item's fit and its situation choice) fix both without a database change.
  5. The plan needs kinds. A fact ticks silently from any source and has no row; a must-say is listed while it applies and surfaces when due; a judgement item exists only while its trigger fits. On this call the live list drops from 10 rows to about 3 and the after-call score reads 3 of 4 applicable must-says instead of 2 of 7.
  6. Pre-call facts seed the engine at evidence i=-1 through a closed-vocabulary precall_json, snapshotted per session, each with its source: the sign-up wizard via pd_ct_id is M2 (one sample call has the key); on 3339895706 the values are typed in and labelled "Seeded for the demo". "Check we can serve them" and "Understand the transfer" are then satisfied before the first line.
  7. The double tick is one concern cleared by a 3-word fragment and re-raised 1.7 s later as a new row. Fix: never clear on a client line under 4 words, and reopen the same row within 60 s of call time. On this call's recorded answers that leaves one row, raised twice, done at 06:04.
  8. Twenty one-commit stories (COPILOT-102 to COPILOT-118, with 104 and 111 split into a/b and 110 into a/b/c, plus one conditional), 33 otto iterations. Stevan's session and the curation are exported before any re-import (104a). The proposed wording stays outside policy/src until Stevan approves it (110c applies the reply); 107, 114 and 117 render their new lines once their labels are approved and work without them meanwhile. None needs a new Cloudflare service.
  9. Cost: the strong calls land at $0.047 and $0.048 after the split and at about $0.050-0.053 with the pre-judge questions, against the $0.05 gate in scripts/lib/eval-l6.ts:25. Stevan re-sets it to $0.06 or keeps it, in which case the pre-judge questions are the first drop under budget (decision 16). Live transcription (M3) is on top: $0.090 for this 587 s call at nova-3's $0.552 an hour.
  10. Live audio stays M3. nova-3 streaming is the only Cloudflare-hosted option with interim words and a speaker per word; it needs the paid Workers plan (the free allowance is about 12 minutes a day) and an unverified Aircall audio transport; a one-day transport spike (media stream or separate legs, against Aircall's real-time transcription webhook) belongs before M3 planning, not in M1b.

2. What was measured

All on call 3339895706 (onboarding, 587 s) unless noted; Stevan's session 1d160d96 equals run 49e3e48f decision for decision (every one a cache hit).

Measure Value Source
Fragments → utterances → decision points 159 → 122 (96 turns, 26 backchannels) → 96 D1 utterances
Decision points by speaker client 48 / rep 48 (CS-strong 66 / 65) D1 utterances
Derived rows a forced re-import deletes on 3339895706 3,472 decisions in 52 sessions (Stevan's 1d160d96: 86, run 49e3e48f: 96), 391 moments, 3 marks; 1 label on 3303259297 D1; src/routes/calls-import.ts:24
Utterance length: median / p90 / max 1.0 s / 14.4 s / 44.5 s forensics
Utterances of 15 s or more 11, holding 309.6 of 619.2 s of speech, 45% of words forensics
Word-weighted wait from spoken to visible 9.1 s (CS call 3303259297: 6.0 s) forensics
Gap between decision points: median / p90 / max 1.8 s / 16.0 s / 45.6 s reference report
Topics in the five longest client turns 19 (5, 4, 4, 3, 3), one decision each forensics
Jev provider latency, 5,655 live full requests p50 304 ms, p95 413 ms, p99 798 ms; flat against size (319 ms at 6k tokens, 346-365 ms at 13-15k) D1 jev_requests
End-to-end p95 at 1x, 8 eval runs 448 to 3,506 ms; latest run 2,042 ms eval reports
Input tokens per full request median 7,797, mean 8,489, p95 9,367, max 10,519 (CS-strong mean 6,993, max 9,323) D1 jev_requests
State share of a request mean 1,838 characters (about 525 tokens) of a 31,448-character request reference report
Decisions that fell back to window 8 (soft budget 11k) 365 of 3,472 on this call across runs reference report
Cost per call today (eval cost_by_call) $0.034 onboarding-strong, $0.038 CS-strong, $0.017 onboarding-weak, $0.007 CS-weak eval/reports/recc-20260927.l4l6.json
Decisions with the engine's move card shown / with an open plan point 50 of 96 / 12 of 96 (3 distinct cards) forensics
CS call: decisions with an open point, 3 runs 0 of 131 forensics
Card spans under 6 s 4 of 17 presenter simulation
Amount point on screen tinted 1.0 s (needed 6.0 s at 2.5 words/s) presenter simulation
First-transfer-date point on screen 23 s, then withdrawn uncovered simulation, code path
Card chosen at 463.8 s, shown at 513.2 s 49.4 s wait (dwell + a 41 s rep monologue) simulation
Presenter delay on the other 10 of 12 card switches ≤ 0.1 s simulation
Reference copilot: decisions per minute / card shown about 5 / on 34-40 of 38-42 lines reference report

3. Live granularity (a)

3.1 The unit model

Today: fragment → stitched turn (merge same-speaker fragments under a 1.0 s gap, 2.5 s when unfinished, up to 120 words; split only above 120 words, src/ingest/stitch.ts:59, 161-171, 193-213) → one decision per turn. The browser reveals a line when the clock passes its t_end (web/src/transcript.ts:209-212) and sends utterance{i} at the same moment (web/src/clock.ts:429, web/src/player.ts:108-113).

Proposed, the same model in replay and live:

Unit What it is Who produces it What moves on it
word a word with an estimated (replay) or real (live) time browser (replay), STT interim result (live) transcript text
part a sentence-sized piece of a turn, at least 6 words and 5 s, forced at 12 s stitch() v2 (replay), STT finals through the incremental stitcher (live) one decision: ticks, chips, caution, points on the open card
turn a same-speaker run of parts stitch() nothing extra
run end the last client turn before the rep speaks derived in the presenter from the utterance list (replay); in live, known when the other speaker's first fragment arrives or the STT endpoint fires, about a second later the card may switch; items change state

The reference copilot used one unit, the finished utterance, at about 5 a minute, and its live-mic mode showed interim words without sending them to Jev (static/index.html:454-462). We already decide at 9.9 a minute on this call. The gain is bounding the worst case, not deciding more on average.

3.2 Replay now

T1. Transcript words as they are spoken (browser only). The row for line i is inserted when the clock passes u.t, and its text grows by floor(words × (t − u.t) / (u.t_end − u.t)) words. Aircall gives no word times (the sample's keys are i, start, end, role, text), so words are spread evenly inside each fragment, and only the fragment boundaries are real; this call runs at 3.0 words a second. Inside a long fragment (up to 41 s here) a pause shifts the words after it by the pause's length, so the reveal is an animation of an estimate, not a measurement, and the gap figures below inherit the same estimate; they are re-measured on real word times once nova-3's words are kept (open question 11). Zero Jev cost. Honest: nothing appears before its estimated spoken moment, unlike the reference's "show the line at its start and cap silences at 6 s" (server.py:27, 196-204), which we reject because it shows text before it was said and cannot carry to live.

T2. STITCH_V2, the split rule. Inside a merged turn, cut a part at a sentence end once the part has at least 6 words and its estimated span is at least 5 s; force a cut at the first sentence or clause boundary after 12 s. Each part's t is the previous part's t_end (today every part keeps turn.t, stitch.ts:205). Parts are ordinary kind:'turn' utterances, so the protocol, the DO, the engine and the decision table need no change. contentHash includes stitch_version, so a re-import is a new transcript_rev and a one-off paid re-run.

Measured on the real D1 lines (word times spread evenly):

Call Decision points today → v2 Per minute p90 gap Longest gap Cost today → v2
3339895706 onboarding-strong 96 → 133 9.9 → 13.7 16.0 → 10.4 s 45.6 → 13.5 s $0.034 → $0.047
3303259297 CS-strong 131 → 165 12.1 → 15.2 12.4 → 8.7 s 56.3 → 13.4 s $0.038 → $0.048
3485591407 onboarding-weak 50 → 68 8.0 → 10.8 15.4 → 10.5 s 31.8 → 13.2 s $0.017 → $0.023
3347356034 CS-weak 22 → 44 4.1 → 8.2 31.2 → 11.1 s 81.4 → 75.0 s (a silence) $0.007 → $0.013

Cost is the eval run's measured cost_by_call scaled by the decision count (mean 8,489 input tokens per request on the onboarding call, 6,993 on CS-strong, at $0.042 per million). The decision-count ratio runs from 1.26x (CS-strong) to 2.0x (CS-weak), 1.39x on the strong onboarding call, so the thresholds in COPILOT-106 are re-tuned by the eval rather than by one multiplier. The alternative rule, split every 25 words, gives 152 points on the strong call and $0.050; every sentence gives 27.8 a minute. The time rule is kept because it bounds the worst case with the fewest extra decisions (§9).

Partial versus final. A true partial (partial{i, upto_words} stepping a copy of the state, superseded by the final) was costed at 4-6 days: a new message, a part column or unpersisted decisions (which breaks "every decision is replayable", INV-COPILOT-001), and presenter rules for supersession. A light partial request would be 2-3k tokens (about $0.0001), but the sentence split already gives sentence-level cadence, and in live audio the interim words are display-only while the final segments are the units. Partials are therefore not proposed for M1b or M3.

Window and budget. The window is 12 utterances including backchannels (src/engine/state.ts:63-84), which on this call covers a median of 58 s and 180 words; the reference's 12 covered 161 s and 537 words. Sentence parts shrink it further. The state is only about 525 of 8,489 tokens, so widening is cheap in tokens: M1b raises the window to 16 units and keeps the soft 11k / hard 12k budget. Defining the window by time or words (about 120 s or 400 words) and raising the hard cap to about 16k is a policy decision for Stevan: Cloudflare served 1,599 eval requests up to 15,674 tokens without error and latency is flat, but the 12k cap is our own (architecture D4). The pre-judge questions of §4 (up to three fits:: at 246-292 tokens each and one situation:: Choice at 361-397, like-for-like §3.4) add about 1,200 tokens to a client-part request, about 650 averaged over all decisions, and get a drop step of their own, prejudge, ahead of fact_values (today's first drop, budget.ts:117-123).

Latency and subrequests. Per decision nothing changes: provider p95 413 ms, end-to-end p95 about 2 s at 1x on the latest run. Parts inside a turn are 5-12 s apart, so the serial chain does not queue at 1x. The DO still spends 2 subrequests per decision; at 20x the 50-per-invocation guard (src/session/subrequests.ts:30-34) reconnects after about 23 decisions, so a 133-decision replay needs about 5 reconnect{after_i} round trips (133 ÷ 23) instead of 4; COPILOT-104b quotes the measured count. The COPILOT-058b path handles it. The rep's wait is the part boundary plus arrival plus the presenter, so COPILOT-118 measures spoken-to-visible and sentence-end-to-first-feedback end to end at 1x; the 13.5 s bound above is the boundary alone.

Thresholds counted in decisions. With about 1.4x more decisions: concern_max_age 8 → 12, purpose_max_decisions 8 → 12, card_cooldown_utts 3 → 4, COVERED_DWELL 4 → 6, window 12 → 16; confirm_updates 2, stage_confirm 2 and ema_alpha 0.4 are kept on purpose (two confirmations at a faster cadence is what a sentence-level card should need) and checked by the same run; the new reopen window (§6) is in call seconds, not decisions. Re-tuned by one eval run (L1 pass rate at or above the 2026-09-27 baseline, L4 and L6 pass, window_to_8 fallbacks at or below today's 10.5%).

3.3 The cadence rule

Element Moves at Rule
Transcript text word time T1
Fact chips, point ticks, caution, purpose arrival of any decision (sentence end) unchanged (presenter.ts:17-18)
A new point on the open card arrival of a client-part decision new; today it waits for a client turn end
A must-say that becomes due on a client signal (safety asked, ready to book) the end of that client run, no dwell treated as an inserted item, like a concern (presenter.ts:164-170; COPILOT-111b marks it)
The card switching, item states the end of the client's run, then the 20 s dwell, then not during a rep turn LIVE_CARD_AT_SENTENCE_END = false; Stevan's decision 1b flips it

3.4 Live audio later (M3)

4. Card lifetime rules (b)

Why points vanish today, with the file and line:

Cause Where Seen on the call
E1 The gate is re-judged from scratch on every client turn; below fit 0.6 the card shows "Listening" src/engine/situation.ts:152-157, 184-194, 232, 243 line 17: fits fell to 0.05 and 0.11, the date point left at 133.7 s
E2 A switch to another situation of the same move replaces the points plan.ts via the fit line 13: amount → first-transfer date
E3 A new current item starts with empty fits, so one decision of "Listening" situation.ts:210-212 line 8 → 9: the amount point arrived after the rep had asked it
E5 A point with a slot requirement stays hidden until the slot is known plan.ts:593 works-abroad fitted at 0.67 and 0.69 but {country} was unknown
P1 The dwell protects the card's id, not its points web/src/presenter.ts:164-170, 176-193 the two withdrawals above
P2 A plan decided on a rep turn is applied at the next change moment presenter.ts:153-157 the stale amount point at 100.3 s

Proposed rules, all inside G's one-card shape (one card, one tag, points only on the current card, no new colour):

Rule Constant What the rep sees
R1 Point floor LIVE_POINT_MIN = 12 s of call time a revealed point keeps its slot, tinted or grey, for at least 12 s when a card switch or one uncertain miss (R5) drops it; then it fades. A point the rep covers folds as today, and one whose situation the engine drops on a clear miss (fit under 0.3) leaves within a second, floor or not
R2 Stacking LIVE_POINTS_MAX = 3 a newer situation's point is added under the older one; the oldest un-floored point leaves first, and when a fourth arrives the oldest leaves at once, floor or not
R3 Sticky last suggestion LIVE_LAST_SUGGESTION = 12 s after a card switch, the previous card's newest uncovered point stays as one grey "Last suggestion: ..." line under the new card
R4 Rep-turn grace until the next rep-turn decision, at most LIVE_REP_POINT_GRACE_S = 3 s a point first introduced by a decision on a rep turn is held until the next rep-turn decision arrives (only rep turns carry covered::, plan.ts:302-305); if it arrives covered it renders grey, never tinted. Line 9's point: the next decision is the 2-word client line 10, which carries no tick; the tick comes with line 11 at 100.9 s, 1.7 s after line 9's, so a one-decision hold would still have flashed it
R5 Gate hysteresis (engine) one uncertain miss a shown situation survives one answer in [0.3, 0.6) or one dropped question and leaves on the second; it leaves at once on an answer under 0.3 (a clear "not this", like the delivery-date must-not case in like-for-like §4.1) or when a different candidate fits at or above 0.6. This amends like-for-like §3.3 (decision 17)
R6 Pre-judged fits (engine) up to 3 fits:: and one situation:: Choice per request the item about to become current has its fits, and its situation Choice when it has two or more candidates, asked before it opens (shownSituationId needs the Choice as well as a fit, situation.ts:228-246; ask_about_the_transfer has two situations), so a new card opens with points, not "Listening"

R1 to R4 are presenter-only: pure functions of the clock, deterministic under replay, no D1 change. R5 and R6 change the state and the question set, so they are a paid re-run and need the eval gate. DESIGN.md §Cadence rules gains rules 10-12 and the constants table gains the four values. The "two or three things worth addressing in one turn" is R2 inside G's card, not a second card. Three 15-word points are about 18 s of reading at 2.5 words a second, so the shotgun board before COPILOT-107's screenshots decides between a cap of three and two (§8).

5. Plan semantics (c)

5.1 Item kinds

Kind Satisfied or triggered by Row in the live list Counted
fact its slots known from sign-up, the CRM or either speaker (satisfied_by) none once known (it becomes a chip with its source); a card only when surface_when fires: the value is outside the served list, sign-up and the call disagree, or unknown after the discovery stage never
must_say the rep's lock (done_when: must_say), any wording listed while applies holds; the card when due fires (a signal such as client_ready_to_book or client_asked_about_safety, or a slot test such as an amount leg below the minimum) after the call: "3 of 4 that applied"
judgement its situation or trigger fitting the card while it fits (with the §4 floor); no row before or after never

Data sketch (policy, plan-<scenario>.json, under weights_hash, so no Jev question changes):

{"id":"check_serviceability_first","kind":"fact","satisfied_by":{"slots":["residency"],"rule":"served"},"surface_when":{"any":[{"slot_outside_served":"residency"},{"unknown_after_stage":"discovery"}]}}
{"id":"explain_booking_is_binding","kind":"must_say","applies":"always","due":{"signal":"client_ready_to_book"},"done_when":[{"must_say":"said_booking_is_binding"}]}
{"id":"explain_who_holds_the_money","kind":"must_say","applies":"always","due":{"any":[{"signal":"client_asked_about_safety"},{"signal":"client_ready_to_book"}]},"done_when":[{"must_say":"said_who_holds_funds"}]}
{"id":"confirm_funding_account","kind":"must_say","applies":{"slot_in":{"funding_account":["third_party","company_account"]},"or_unknown":true},"due":{"signal":"client_ready_to_book"},"done_when":[{"must_say":"said_fund_from_own_account"}]}
{"id":"explain_settlement_and_cutoff","kind":"judgement","surface_when":{"any":[{"slot_in":{"timing":["this_week","this_month"]}},{"signal":"client_asked_about_timing"}]}}

Every signal, slot and must-say id named above exists in the policy today (client_asked_about_safety, client_asked_about_timing and client_ready_to_book in rules-onboarding.json; funding_account and timing in plan-onboarding.json; the said_* ids in the must-say group), so the grammar changes no Jev question and no hash but weights_hash. What does not exist yet: a bank_country slot (a new fact question, so a bank change with its own L1 case and a paid re-ask, COPILOT-110b), and a served-country list (served_countries in the scenario bank, empty until Stevan or the partners supply one; with an empty list a known residency satisfies the fact and nothing is "outside served"). A kind is single-valued: confirm_funding_account is a must-say whose applies reads the funding-account fact, not a fact that turns into a must-say. agree_next_step stays a fact locked by the client's words only (next_step_agreed persists on client turns, rules-onboarding.json:308, COPILOT-024).

The must-say checklist in rules-onboarding.json:20-104 is derived from the must_say items at build time, so the two lists cannot drift. The kinds for the ten first-call items, and the proposed wording, are in FEEDBACK-2026-09-27.md §2d; nothing ships until Stevan approves the labels (the loader hard-rejects drafts, INV-COPILOT-003).

5.2 Pre-call facts

5.3 Silent satisfaction, never-applicable items, the review view

6. The duplicate tick (d)

Trace: concern:jurisdiction:54 raised 05:08 (line 54, "worried ... being a tax resident"), closed 05:26 when the 3-word fragment "Sure. Well, I" (line 56) scored as client_accepts at or above 0.6 (src/engine/concern.ts:120); concern:jurisdiction:58 raised 05:27 on "both. I'm just more hesitant" as a new episode (concern.ts:110 sets start_i = u.i when the previous one is closed), inserted as a new row by plan.ts:445-459 (existing matches only the same start_i), closed 06:04. Both carry concern.jurisdiction.headline (policy/src/playbook-onboarding.json:2056). Not a re-template (the purpose locked at line 1 and template ids are shared, plan.ts:259-274), and not a client ask (those dedupe by move, plan.ts:495).

Fix, in order of leverage:

  1. Reopen instead of re-insert. A raise of the same type within concern_reopen_window_s (60 s of call time) of the last close keeps the previous start_i; the done row returns to current with done_at: null and reads "raised again 05:27". In seconds, not decisions: 6 decisions are about 36 s today and 26 s at 13.7 a minute.
  2. Clearing guard. A concern clears only on a client utterance of at least 4 words; the Jev signal (client_accepts ≥ concern_clear) is still required, the guard only says which lines may carry it. No mid-sentence or run-end test: the engine cannot know a turn ends its run until the next one arrives (lastOfRun exists only inside stitch(), stitch.ts:190, and is not stored). STITCH_V2 does not merge across speakers, so a fragment like line 56 still stands alone; the word count handles it. Measured on the recorded answers of this call: line 56 scored client_accepts 0.71-0.77 in every run and no longer clears; line 58 (client_objecting 0.75-0.81) then re-raises the open concern (raises 2, same start_i 54, concern.ts:102-110), so there is no reopen on this call; line 64 "It is. It's very" (4 words, client_accepts 0.70-0.75) clears it at 364.3 s, as today. One row, raised twice, done at 06:04. The reopen path is tested on a synthetic transcript.
  3. Gate. The jurisdiction concern cannot open while residency is known and on the served list (§5.2). The policy has no served list today (grep -ri served policy/src finds nothing), so it starts empty and the gate is inert until Stevan or the partners supply one; keyed on "known" alone it would silence the concern for every recognised country and lose the "we can't serve you" exit (FEEDBACK 2c). With SA on the list, no episode opens on this call; without it, one row (fixes 1-2).
  4. A situation for the real worry. The policy has no tax-residency or privacy concern type (policy/src/onboarding.json:246-252 lists the ten), so the classifier chose jurisdiction. A document situation "Will this address tie me to that country?" is drafted for the red pen (FEEDBACK-2026-09-27.md §2b), with its line marked [VERIFY], so it stays out of policy/src until verified. A concern type of its own (tax residency, privacy) would be a bank change (a new classifier label); M1b drafts the situation, M2 decides the type from the corpus.

Recompute is approximate here because concern_open feeds the state Jev saw (architecture §5), so the fix is verified by a replay, not a recompute.

7. PRD sketch (e)

Format as in prd.md §5. Every story is one commit. An otto iteration is one otto pass over one story (one context window, one commit); the estimate is 2 where a live probe or an eval run is likely to need a second pass. Executor claude:opus throughout. Proposed wording never enters policy/src before Stevan's reply: the loader rejects any unapproved text (src/policy/loader.ts:4-9, INV-COPILOT-003), so a draft under an approved id would stop every build, v4 included. Proposals live in policy/proposals/2026-09-27.json and on policy/LABELS.md; COPILOT-110c applies the reply (decision 2f). A story marked [LABEL PENDING] renders its new line only once that label is approved and works without it meanwhile.

Phase A: live granularity in replay

Phase B: card lifetime

Phase C: plan semantics and pre-call facts

Phase D: the duplicate tick

Phase E: verification

Sizing and order

Phase Stories Otto iterations Blocked on
A live granularity 102, 103, 104a, 104b, 105, 106 9 decisions 1a (rule), 1b (flag default) and 16 (cost gate)
B card lifetime 107, 108, (109), 120 5 (+1) decision 17 (band rule); decision 11 option B for 109; the shotgun board for 107
C plan semantics 110a, 110b, 110c, 111a, 111b, 112, 113, 114, 115, 116 17 Stevan's reply on the wording (110c; 114 renders today's words until then), decisions 2a-2h, the served list (110b)
D duplicate tick and the rate false positive 117, 119 2 119 on 104a
E verification 118 2 all
Total 22 (+1) 35 (+1)

Start with 102, 103, 104a, 110a, 112 and 117 in parallel: no dependencies between them (117's test needs 104a's fixture; its code does not), and 117 edits rules-*.json while 110a edits plan-*.json, so they do not collide. The critical path is 103 → 104b → 106 → 108 → 118 (about 10 iterations). 110c waits on Stevan and gates nothing but the new wording.

8. Risks and open questions (f)

Risks

Risk Effect Mitigation
A forced re-import deletes every derived row of the call (calls-import.ts:24: decisions, marks, moments, labels, answers) and renumbers i; marks, labels, the L1 ground truth, the demo script and the acceptance runbook are keyed by i Stevan's session and the curation gone, L1 cases and the demo wrong COPILOT-104a exports the two sessions and the curation before anything is re-imported; 104b re-inserts the marks and labels remapped by time overlap (a label to the part holding its quote's end) and regenerates the demo script; the v1 fixtures stay; a probe compares mark and label counts before and after
Every state change (seeded facts, hysteresis, pre-judge, concern) invalidates the cache a paid re-run per story, about $0.05 a call trivial in money; each story's eval run is fresh
The strong calls land at $0.047-0.048 after the split and about $0.050-0.053 with the pre-judge questions the $0.05 per call gate (eval-l6.ts:25) trips decision 16: Stevan re-sets the gate to $0.06 (COPILOT-106 edits L6_LIMITS) or keeps it, in which case prejudge is the first drop and 108 measures what that costs in empty cards
Word times inside a fragment are interpolated a pause inside a long fragment (up to 41 s here) shifts the words after it by the pause's length; the v2 gap figures inherit the estimate exact enough on short lines; real times arrive with nova-3 uploads (open question 11) and in M3, and 118 reports the gaps again on them
Hysteresis keeps an uncertain situation one decision longer one stale point for one decision, only for fits in [0.3, 0.6) a clear miss (under 0.3) still leaves at once; measured in COPILOT-108
Three stacked points in G's card about 18 s of reading at 2.5 words a second while listening a shotgun board before 107's and 114's screenshots decides between three and two; oldest leaves first
Pre-call facts can be wrong (the client changed plans since sign-up) a stale chip a confident different value on the call replaces it with "updated"; a disagreement surfaces the fact card
The jurisdiction gate needs the partners' served-country list until then the gate is inert and the concern still opens on this call (one row after 117) Stevan supplies the list (open question 9); the gate is never keyed on "known" alone, or the "can't serve" exit is lost
The new wording waits on Stevan's reply the guide framing (114) and the sticky and reopen lines (107, 117) ship with today's words until then proposals stay outside policy/src, so every build stays green; 110c applies the reply in one commit
More decisions at 20x hit the subrequest guard sooner about 5 reconnects per call instead of 4; a cold seek late in a long call still re-steps 0..i in one invocation (architecture §4 follow-up) the COPILOT-058b path; measured in 104b; 118 warms the cache at 20x before the 1x walk
Uploaded calls keep 45 s turns if the Worker ingest stays on v1 uploads never show the sentence cadence 104b moves ingest.ts:381 and speaker-map.ts:308 to STITCH_V2 and bumps the bundle's ingest version (build.ts:120); the four existing uploaded copies of this call stay v1 unless re-transcribed with COPILOT-065's {force: true}
Run-to-run variance on cache misses (residency in 2 of 4 runs, rate versus fees at line 30) the next replay differs from the last seeding removes the residency variance; the rest is Jev's, tracked by the eval

Open questions

  1. Which reference mode did Stevan compare against: replay (silences capped at 6 s) or the live mic (interim words)? It decides whether T1 alone answers most of point 1.
  2. Should the card itself switch at a sentence end (flag default in COPILOT-105)? Recommended no.
  3. Raise the hard token cap from 12k to about 16k so the window can cover the reference's span (about 160 s)? Cloudflare served requests up to 15,674 tokens with flat latency; the cap is ours.
  4. Which pre-call source and fields, and is there consent to show them to reps? The wizard through pd_ct_id is recommended; the sample calls lack the key.
  5. Multi-leg amounts and pairs (the Canada leg, under the minimum): an M2 policy change or pulled into M1b? Recommended M2, because it changes the chip templates and the minimum rule together.
  6. Is the 3-word "Sure. Well, I" scoring as client_accepts a Jev judging problem (the noul's criteria) or purely a stitching problem? The guard fixes both; the criteria could still be tightened in the bank (a paid re-ask).
  7. Does an employer or HR letter count as proof of address for the activating partner? Needed before the tax-residency line can be approved.
  8. The rep's line 55 "we don't share it with anyone else": approved wording or a caution?
  9. The served-country list per partner, for the jurisdiction gate and the serviceability fact: until it exists the gate is inert and the fact is satisfied by a known residency.
  10. M3 transport: does Aircall expose a live media stream or separate legs, and does its real-time transcription webhook (named by a reviewer, unchecked) carry both parties with usable timing? A one-day spike against a real Aircall call, before M3 is planned. And is the paid Workers plan acceptable for nova-3 streaming, at about $0.09 per ten-minute call on top of the engine?
  11. Should nova-3's real word times be kept for uploads now (4-6 h) so uploaded calls get exact sentence splits before M3, and the gap figures of §3.2 can be re-measured on real times?
  12. A correction mid-call versus a second transfer: one amount slot cannot tell them apart (the chip reads "updated"; the second leg is lost). Multi-leg slots (M2) fix the second; the first needs a value history, also M2. Until then 111b keeps the minimum item open once a second amount is heard.
  13. Do reps need a dismiss or defer control on a suggestion? A question for the first team trial.
  14. Hold music, talk-over and non-English stretches have no defined behaviour; the M2 corpus shows how often they occur. On Aircall transcripts talk-over lands as interleaved short lines (lines 55-60 here), which the 4-word guard and the run-end rule absorb.
  15. LIVE_POINTS_MAX: three or two? Decided on the shotgun board before COPILOT-107.

9. Where the investigations disagreed, and what was kept

Point Figures Kept Why
Cost per call today $0.031 (96 × median 7.8k tokens × $0.042/M) vs $0.034 (eval cost_by_call) $0.034 measured on actual token counts (mean 8,489), not the median
Split rule every 25 words (152 points, $0.050) vs sentence end after 6 words and 5 s, forced at 12 s (133 points, $0.047) the time rule, implemented inside stitch() as the 25-word proposal was bounded worst case with the fewest extra decisions, under the gate before the pre-judge questions
How far Stevan played 56 decisions to line 69 vs 86 decisions to line 107 both queried at different times; he played on after a pause; his remarks concern the first seven minutes
Time the first-transfer-date point was on screen about 19 s (raw decision times) vs 23 s (presenter simulation) 23 s the simulation runs the real presenter.ts; either way it was readable and the fault is the uncovered withdrawal
What text vanished the caution fade or covered-point fold (a guess from the rules) vs the amount point at 1.0 s (measured) the measured point no caution has ever fired on the sample calls
Presenter change moment after splitting "must become the end of a speaker run" vs "keep turn end, or allow sentence end" run end for the card, sentence end for points, behind a flag matches G's rule 1 and Stevan's ask at once; the flag makes 1b one line
Show each line at its start and cap gaps at 6 s, like the reference proposed as a "more live" option rejected shows text before it was said; cannot carry to live audio
Where the jurisdiction concern should be fixed engine reopen and clearing guard vs a code gate on known-and-served residency vs a view-only collapse the first two; the gate once the partners' served list exists (inert until then); the collapse only as a fallback the view fix would hide two engine episodes rather than prevent them

10. Changes after the 2026-09-27 reviews

Two independent reviews (one against the code and the live data, one against the design) were applied the same day. What changed, and where:

  1. A forced re-import deletes every derived row of a call (calls-import.ts:24), so the fixtures 107 and 117 need, Stevan's session and the marks and labels are exported first (COPILOT-104a) and re-inserted remapped (104b); labels map to the part holding the quote's end; the demo script is regenerated. The Worker ingest moves to v2 with it (ingest.ts:381, speaker-map.ts:308, build.ts:120).
  2. The cost gate is $0.05 in eval-l6.ts:25, not $0.055; the pre-judge questions cost about 1,200 tokens per client-part request (like-for-like §3.4), not 300, so the strong calls land at about $0.050-0.053; decision 16 and COPILOT-106 own the gate. Live transcription is a separate $0.09 per ten-minute call.
  3. R6 pre-judges the situation Choice as well as the fits (shownSituationId needs both); the pre-judge questions get their own drop step ahead of fact_values. R4 holds a rep-turn point until the next rep-turn decision (line 10 is a client line and carries no tick). R5 applies only in the [0.3, 0.6) band; a clear miss leaves at once (decision 17). The point floor yields to a cover, a hard drop and a fourth point.
  4. The clearing guard is a word count only (no run-end test: the engine cannot see the next turn); measured on the recorded answers, this call yields one row raised twice, no reopen, so 117's test says so and the reopen path uses a synthetic transcript. The reopen window is in call seconds.
  5. The served-country list starts empty, so the jurisdiction gate is inert until the partners' list arrives and the "can't serve" exit is kept; 113, 117 and 118 no longer contradict each other on the episode count.
  6. Seeded facts on the sample call carry source demo and the label "Seeded for the demo", never "From sign-up"; the wizard has no residency field (KYC address); 3485591407 has a real pd_ct_id for the M2 join. The 112 probe uses the policy's label ids (salary_or_pension, proof_of_address_outstanding) and supplies the amount that understand_the_transfer needs.
  7. The plan grammar names only signals and slots that exist; bank_country is a bank change of its own (110b); confirm_funding_account is a must-say with an applies rule; agree_next_step keeps the client-only lock; 110 and 111 are split into one-commit stories.
  8. Proposed wording stays outside policy/src (the loader rejects drafts and [VERIFY]); 110c applies Stevan's reply; the [VERIFY] line is never covered by "approve all"; the tax-residency point uses {country}.
  9. Option B of decision 11 (the FEEDBACK's decision 3, COPILOT-109) is restated honestly (no approved flat lines exist to restore; it loosens the like-for-like rule) and the FEEDBACK attributes the gate to the like-for-like restructure, not the rate one. The "{country} copy bug" was not one (render.ts:87 already renders the display name) and is gone.
  10. Interpolated word times are an animation, not a measurement (a pause inside a 41 s fragment shifts words by the pause); the live mapping no longer equates a Deepgram final with a part; the reconnect estimate is 5, not 6; 118 measures spoken-to-visible and sentence-end-to-feedback end to end, warms the cache before the 1x walk, and counts withdrawn points; judgement items are not scored anywhere; a second amount keeps the minimum item open; a shotgun board precedes 107's and 114's screenshots.

Rejected: "word count is not resolution evidence" (the Jev client_accepts signal is still required; the guard only filters which lines may carry it), "the 12 s floor keeps contradicted advice" in its strong form (the engine has no contradiction signal beyond the fit; a fit under 0.3 now removes the point at once), and "a safety concern waits out the dwell" (an inserted client item never waits for the dwell, presenter.ts:164-170; a must-say due on a client signal is now treated the same).

15,029 words · Internal working documents. Do not share outside CurrencyTransfer.