Version A · Single Orchestrator v1 · Pragmatic Build

Sallie se Schoenen — Returns Agentic Pipeline

One orchestrator · Six functional nodes · Shared case state · Phased deployment

Architecture: Single LangGraph state machine, one agent runtime Agents: 1 orchestrator (6 capability nodes, not separate agents) State: ReturnCaseState — single shared source of truth HITL: Shared supervisor queue (Anouk + Robin)
Agent node (tool execution / action) Decision gate (branching) Human-in-the-loop escalation Parallel / dual-track execution Terminal resolution (customer notified) ReturnCaseState mutation point Information flow Resolution flow (active path)
1

Intake & Condition Eval

Customer presents shoes at till. Eva captures receipt images + shoe photos + customer identifiers. First node consumes raw intake and produces a structured condition verdict.

verify_receipt_data() analyze_shoe_condition() route_ticket()
Output →
ReturnCaseState v1 written: items[], receipt_verified (bool), condition_verdict { is_worn, defect_suspected, condition_score, confidence }, customer_id, origin_store_id
2

Cross-Store Proof Lookup

If receipt is missing or unreadable, agent attempts federated search across all 4 Randstad stores + e-comm records using partial identifiers. No phone calls required.

federated_sales_search() payment_token_lookup() generate_replacement_proof()
Output →
If found: transaction enriched in state, proof_verified = True, replacement proof artifact minted → re-enters policy_check.
If not found: routes to HITL with full context; customer notified of pending lookup.
3

Policy Rule Check

Applies the 30-day window, unworn requirement, return-type preference, and €150 refund cap. Routes to the correct resolution path. Rules are deterministic.

compute_days_since_purchase() evaluate_policy_compliance() check_refund_cap()
Output →
Routes to: stock_resolve (exchange), execute_till_refund (refund ≤ €150), defect_assess (defect suspected), hitl_escalate (out of policy / worn / over cap).
4a

Inventory & Logistics Resolve

For exchange cases: queries real-time stock across all 4 stores + warehouse. Attempts atomic hold (48h TTL) on best alternative location. Creates transfer manifest if hold succeeds.

query_stock_matrix() acquire_atomic_hold() create_interstore_transfer_manifest()
Output →
Hold succeeds: transfer manifest + 48h hold ID → customer notified with collection date.
No stock: alternative_offer (store credit / different product).
API down: hitl_escalate — never silent failure.
4b

Defect Assessment & RMA

For defect-suspected cases: checks photo quality first, then runs multimodal defect classification. Separate parallel track handles supplier RMA assembly. Confidence threshold gates HITL.

check_photo_quality_compliance() classify_structural_defect() submit_supplier_rma_claim() notify_purchasing_lead()
Output →
Conf ≥ 0.85: till remedy authorized + RMA auto-submitted (parallel).
Conf < 0.85: HITL to Anouk for physical inspection; re-capture prompts if images insufficient.
5

HITL Escalation (Shared Queue)

Any case that cannot be fully resolved by the agent — no proof match, worn item disputed, over-€150 refund, borderline defect — routes to a single supervisor dashboard. Anouk or Robin action it; case re-enters the flow at the appropriate node.

escalate_to_supervisor() supervisor_action_callback()
Output →
Case posted to: Anouk's supervisor dashboard with full state, elapsed time, what was tried. After resolution: case re-enters state machine, customer notified of outcome.
6

Customer Notification

Every terminal resolution emits a notification before the case closes. No silent closures. Channels: SMS + email. Also fires on hold expiry and on HITL status changes.

