Rendering... (320+ nodes, ~6-10s)
flowchart TB
%% ================== Entry ==================
Start([👤 Human operator uploads
contract + invoice
D-007]) --> P1
%% ================== Pre-flight ==================
subgraph PreFlight[1️⃣ Pre-flight Agent]
direction TB
P1[Extract contract clauses
governing law / arbitration forum / late-fee rate
+ deposit_pct]
P1Dep[💡 D-038 outstanding, NOT gross
Lawyers chase gross. We chase outstanding.
routing target = invoice − deposit_paid
e.g. 47300 gross × 50% deposit → 23650 outstanding]
P1 --> P1Dep
P1Dep --> P1A{Outstanding falls in which range?
D-031 sweet-spot × D-038}
P1A -- "outstanding < $3K" --> Lite[🪶 Lite mode
Concierge 2-button card
Run Lite / Write Off]
P1A -- "$3K-$40K outstanding ✅" --> InSpot[💼 Main flow — the demo]
P1A -- "outstanding > $40K" --> Attorney["⚖️ Attorney-recommended mode D-021
Concierge 2-button card
Run full flow + attorney tag
OR provide 3 attorney referrals only
Note: we take ZERO referral fee"]
P1B{Contract has governing-law clause?}
P1 --> P1B
P1B -- Yes --> Continue1[Load that jurisdiction's RAG]
P1B -- No --> Fallback[Concierge 3-choice fallback:
Amicable / NY law / AAA arbitration]
P1C["D-035 derives arbitration_forum_spoken
for Voice Phase 5
e.g. the American Arbitration Association in San Francisco"]
P1 --> P1C
end
Lite --> LiteDip[Lite runs Diplomat only
no voice call, no demand letter]
Attorney --> AttRefer["Pull 3 attorney referrals from config
D-021: 0% referral fee, pure goodwill"]
InSpot --> Inv
%% ================== Investigator ==================
subgraph Investigator[3️⃣ Investigator Agent — D-025]
direction TB
Inv[Auto-runs at case start]
Inv --> T1a[Tier 1a internal history
last 6 months of customer_replies
spec §3 Investigator]
Inv --> T1b["Tier 1b external public records
US Secretary of State + OpenCorporates
❌ Skip on API failure, never block flow"]
T1a --> PT{Customer pattern_tag classification
6 categories — D-026}
T1b --> PT
PT -- prior escalation --> Alert[🚨 Red alert
Pre-warm Escalator on Day 0]
PT -- chronic late --> ToneFirm[Diplomat opens firm]
PT -- occasional drift --> ToneStd[Diplomat opens standard]
PT -- clean record --> ToneMax[Diplomat opens softest]
PT -- new customer --> ToneStd2[Diplomat opens standard]
T2[Tier 2 deep-dive on demand
LinkedIn + CourtListener litigation]
end
Alert --> Dip
ToneFirm --> Dip
ToneStd --> Dip
ToneMax --> Dip
ToneStd2 --> Dip
%% ================== Diplomat Cadence + Scheduler ==================
subgraph Diplomat[2️⃣ Diplomat Agent + D-035 case-state gate]
direction TB
CronStart["⏰ Cron scheduler — daily KST 09:00
Sweeps all active cases
Finds cadence steps due today
D-032 + D-007"]
CronStart --> CadenceList[List today's due Day -7/0/7/14/30 cadence]
CadenceList --> Dip[Diplomat receives each cadence trigger]
Dip --> StateGate{🚨 Case state?
D-035 mandatory gate}
StateGate -- normal active --> Draft[Draft email]
StateGate -- frozen --> Pause[outreach_revoked event
Pause sending]
StateGate -- human_owned --> RejectD[auto_reject_post_handoff
case belongs to operator]
Draft --> Tone
Pause --> WaitUnfreeze[Wait for case_unfrozen broadcast
or /case-unhold command]
RejectD --> AuditOnly[Audit-only, no action]
end
%% ================== Tone Coach + reject loop ==================
subgraph ToneCoachSeg[5️⃣ Tone Coach — Claude pinned, D-029]
direction TB
Tone[Check email draft tone]
Tone --> ToneCheck{Tone score ≥ 8.5?}
ToneCheck -- pass ✅ --> Q
ToneCheck -- "❌ too aggressive
or off Judy's voice" --> ToneReject[Audit: tone_rejected
with reason]
ToneReject --> ToneFix[Diplomat receives revision hint
e.g. opening too commanding, add 'hope you are well']
ToneFix --> Draft
end
%% ================== Pre-Approval Queue (TCPA windowing) ==================
subgraph QueueSeg[6️⃣ Pre-Approval Queue — D-032]
direction TB
Q[Schedule to customer local time
email 1h / voice 4h / demand-letter 8h pre-approval]
Q --> TCPAcheck["TCPA safe-time window check
US local: Tue/Wed/Thu 10-11 AM OR 2-3 PM
HARD BLOCK: customer local 19:00 - 08:00
Skip: US federal holidays"]
TCPAcheck -- in window --> Conc
TCPAcheck -- "out of window (e.g. customer local midnight)" --> QueueDelay[Defer to next legal window
Concierge shows wait time to Judy]
QueueDelay --> Conc
end
%% ================== Concierge + SLA matrix ==================
subgraph Concierge[8️⃣ Concierge Agent — D-024 + D-035]
direction TB
Conc[Receives approval request]
Conc --> PriorityCheck{Priority?}
PriorityCheck -- standard --> Daily[Bundle into daily KST 09:00
digest — no immediate push]
PriorityCheck -- high_24h --> Push24[Slack push + TG DM immediately]
PriorityCheck -- critical_immediate --> Page5[Page operator within 5 min
Slack @here + TG + optional phone]
Push24 --> Watch1{Human ack within 22h?}
Watch1 -- yes --> Done1[Resolved]
Watch1 -- no --> Nudge[T-2h nudge:
'SLA expires in 2h']
Nudge --> Watch2{Ack within 24h?}
Watch2 -- yes --> Done1
Watch2 -- no --> Breach[sla_breached audit event]
Page5 --> Watch3{Human marks resolved?}
Watch3 -- yes --> Done2[Resolved]
Watch3 -- no --> RePage[Re-page at min 5/15/30
escalate to backup contact]
Daily --> Slack3Btn{Operator clicks a Slack button}
Done1 --> Slack3Btn
Done2 --> Slack3Btn
Slack3Btn -- Approve ✅ --> ApproveAct[Execute action]
Slack3Btn -- Revise ✏️ --> ReviseModal[Slack modal
fill in revision hint]
Slack3Btn -- Reject ❌ --> RejectModal[Confirm modal
cancel + re-plan]
ReviseModal --> Dip
end
ApproveAct --> Sched[Schedule to customer local time
actually send]
Sched --> Send
%% ================== Outbound channel + retry ==================
Send{Outbound channel?}
Send -- email --> EmailResend[📧 Resend API send]
Send -- voice --> VoiceDial[📞 Voice Agent 06
Check state before dialing]
EmailResend --> EmailCheck{Resend response OK?}
EmailCheck -- 200 OK --> EmailOK[Email lands in inbox]
EmailCheck -- "❌ 5xx error or timeout" --> EmailRetry[Exponential backoff × 3
1s / 5s / 30s]
EmailRetry --> EmailCheck
EmailCheck -- "❌ 3 retries failed" --> EmailFail[Audit: email_send_failed
Notify Concierge
priority=high_24h]
VoiceDial --> CanDial{can_dial passes?
D-035}
CanDial -- account frozen --> SkipDial1[Voice dial skipped
Audit: voice_dial_skipped]
CanDial -- case human-owned --> SkipDial2[Voice dial skipped
Audit only]
CanDial -- TCPA block --> SkipDial3[Voice dial skipped
Permanent do-not-call]
CanDial -- ✅ passes --> TwilioCheck[ElevenLabs ConvAI 'Sarah'
dials out via Twilio]
TwilioCheck --> TwilioStatus{Twilio dial status?}
TwilioStatus -- "✅ ringing → answered" --> ConvAI[Call begins → Voice 8 Phases]
TwilioStatus -- "📩 voicemail" --> R11
TwilioStatus -- "❌ no-answer timeout" --> CallRetry[Audit + retry on next cadence]
TwilioStatus -- "❌ busy / failed (Twilio error)" --> CallError[Audit: call_failed_technical
Notify Concierge priority=standard]
EmailOK --> ResendHook
EmailOK --> CustReply
EmailFail -.-> Conc
ConvAI --> VoicePhases
%% ================== 🆕 Day 4 — Resend post-send webhook G1 ==================
subgraph ResendWebhookSeg["🆕 📨 Resend Webhook G1 — open/click tracking (Day 4)"]
direction TB
ResendHook["📨 Resend post-send event stream
src/concierge/resend_webhook.py"]
ResendHook --> RWEvent{event_type?}
RWEvent -- email.opened --> RWOpen["Audit: email_opened
Update case_state.last_open_ts"]
RWEvent -- email.clicked --> RWClick["Audit: email_clicked
Raise engagement_score"]
RWEvent -- email.bounced --> RWBounce["Audit: email_bounced
Concierge priority=high_24h"]
RWEvent -- email.complained --> RWComplain["Audit: email_complained
Trigger case_freeze immediately"]
end
%% ================== 🆕 Day 4 — Customer Reply Detector (F1/F2/F5/F8) ==================
subgraph ReplyDetector["🆕 Customer Reply Detector — Day 4, src/agents/_utils.py"]
direction TB
CustReply["👤 Customer email reply
Resend inbound / IMAP"]
CustReply --> DetectFanout["Run 4 detectors in parallel"]
DetectFanout --> D_Disp["detect_dispute (F1)
Customer disputes invoice items"]
DetectFanout --> D_Part["detect_partial_payment (F5)
Customer claims partial payment"]
DetectFanout --> D_Ext["detect_extension_request (F8)
Customer asks for extension"]
DetectFanout --> D_Prom["detect_promise_to_pay (F2)
Customer commits to a date"]
D_Disp --> DetectMerge{Merge verdict
highest-confidence match}
D_Part --> DetectMerge
D_Ext --> DetectMerge
D_Prom --> DetectMerge
end
RWOpen -.-> Audit
RWClick -.-> Audit
RWBounce --> Conc
RWComplain --> FreezeBroadcast
DetectMerge --> Reply
%% ================== Voice 8 Phases ==================
subgraph VoicePhases[STEP 8 — Voice Call 8 Phases, D-034]
direction TB
VPh1[Phase 1 verify identity
recording-consent disclosure]
VPh1 --> VPh2[Phase 2 ask reason
11 excuse categories]
VPh2 --> VPh3[Phase 3 negotiate payment date]
VPh3 --> VPh3b["Phase 3b customer commits via email
D-034.2 cooperative path"]
VPh3 --> VPh4[Phase 4 confirm
promise 24h email summary]
VPh3 --> VPh5["Phase 5 escalation-notice close
cite arbitration_forum_spoken
+ 24h SLA"]
VPh3 --> VPh6[Phase 6 conflict de-escalation]
VPh1 --> VPh7[Phase 7 wrong party answered
do NOT disclose invoice details]
VPh1 --> VPh8[Phase 8 graceful close
24h SLA]
VPh1 --> VPhRecRefused["Customer refuses recording
D-035 new script
explain + 2 alternative paths"]
end
VPh4 --> CallEnd
VPh5 --> CallEnd
VPh6 --> CallEnd
VPh3b --> CallEnd
VPh7 --> CallEnd
VPh8 --> CallEnd
VPhRecRefused --> CallEnd
%% ================== Post-call Webhook ==================
CallEnd["📨 ElevenLabs call ended
post-call webhook fires — D-034.4"]
CallEnd --> PostHook[HMAC verify → Flask /events/post-call]
PostHook --> PostAudit1[Audit: voice_call_completed
incl. transcript + analysis + metadata]
PostHook --> PostAudit2[Emit voice_call_completed in Band Room]
PostAudit1 --> VR
PostAudit2 --> VR
%% ================== 16 reasons routing ==================
subgraph VoiceEsc[STEP 9 ⭐ Voice Call 16 ending types — D-035 core]
direction LR
VR{How did the call end?}
VR --> R1[1 customer refused recording
handoff=Y freeze=N 24h]
VR --> R2[2 customer said do-not-call
handoff=Y freeze=Y 24h]
VR --> R3[3 customer engaged counsel
handoff=Y freeze=Y 24h]
VR --> R4[4 customer declared bankruptcy
handoff=Y freeze=Y 24h]
VR --> R5[💥 5 customer welfare signal
handoff=Y freeze=Y CRITICAL]
VR --> R6[6 wrong party answered
handoff=Y freeze=N standard]
VR --> R7[7 customer not coherent
handoff=Y freeze=N 24h]
VR --> R8[8 call dropped
handoff=N freeze=N standard]
VR --> R9[9 customer silent 5+ sec
handoff=N freeze=N standard]
VR --> R10[10 customer commits via email
handoff=N freeze=N standard]
VR --> R11[11 voicemail left
handoff=N freeze=N standard]
VR --> R12[12 customer cannot commit date
handoff=N freeze=N standard]
VR --> R13[💥 13 customer emotional collapse
handoff=N freeze=Y CRITICAL]
VR --> R14[14 customer wants to record us publicly
handoff=N freeze=N 24h]
VR --> R15[15 customer persistently hostile
handoff=N freeze=N 24h]
VR --> R16[16 language mismatch
handoff=N freeze=N standard]
end
R1 --> Handoff
R2 --> Handoff
R3 --> Handoff
R4 --> Handoff
R5 --> Handoff
R6 --> Handoff
R7 --> Handoff
R8 --> Park
R9 --> Park
R10 --> Park
R11 --> Park
R12 --> Park
R13 --> Park
R14 --> Park
R15 --> Park
R16 --> Park
%% ================== Welfare SOP ==================
R5 --> WelfareSOP
subgraph WelfareSOP[💥 STEP 11 Welfare-signal 5-step SOP — D-035]
direction LR
W1[1 Stop immediately
no more invoice / amount talk] --> W2[2 Do NOT play therapist
do NOT say 'I understand how you feel']
W2 --> W3[3 Offer 988 Suicide Prevention Lifeline
free · confidential · 24/7]
W3 --> W4[4 Freeze account immediately
broadcast case_freeze]
W4 --> W5[5 Emergency page operator
5 min: Slack + TG + optional phone]
W5 --> WClose[Warm 30-second close
'please take care of yourself first']
end
WClose --> Handoff
%% ================== Case Handoff full chain ==================
Handoff["🔴 Voice Agent submits to Concierge
case_handoff=true + reason"]
Handoff --> ConciergeCard[Concierge posts Slack card
prominent red case_handoff badge
incl. transcript link]
ConciergeCard --> HumanSeeCard[👤 Operator sees card in Slack]
HumanSeeCard --> HumanClick{Which button?}
HumanClick -- "✅ Accept handoff" --> WriteAccept[Audit: case_handoff_accepted
incl. ack_ts + operator_id]
HumanClick -- "❌ Reject handoff" --> RejectHandoff[Audit: handoff_rejected
case stays active, Concierge re-queues]
WriteAccept --> Broadcast[Concierge broadcasts case_owner_changed
to all 8 always-on agents
9th AAA Specialist only joins dynamically on Day 65]
RejectHandoff --> Dip
Broadcast --> StandDown1[Diplomat: cancel all future cadence]
Broadcast --> StandDown2[Investigator: halt all probes]
Broadcast --> StandDown3[Escalator: no Day 60+ activation]
Broadcast --> StandDown4[Voice: reject all dials]
Broadcast --> StandDown5[Payment: stop issuing paylinks]
StandDown1 --> CaseHumanOwned([Case = permanently human-owned])
StandDown2 --> CaseHumanOwned
StandDown3 --> CaseHumanOwned
StandDown4 --> CaseHumanOwned
StandDown5 --> CaseHumanOwned
CaseHumanOwned --> ReopenPath{Will human re-try AI later?}
ReopenPath -- "rare — e.g. customer improves after 6 months" --> HumanReopen["👤 Operator
Slack: /case-reopen case=... reason=..."]
HumanReopen --> ReopenAction[Concierge receives command]
ReopenAction --> R_R1[Audit: case_reopened]
ReopenAction --> R_R2[Broadcast case_active_again to all 8 agents]
R_R2 --> R_R3[Diplomat resumes: restart from review step]
R_R2 --> R_R4[Investigator resumes: re-run Tier 1a/1b]
R_R2 --> R_R5[Escalator resumes]
R_R2 --> R_R6[Voice resumes: can_dial re-permits]
R_R2 --> R_R7[Payment resumes]
R_R3 --> ReopenCadence[Cadence restarts at review]
R_R4 --> ReopenCadence
R_R5 --> ReopenCadence
R_R6 --> ReopenCadence
R_R7 --> ReopenCadence
ReopenPath -- case stays closed --> Closed([Permanently closed])
%% ================== Account Hold ==================
Park[🟡 case_handoff=N / retry on next cadence]
Park --> AHCheck{account_hold = true?}
AHCheck -- yes --> FreezeBroadcast[Broadcast case_freeze
Audit: account_held]
AHCheck -- no --> NextCadence[Continue to next cadence step]
FreezeBroadcast --> FZ1[Diplomat: cancel scheduled email]
FreezeBroadcast --> FZ2[Investigator: halt external API
internal history read-only OK]
FreezeBroadcast --> FZ3[Escalator: pause Day 60+
cancel Day 55 voice]
FreezeBroadcast --> FZ4[Voice: cancel scheduled dial]
FreezeBroadcast --> FZ5[Payment: stop issuing new paylinks
BUT inbound webhook still processed]
FZ1 --> CaseFrozen([Case = frozen])
FZ2 --> CaseFrozen
FZ3 --> CaseFrozen
FZ4 --> CaseFrozen
FZ5 --> CaseFrozen
CaseFrozen --> UnfreezeCheck{Unfreeze trigger?}
UnfreezeCheck -- "Customer pays — non-legal" --> AutoUnfreeze[System auto-broadcasts
case_unfrozen]
UnfreezeCheck -- "Operator decides to unfreeze" --> HumanUnhold["👤 Operator judges case can restart
e.g. customer cooled down
Slack: /case-unhold case=... reason=..."]
UnfreezeCheck -- "Customer pays BUT case is legal/bankruptcy" --> OperatorSurface[Surface to operator for review
do NOT auto-unfreeze]
HumanUnhold --> ManualUnfreeze[Concierge broadcasts case_unfrozen]
AutoUnfreeze --> Dip
ManualUnfreeze --> Dip
OperatorSurface --> ManualReview[Operator manual review]
%% ================== Customer Reply ==================
Reply{Customer reply type?}
Reply -- excuse --> Excuse[Look up excuse library
Featherless RAG D-029
31 excuse categories]
Excuse --> Dip
Reply -- partial payment --> Sub
Reply -- paid in full --> Paid
Reply -- Day 30 no reply --> Esc
Reply -- 2+ excuses --> Esc
Reply -- replies only after demand letter --> Dip
%% ================== Sub-Cycle ==================
subgraph PartialSub[STEP 15 Partial-payment flow — D-028]
direction TB
Sub[Customer paid part — e.g. $3K of $8K]
Sub --> SubDip[Diplomat asks for new completion date]
SubDip --> Sub3btn[Concierge 3-button card]
Sub3btn -- Approve ✅ --> SubReset[Reset cadence to customer date]
Sub3btn -- Override ✏️ --> SubModal[Slack modal — pick date + reason]
Sub3btn -- Treat as default ❌ --> SubConf[Confirm modal → jump to Escalator]
SubReset --> Dip
SubModal --> Dip
SubConf --> Esc
SubLimit{partial-payment count == 2?}
Sub --> SubLimit
SubLimit -- yes --> NoApprove[Concierge removes Approve button
only Override + Treat-as-default remain]
end
%% ================== Escalator + Day 55 30-min cancel + Day 65 AAA ==================
subgraph Escalator[4️⃣ Escalator Agent — Day 30-60+]
direction TB
Esc[Escalator joins room]
Esc --> EscCB[Draft cost-benefit card]
EscCB --> EscMode{Mode?}
EscMode -- in_spot main flow --> EscFull[Full demand-letter flow]
EscMode -- lite --> EscSkip[Skip — lite mode does not escalate]
EscMode -- attorney --> EscTag[Demand letter + attorney-recommended tag]
EscFull --> D55Check{Day 55 voice trigger?
D-035 mandatory state check}
D55Check -- "frozen / human-owned" --> D55Skip[Skip — audit only]
D55Check -- "active + prior escalation / chronic late" --> D55Auto[Auto-trigger ConvAI — D-027]
D55Check -- "active + other patterns" --> D55HITL[Route through Concierge approval]
D55Auto --> D55CancelWindow["⏰ 30-minute cancel window — D-027
Concierge shows audit-only card
with 'Cancel before dial' button"]
D55CancelWindow --> D55Wait{Human clicks Cancel within 30 min?}
D55Wait -- "no — default no-click" --> Day55Fire[ConvAI dials]
D55Wait -- "yes — human vetoes" --> D55Cancelled[Audit: day55_cancelled_by_operator
dial cancelled]
Day55Fire --> Day60[Day 60 demand letter
cites arbitration_forum_spoken]
D55HITL --> Day60
D55Skip --> Day60
D55Cancelled --> Day60
Day60 --> Day65[⭐ Day 65 Dynamic Agent Discovery
query Band Registry
capability=aaa_arbitration]
Day65 --> SpecJoin[AAA Specialist Agent joins room
self-introduces]
SpecJoin --> SpecRecs[Provides AAA filing details
fees / timeline / required docs]
SpecRecs --> EscFinal[Escalator drafts final demand letter
using Specialist input]
EscFinal --> Concierge_DemandLetter[Concierge surfaces demand letter for human approval]
Concierge_DemandLetter --> SendDemand[Send demand letter]
SendDemand --> Reply
SendDemand --> Settle[💰 Customer agrees to pay]
end
%% ================== 4 channels + TW SWIFT ==================
subgraph PaymentSeg[7️⃣ Payment Agent — 4 channels, D-019 + D-022 + D-030 + D-033]
direction TB
Settle --> PayShow[Generate deterministic checkout URL
URL is generated, NOT via LLM]
PayShow --> PayCh{Customer picks channel}
PayCh -- "USDC crypto ⚡" --> AutoUSDC["Circle ARC chain auto-receive
seconds · gas-free · D-033
🆕 Day 4 MVP = polling, see LP1-LP5
webhook swap on Day 5+"]
PayCh -- "Credit card via Stripe" --> AutoStripe[Stripe webhook auto-confirms]
PayCh -- "US ACH 💳" --> ACHWait
PayCh -- "US Wire (USD) 🏦" --> WireWait
PayCh -- "Taiwan SWIFT outbound (display only) 🇹🇼" --> TWSwift["D-022: we only DISPLAY Taiwan bank SWIFT details
we never originate funds, never handle FX
customer initiates on their side
same /payment-received command"]
WireWait[Bank deposit lands
no auto webhook from bank]
ACHWait[Bank deposit lands
no auto webhook from bank]
TWSwift --> SwiftWait[Bank deposit lands
no auto webhook from bank]
WireWait --> HumanWire["👤 Operator checks bank app
Slack: /payment-received case=... amount=... channel=wire"]
ACHWait --> HumanACH["👤 Operator: /payment-received case=... amount=... channel=ach"]
SwiftWait --> HumanSwift["👤 Operator: /payment-received case=... amount=... channel=swift_tw"]
HumanWire --> ManualWire[Payment Agent receives command
mark invoice paid]
HumanACH --> ManualACH[Payment Agent receives command
mark invoice paid]
HumanSwift --> ManualSwift[Payment Agent receives command
mark invoice paid]
AutoUSDC --> StateCheck
AutoStripe --> StateCheck
ManualWire --> StateCheck
ManualACH --> StateCheck
ManualSwift --> StateCheck
StateCheck{Case state?
D-035}
StateCheck -- "active" --> Close[Mark invoice paid
broadcast case_closed]
StateCheck -- "frozen, non-legal" --> AutoUnfreezePay[Auto-unfreeze + close]
StateCheck -- "frozen, legal/bankruptcy" --> SurfaceLegal[Surface to operator review]
StateCheck -- "human-owned" --> SurfaceHuman[Surface to operator
do NOT auto-close]
end
Close --> Paid([💰 Invoice closed])
AutoUnfreezePay --> Paid
SurfaceLegal --> ManualReview2[Operator confirms]
SurfaceHuman --> ManualReview2
%% ================== /case-status anytime query ==================
HumanStatus["👤 Operator wants live case status
e.g. 'where's ABC Trading now?'"] --> StatusCmd["Slack: /case-status case=case-1234"]
StatusCmd --> StatusFetch[Concierge receives command]
StatusFetch --> Fetch1[Pull full timeline from audit_trail.jsonl]
StatusFetch --> Fetch2[Pull current owner state from case_db]
StatusFetch --> Fetch3[Pull hold/handoff flags from case_db]
Fetch1 --> StatusReply
Fetch2 --> StatusReply
Fetch3 --> StatusReply
StatusReply[Concierge replies via Slack ephemeral message
operator-only — no channel noise
shows full timeline + ownership + state]
StatusReply --> HumanRead[👤 Operator reads, decides next step]
HumanRead -.choose.-> HumanUnhold
HumanRead -.choose.-> HumanReopen
HumanRead -.choose.-> ManualClose[Manually close case]
HumanRead -.choose.-> NoActionNeeded([No action — case is fine])
%% ================== Audit Trail ==================
subgraph Audit[STEP 18 Audit trail — audit_trail.jsonl, D-024 + D-035]
direction LR
Aud1[Basics: request / response / pause / resume]
Aud2[D-028: cadence reset / extension reject / auto-cancel]
Aud3[D-030: payment manual ack / payment auto webhook]
Aud4[D-031: path select / lite skip / attorney referral]
Aud5[D-032: outbound scheduled / executed / revoked]
Aud6["D-035 new 8 types:
handoff request/accept · account freeze/unfreeze
SLA pre-breach/breach · emergency page · post-handoff auto-reject"]
Aud7["D-034.4: voice_call_completed + transcript"]
end
Conc -.-> Audit
Handoff -.-> Audit
FreezeBroadcast -.-> Audit
Sub -.-> Audit
PostHook -.-> Audit
StatusFetch -.-> Audit
HumanReopen -.-> Audit
HumanUnhold -.-> Audit
%% ================== 🆕 Day 4 — Case State resilience layer D4-D8 ==================
subgraph CaseStateSeg["🆕 Case State resilience layer D4-D8 — Day 4, src/agents/_case_state.py"]
direction TB
CSStore["📁 11 per-case JSON files
src/agents/case_state/*.json"]
CSStore --> CS_D4["D4 audit-trail-driven recovery
rebuild_all_from_audit()
rebuild all state after crash"]
CSStore --> CS_D5["D5 HITL SLA poller
list_pending_hitl_cases()
proactively scan pending → Concierge push"]
CSStore --> CS_D6["D6 cost budget cap
add_cost() default \$0.50/case
overrun → forced handoff"]
CSStore --> CS_D7["D7 restart idempotency
events_seen_count
0 duplicate events on restart"]
CSStore --> CS_D8["D8 audit-replay correctness
28 distinct event types
0 drift verified"]
end
CS_D4 -.crash recovery.-> CronStart
CS_D5 -.SLA push.-> Conc
CS_D6 -.overrun trigger.-> Handoff
CS_D7 -.-> Audit
CS_D8 -.-> Audit
%% ================== 🆕 Day 4 — Live USDC Settlement Layer LP1-LP5 ==================
subgraph LiveUSDCSeg["🆕 Live USDC Settlement Layer LP1-LP5 — Day 4, src/payments/recoverflow_*.py"]
direction TB
LP1["🛒 LP1 · Buyer simulated payment
tools/simulate_buyer_payment.py
(demo: 4 US buyer personas)"]
LP1 --> LP2["📤 LP2 · Circle ARC-TESTNET transfer
POST /developer/transactions/transfer
buyer wallet → receiving wallet
state: INITIATED → COMPLETE (~5-30s)"]
LP2 --> LP3["🔭 LP3 · Wallet Poller every 10s
src/payments/recoverflow_wallet_poller.py
poll receiving balance + list INBOUND tx
first-launch baseline mode to skip historic tx"]
LP3 --> LP4["📝 LP4 · payment_received audit
data/audit_trail.jsonl
+ refId → case_id reverse lookup"]
LP4 --> LP4R["🧮 LP4R · Reconciler 4-state classify
src/payments/recoverflow_reconciler.py
aligned to outstanding_balance_usd (D-038)
flock + atomic rename — no double-credit"]
LP4R --> LP4Full["✅ FULL
paid_in_collection ≥ outstanding
case → settled"]
LP4R --> LP4Part["🟨 PARTIAL
0 < paid < outstanding
case → partially_paid
continue chasing remainder"]
LP4R --> LP4Over["💜 OVERPAID
paid > outstanding
refund ticket triggered"]
LP4R --> LP4Unm["❓ UNMATCHED
refId does not map to a case
enter unmatched queue
Concierge manual assignment"]
LP4Full --> LP5["📧 LP5 · Resend real receipt email
src/payments/recoverflow_notify.py
deliver to supplier inbox with tx_id
Circle explorer link · bilingual template"]
LP4Part --> LP5
LP4Over --> LP5
end
LP5 -.cancel cadence.-> Conc
LP4 -.-> Audit
AutoUSDC -.same inbound path.-> LP3
%% ================== Styling ==================
classDef d035 fill:#ffe4e1,stroke:#dc143c,stroke-width:3px
classDef critical fill:#ff6b6b,stroke:#8b0000,color:#fff,stroke-width:3px
classDef freeze fill:#fff3cd,stroke:#856404
classDef handoff fill:#ffcccc,stroke:#dc143c
classDef happy fill:#d4edda,stroke:#28a745
classDef xfactor fill:#ffd700,stroke:#ff8c00,stroke-width:3px
classDef humanaction fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
classDef errorpath fill:#ffe0e0,stroke:#c62828
classDef day4 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
classDef detector fill:#fff9c4,stroke:#f57f17,stroke-width:2px
class ResendHook,RWEvent,RWOpen,RWClick,RWBounce,RWComplain,CSStore,CS_D4,CS_D5,CS_D6,CS_D7,CS_D8,LP1,LP2,LP3,LP4,LP4R,LP4Full,LP4Part,LP4Over,LP4Unm,LP5,P1Dep day4
class CustReply,DetectFanout,DetectMerge,D_Disp,D_Part,D_Ext,D_Prom detector
class StateGate,CanDial,VR,Handoff,Park,FreezeBroadcast,CaseFrozen,CaseHumanOwned,P1C,StateCheck d035
class R5,R13,WelfareSOP,W1,W2,W3,W4,W5,WClose,Page5 critical
class FZ1,FZ2,FZ3,FZ4,FZ5,Pause freeze
class StandDown1,StandDown2,StandDown3,StandDown4,StandDown5,RejectD handoff
class Paid,Close,AutoUnfreezePay,Done1,Done2 happy
class Day65,SpecJoin,Alert,EscFinal xfactor
class Start,HumanSeeCard,HumanClick,HumanWire,HumanACH,HumanSwift,HumanUnhold,HumanReopen,HumanStatus,StatusCmd,HumanRead,ManualClose,HumanSeeCard humanaction
class EmailRetry,EmailFail,CallRetry,CallError,ToneReject errorpath