notify_customer_sms() notify_customer_email()
Output →
Notification sent: refund confirmed / exchange ticket issued / defect remedy authorized / claim submitted / hold expired / case escalated. customer_notified = True → case can close.
End-to-End State Machine — Version A · Single Orchestrator
ReturnCaseState — single shared source of truth (mutated at each ● node) Customer presents item at till Eva captures: receipt images · shoe photos · customer ID state ① Intake & Condition Eval verify_receipt_data(receipt_id, purchase_date) analyze_shoe_condition(image_urls[]) → { is_worn, defect_suspected, condition_score, confidence } state+1 proof verified proof missing / unreadable ② Cross-Store Proof Lookup federated_sales_search(customer_id, date_range) payment_token_lookup(card_last_four, bank_ref) generate_replacement_proof(tx_id) → digital artifact state+2 match found ③ Policy Rule Check compute_days_since_purchase() · evaluate_policy_compliance() · check_refund_cap() Rules: 30-day window · unworn required · refund cap €150 · return-type preference state+3 refund ≤ €150 · refund desired exchange desired · within policy defect suspected out of policy / over cap / worn dispute No match ⑤ HITL — Supervisor Queue (Anouk / Robin) Case posted with full state · elapsed time · actions attempted Anouk actions → case re-enters flow at appropriate node re-entry → proof_ok / policy / defect re-entry → stock_resolve re-entry → defect_track ④a Stock & Logistics query_stock_matrix(sku, size) → 4 stores + warehouse acquire_atomic_hold(sku, store, ttl=48h) state+4a hold confirmed no network stock API down Transfer Manifest + Customer Hold create_interstore_transfer_manifest() 48h atomic hold · customer notified with collection date transfer dispatched Alternative Offer store credit / different model → notify → final Anouk manual check ④b Defect Track check_photo_quality_compliance(images[]) classify_structural_defect(images[], desc) → { fault_type, confidence, is_manufacturing_fault } quality fail → re-capture (loops back to intake) ∥ Supplier RMA (Parallel Track) Assemble evidence package images · receipt metadata · defect classification submit_supplier_rma_claim(supplier_id, case_data) notify_purchasing_lead(claim_id, status) → Robin RMA submitted · claim tracked conf < 0.85 → Anouk physical inspection Execute Till Refund execute_till_refund(amount, tender) Capped at €150 auto · over cap → HITL refund complete over €150 Update Local Inventory Return shoes to store inventory Update stock counts in WMS/ERP ⑥ Notify Customer → Case Close notify_customer( outcome, delivery_details ) customer_notified = True · case.closed = True TERMINAL Case resolved · customer notified Dead-Letter: Lock Expired 48h TTL expires · inventory released customer_notified: hold expired Inventory returns to active WMS/ERP — no phantom stockout INPUT FLOW customer_presented_item → intake_eval receipt_images + shoe_photos + customer_id customer_identifier (email / phone / card_hash) STATE MUTATIONS case_id · customer_id · items[] · origin_store_id proof_verified · condition_verdict · policy_result hold_reservation_id · supplier_claim_id OUTPUTS refund_authorized · exchange_ticket · rma_claim_id customer_notification_sent · case.closed dead_letter_reason (if applicable)
Stage-by-Stage Information Flow & Outputs

Input → Process → Output Matrix

StageInputTool / ActionOutputState Mutation
① Intake Receipt images, shoe photos (3+), customer ID, reason code verify_receipt_data · analyze_shoe_condition (CV) Receipt validity result; condition score; is_worn flag; defect_suspected flag proof_verified · condition_verdict { is_worn, defect_suspected, score, confidence }
② Cross-Store customer_identifier, approx_date_range, card_last_four (optional) federated_sales_search · payment_token_lookup · generate_replacement_proof Matched transaction OR no-match signal; digital proof artifact (if found) proof_verified = True; enriched transaction data; replacement_proof_ref
③ Policy purchase_date, is_worn, defect_suspected, resolution_pref, refund_amount compute_days_since_purchase · evaluate_policy_compliance · check_refund_cap Route signal: REFUND / EXCHANGE / DEFECT / HITL; cap check result resolution_type · escalation_reason (if HITL) · cap_authorization
④a Stock sku, size, origin_store_id, dest_pref (customer location) query_stock_matrix · acquire_atomic_hold · create_interstore_transfer_manifest Stock matrix; hold_id (or no-stock flag); transfer manifest; collection date hold_reservation_id · transfer_manifest_ref · stock_source_store_id
④b Defect image_urls[], fault_description, receipt metadata (from state) check_photo_quality_compliance · classify_structural_defect · submit_supplier_rma_claim quality verdict; fault_type; confidence; RMA claim_id; purchasing notification supplier_claim_id · defect_classification · rma_status · confidence_score
⑤ HITL Full ReturnCaseState; elapsed time; actions attempted; reason for escalation escalate_to_supervisor · supervisor_action_callback Anouk's resolution action; supervisor notes; resolution path chosen escalation_resolved · supervisor_notes · resolution_path · resolution_time
⑥ Notify outcome_type, delivery_details (refund / exchange / defect / claim / expiry), customer contact notify_customer_sms · notify_customer_email Notification delivery confirmation; customer_notified flag customer_notification_status · notification_channel · notified_at
◆ Close All accumulated state fields case.close() — only permitted when customer_notified = True Case closed; final audit log entry; metrics emitted case.closed = True · closed_at · final_audit_entry

Guardrails & Edge Cases

GuardrailTriggerActionFallback
Refund cap €150execute_till_refund amount > €150Route to HITL; do not auto-refundAnouk digital approval via supervisor dashboard
Defect conf < 0.85classify_structural_defect confidence < 0.85Route to HITL (Anouk physical inspection)High-res re-capture prompts sent to till UI
Photo quality failcheck_photo_quality_compliance = FalseHold case; prompt till UI to re-captureLoops back to intake_eval; case stays open
Stock API downquery_stock_matrix throws / times outRoute to HITL; do not guess stockAnouk manual phone check (old path as fallback)
Hold expiry (48h)TTL elapsed; customer hasn't collectedLock auto-expires; inventory released to WMSCustomer notified before expiry + at expiry
No match (cross-store)federated_sales_search returns emptyRoute to HITL; case stays openAnouk manual store-by-store check
Out of policyWorn item vs. change-of-mind; >30 days; no defect claimedRoute to HITL; do not force resolutionAnouk supervisor review; policy exception decision
No customer notificationAttempt to close case with customer_notified = FalseBlock case closureSystem prevents close; flag for ops review
Bottleneck Remediation — What Changes for Each Persona

Customer

Pain point C1 — Till delays at peak
Manual receipt inspection every return consumes till time.
Before: Eva manually checks receipt, visually inspects shoe, may call supervisor — 8–15 min per return.
Receipt verification + condition assessment automated by agent; Eva confirms and executes. Standard in-policy returns: < 3 min.
Pain point C3 — Customer chases own proof
No receipt → store phones other stores → customer told to find bank statements.
Before: Customer leaves without resolution; returns days later with proof they had to dig up.
Agent searches all 4 stores in one query; digital proof minted if found. If not found, case stays open with status update — customer doesn't need to chase.
Pain point C5 — Exchange holds evaporate
Verbal hold on paper/spreadsheet; lost when Eva goes off-duty.
Before: Customer returns for collection; pair is gone. Re-resolution required.
Atomic hold with 48h TTL in system; customer gets collection date by SMS. Hold releases inventory back to WMS automatically if uncollected.

Eva (Store Associate)

Pain point E3+E4 — Phone store-by-store for stock
Listed inventory untrustworthy; must call each store for manual shelf check.
Before: 3–5 phone calls per exchange; 10–20 min; counts still unreliable.
query_stock_matrix returns all locations in one call; atomic hold prevents double booking. Only fallback to phone if API is down.
Pain point E5 — Paper/spreadsheet holds
No system of record for holds; lost between shifts.
Before: Sticky note on till; spreadsheet cell; nothing survives shift change.
Hold is a system record with reservation_id; survives any staff change; TTL enforced by system.
Pain point E6 — Blurry defect photos rejected
Robin bounces back photos for quality; rework loop.
Before: Eva takes photos; days later Robin emails "please re-capture from this angle."
check_photo_quality_compliance runs before classification; till UI prompts for missing angles immediately. No bounce-back lag.

Anouk (Supervisor)

Pain point A1+A2 — Single gatekeeper bottleneck
Every exception — missing proof, policy edge, till override, defect — goes to Anouk.
Before: Anouk queues manually; customers wait or return later; peak hours overwhelm.
Clear receipts, in-policy returns, confirmed defects (≥0.85) all auto-resolve. Only genuine edge cases reach Anouk's dashboard — with full context already assembled.
Pain point A3 — Manual defect judgment
Anouk must visually decide worn vs. defective — subjective, inconsistent.
Before: Anouk's judgment is the only standard; varies by day, by mood, by pair.
CV classification provides structured evidence (fault_type, confidence) as decision-support. Anouk reviews the output, not the raw shoe from scratch. Consistency improves.

Robin (Purchasing Manager)

Pain point R1+R2 — Inconsistent evidence + no structured intake
Emailed photos arrive randomly; no standard evidence package; Robin chases missing angles.
Before: Incoming emails; Robin decides what's adequate; bounces back for more.
Warranty Claims node assembles a structured evidence package (images + receipt metadata + defect classification) before anything reaches Robin. Robin sees a complete case, not a request for more info.
Pain point R3 — No visibility into claim status
Store staff don't know if vendor credit lands; inventory write-off ambiguous.
Before: Robin submits RMA; silence for weeks; store has no idea if it's approved.
supplier_claim_id tracked in case state; notify_purchasing_lead fires on status change. Both Robin and the originating store see status in the case record.

Version A vs. Current As-Is — Bottleneck-by-Bottleneck

Which bottlenecks does Version A actually resolve, and which does it not yet touch?

Resolved in Version A

  • C1 — Till delays: Receipt + condition automated; standard returns under 3 min
  • C3 — Customer chases proof: Federated search replaces phone tag; case stays open with status
  • E3+E4 — Phone store-by-store: query_stock_matrix in one call; phone only as fallback
  • E5 — Paper holds: Atomic hold with reservation_id in system; TTL enforced
  • E6 — Blurry photos: Quality check runs before classification; re-capture prompts immediately
  • A1 — Single gatekeeper: High-confidence paths auto-resolve; only edge cases reach Anouk
  • R1+R2 — Evidence quality: Structured package assembled before anything reaches Robin
  • C5 — Hold evasion: 48h TTL + customer notification; no silent losses

Not Yet Resolved (Requires Infra / Future Phase)

  • C2 — Supervisor wait time: Reduced but not eliminated; some cases still hit Anouk
  • S1 — Data silos: Federated search only works if POS has a readable API — prerequisite
  • S2 — Untrusted inventory: Atomic hold requires WMS lock semantics; best-effort until WMS upgraded
  • S5 — Defect classifier accuracy: Needs labeled dataset; early phase = CV as decision-support only
  • S6 — Decoupled RMA track: Improved (evidence package + status tracking) but full automation needs supplier API
  • C4 — Customer timeline visibility: Notifications fixed; but no customer-facing self-service portal yet
  • GDPR / payment token review: Not addressed in this version — needs legal review before payment_token_lookup goes live