Case Study · Retail AI · Claude Opus 4.8

Harborline
AI Customer-Care & Order-Resolution Agent

An agentic system that resolves orders, returns, and stock queries end-to-end — cutting average handle time from 11 minutes to under 4, and self-serving 55% of tickets that today 100% require a human.

12M
Customers
600
Stores
11 min
Avg handle time today
≤ 4 min
Target with agent
55%
Target self-serve rate
1

Problem & Impact

Harborline's contact centre is operationally broken — not from lack of staff effort, but from system fragmentation, policy inconsistency, and zero automation.

Root Causes

🔀

4–5 Disconnected Systems

Agents manually switch between the Order Management System, inventory platform, carrier portal, returns tool, and CRM just to answer a single query. Every screen-switch adds seconds that compound into minutes.

📄

Scattered Refund Policy

Policy is buried across long documents. Agents interpret it differently, creating inconsistent outcomes for identical cases — eroding customer trust and exposing Harborline to unfair-treatment risk.

👤

100% Human-Touched Tickets

Every contact — including simple WISMO queries answerable in seconds — requires human handling. There is no self-service path for any intent, regardless of complexity.

Current State vs. Acceptable Threshold

Metric Now Target Gap
Avg. handle time 11 min ≤ 4 min −64 %
First-contact resolution 61 % ≥ 80 % +19 pp
Self-served tickets 0 % 55 % +55 pp
CSAT score 3.9 / 5 ≥ 4.5 / 5 +0.6 pts
Peak wait time 14 min ≤ 3 min −79 %
💸

Real-World Cost of Inaction

At 12 million customers and current handle times, a 1% increase in contact volume adds ~1,300 extra agent-hours per week. The absence of any self-serve path means every traffic spike translates directly to queue time, agent burnout, and customer churn — with no mechanism to absorb it.

Who Bears the Cost

🧑‍💼

Customers

Wait 14 minutes at peak for answers that should take 30 seconds. Receive inconsistent refund decisions on identical cases. Must repeat their story if transferred to a specialist.

🎧

Contact-Centre Agents

Spend the majority of every call on data retrieval across 4–5 systems rather than on judgement and empathy — the work they were hired to do. Context-switching causes cognitive fatigue and higher error rates.

🏢

Harborline Operations

Headcount scales linearly with volume — there is no leverage. Inconsistent policy application creates compliance risk. Long wait times directly suppress CSAT and repeat-purchase rate.

2

Solution & Innovation

A single orchestrating AI agent built on Claude Opus 4.8 that unifies all five fragmented systems behind one intelligent interface — with hard guardrails so the model can only act within policy.

What the Agent Does

  • 1

    Classifies Intent

    Identifies whether the customer needs order tracking, a return, a stock check, or specialist escalation — before touching any backend system.

  • 2

    Pulls Facts in Parallel

    Calls get_order, track_shipment, and check_inventory simultaneously — what took a human 3 screen-switches happens in a single agentic step.

  • 3

    Retrieves Policy, Never Infers It

    search_policy() fetches the authoritative returns document before every eligibility decision. The model cannot recall or guess policy from training weights.

  • 4

    Executes In-Policy Actions

    Issues return labels, initiates reships, or stages refunds — but only after a write-gate check confirms both policy compliance and explicit customer confirmation. Money never moves without a confirmed "yes".

  • 5

    Escalates with Full Context

    For out-of-policy or complex cases, the agent hands off a complete context bundle — transcript, facts, policy citations, recommendation — so the human specialist needs zero re-work from the customer.

What Makes It Innovative

🔑

Policy as Retrieved Ground Truth

Rather than hoping the model "knows" the returns policy, the system architecturally enforces retrieval. Every eligibility statement the agent makes is backed by a cited passage from returns_policy.md. This eliminates the single biggest source of inconsistency in the existing contact centre.

🔒

Read/Write Architectural Separation

READ tools (order lookups, inventory checks) flow freely with no gate. WRITE tools (refunds, labels, reships) are blocked at the module level by a dedicated gate that requires both policy approval and customer confirmation — independently of anything the model says.

↔️

Two-Step Irreversible-Action Flow

prepare_refund stages and displays the amount — no money moves. confirm_refund executes — but only after an explicit customer "yes", and only in a separate turn. This is enforced by both the system prompt and the test suite, not just the model's good behaviour.

🧪

Simulation-First, Production-Ready

The full agentic loop runs offline via SimulatedAnthropicClient — no API key, no live data, no cost during development. Swapping to production requires changing exactly two lines in agent.py. The 117-test suite runs against both modes.

How Claude Code Was Central to This Build

Architecture Design

Claude Code designed the single-orchestrator + strong-tool-suite pattern from first principles, recommending against premature multi-agent complexity for a latency-sensitive use case.

Guardrail Implementation

The write-gate module, two-step refund flow, and context-packet validation in handoff.py were all designed and implemented collaboratively, with constraints treated as non-negotiable.

Test Suite (117 tests)

Claude Code wrote all 117 pytest tests across 14 classes — including class-scoped fixtures that run run_agent() once per scenario and snapshot the audit log tail for assertions.

Bug Detection

Claude Code identified and fixed a path-depth bug across 6 src/ modules where .parents[4] was used instead of .parents[3] — a class of error that would have been silent in simulation but catastrophic in production.

3

Technical Execution

A clean, layered architecture — single agent, typed tool contracts, and an append-only audit log — built to be verifiable at every decision point.

Architecture & Data Flow

Customer message │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ agent.py (orchestrator) │ │ Model: Claude Opus 4.8 │ │ │ │ AGENTIC LOOP ───────────────────────────────────────────────── │ │ while True: │ │ response = client.messages.create(history, tools, system_prompt) │ │ if stop_reason == "end_turn" → print reply, exit │ │ if stop_reason == "tool_use" → execute tools, append results │ │ │ │ Tool taxonomy: │ │ ┌─ READ get_order · track_shipment · check_inventory │ │ │ (ungated, side-effect-free, run in parallel) │ │ ├─ RETRIEVE search_policy ← always before any eligibility call │ │ ├─ WRITE create_return_label · reship_order │ │ │ prepare_refund → [confirm] → confirm_refund │ │ │ ALL gated: in_policy=True AND confirmed=True required │ │ └─ ACTION handoff_to_human (validates 4-field context_packet) │ └──────────────┬────────────────────────────────┬────────────────────┘ │ │ ┌────────▼────────┐ ┌──────────▼──────────┐ │ Mock JSON data │ │ returns_policy.md │ │ orders.json │ │ (authoritative │ │ shipping.json │ │ policy ground truth│ │ inventory.json │ │ retrieved, not │ │ customers.json │ │ inferred) │ └─────────────────┘ └─────────────────────┘ │ ┌────────▼────────┐ │ audit.jsonl │ │ append-only log │ │ every tool call, │ │ confirmation, │ │ escalation │ └─────────────────┘

Tool Reference

CategoryToolGated?Key behaviour
READget_orderNoOrder status, line items, delivery date — by order_id or customer_id
READtrack_shipmentNoCarrier status, ETA, tracking ID; returns processing message if no tracking yet
READcheck_inventoryNoOnline and in-store stock levels per SKU; used before any reship attempt
READget_customerNoCustomer profile — only accessible with identity_verified=True
RETRIEVEsearch_policyNoReturns full policy text + citation. Must be called before any eligibility decision.
WRITEcreate_return_labelYesIssues FedEx label with 7-day drop-off window; requires multi-SKU line_items
WRITEreship_orderYesBlocked if any requested SKU has online_stock == 0
WRITEprepare_refundYesStages refund — no money moves. Returns WARNING key. Awaits explicit customer confirmation.
WRITEconfirm_refundYesIRREVERSIBLE. Only callable after customer explicit "yes", never same turn as prepare.
ACTIONhandoff_to_humanValidatesRequires all 4 context_packet fields: transcript · facts · policy_citations · recommendation

⚠ Safety Layers — Non-Negotiable Constraints

  • Policy guard: search_policy() must be called before any return/refund eligibility decision. Model may not infer policy from training weights — if retrieval fails, escalate.
  • Write gate (src/tools/write/gate.py): Single choke point for all write actions. Both in_policy=True AND confirmed=True required or WriteGateError is raised. in_policy is checked before confirmed.
  • Two-step refund: prepare_refund stages only — no money moves. confirm_refund is called ONLY after explicit customer "yes", and NEVER in the same turn as prepare_refund.
  • Escalation context: handoff_to_human validates all 4 packet fields; returns an error if any are missing — forcing complete context before handoff.
  • Audit trail: Every tool call, policy retrieval, confirmation event, and escalation is written to audit.jsonl. Guardrail check: action_executed count must match confirmation_received count.

Test Coverage — 117 Tests, 14 Classes

ClassTestsWhat it validates
TestTCK01_WISMO10Order lookup, ETA, no write tools called
TestTCK02_DefectiveReturn11Policy before write; label issued; $89.50 staged; confirm_refund NOT called same turn
TestTCK03_OutOfWindow871-day-old return escalated; zero refund/label issued
TestTCK04_StockCheck7Inventory check, online=3/store=0 reported, no write tools
TestTCK05_AngryFollowup11Tone constraint (apology before facts); escalation with context
TestWriteGate8Gate blocks on in_policy=False; gate blocks on confirmed=False
TestRefunds11Two-step flow; WARNING key; double-confirm error; confirm-without-prepare error
TestEscalation12Valid packet succeeds; each of 4 missing fields → distinct error
TestPolicyRetrieval830-day clause present; defective/changed-mind distinction correct
5 unit test classes39get_order · track_shipment · check_inventory · return label · reship

Agentic Loop (Simplified)

# agent.py — core loop (simplified)
def run_agent(query: str) -> None:
    case_id = uuid4().hex[:8].upper()
    history = [{"role": "user",
                "content": query}]

    while True:
        resp = client.messages.create(
            model=ORCHESTRATOR_MODEL,
            system=SYSTEM_PROMPT,
            tools=TOOLS,
            messages=history,
        )

        if resp.stop_reason == "end_turn":
            print(resp.content[0].text)
            break

        # Execute each requested tool
        tool_results = []
        for block in resp.content:
            result = _dispatch(block.name,
                               block.input)
            _audit(case_id, block.name, result)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": json.dumps(result),
            })

        history.append(
            {"role": "assistant",
             "content": resp.content})
        history.append(
            {"role": "user",
             "content": tool_results})

Key Design Decisions

Single Agent, Not Multi-Agent

Conversation state stays in one place. No inter-agent message routing overhead. Lowest latency path for the ≤ 4-minute AHT target. Multi-agent complexity only worthwhile if future scope demands it.

idempotency_key on Every Write

All WRITE tools require an idempotency_key in format {case_id}-{action}-{order_id}. Prevents duplicate refunds or labels on network retries — a critical production safety property.

4

Business Viability

A solution is only valuable if it can be adopted at scale, measured, and extended without a rewrite. Every design choice here serves that goal.

Adoption Potential

Zero Infrastructure Change for Simulation

The agent runs today — with no API key, no cloud dependency, and no data migration — via SimulatedAnthropicClient. Stakeholder demos, contact-centre pilots, and agent training can begin immediately.

🔌

Two-Line Production Migration

Swapping to the live Claude API requires changing exactly two lines in agent.py. The tool contracts, test suite, and audit log format are identical between simulation and production.

🌐

Channel-Agnostic Design

The agent interface is a plain string query in, structured response out. It can be wired to chat widgets, email parsing, IVR speech-to-text, or a human-agent co-pilot panel without touching the core logic.

🧑‍🤝‍🧑

Human-in-the-Loop by Design

The agent self-serves tractable cases and escalates complex ones — with a full context bundle. Contact-centre agents gain a co-pilot, not a replacement. This framing reduces adoption resistance.

ROI Indicators

55%
Tickets fully self-served
(target)
−64%
Reduction in avg.
handle time
−79%
Reduction in peak
wait time
+19pp
First-contact
resolution gain
100%
Policy consistency
(retrieved, not inferred)

Direct Cost Reduction

If 55% of the contact volume is self-served, the number of tickets requiring a human agent drops by more than half. At Harborline's scale (12M customers), that represents thousands of agent-hours per week — directly reducing headcount growth requirements and allowing specialists to focus on genuinely complex cases.

Consistency as Risk Reduction

Every current refund decision is a potential inconsistency risk — agents interpret scattered policy differently. With retrieval-grounded policy enforcement, every decision is backed by the same cited passage. This reduces refund leakage, dispute rates, and regulatory exposure from unfair-treatment claims.

Scalability

📈

Traffic Spikes Don't Scale Headcount

The agent handles unlimited concurrent conversations. A 3× Black Friday traffic surge that would previously require emergency staffing is absorbed by the model layer — not the HR department.

🔄

Policy Updates Are a File Edit

Changing the return window or adding a new ineligible category means editing returns_policy.md. The model picks it up on the next retrieval call. No retraining, no prompt engineering cycle, no deployment.

📦

Intent Coverage Grows with Tools

Adding a new capability — price-match queries, loyalty points, store locator — means adding one new tool and its entry in the TOOLS list. The orchestrator loop, guardrails, and audit log require no changes.

Roadmap

Now · Simulation Complete

Foundation

  • All 5 intent scenarios functional
  • 117 tests, all passing
  • Write gate + two-step refund enforced
  • Audit log operational
  • Full project documentation
Next · Production Hook-up

Live Integration

  • Swap SimulatedClient → Anthropic SDK
  • Connect real OMS, inventory, carrier APIs
  • Replace full-doc retrieval with RAG (vector store)
  • Add authentication gate + PII scoping
Later · Optimisation

Performance & Load

  • Add triage router (Claude Haiku) to deflect FAQ
  • Prompt caching across stable context segments
  • KPI dashboard wired to audit.jsonl events
  • A/B test tone variants on CSAT
Vision · Expansion

Broader Scope

  • Proactive outreach (delayed shipment alerts)
  • Price-match and loyalty points intents
  • Store-locator and product recommendations
  • Multi-lingual contact centre coverage

Production Migration Path — 5 Steps, All Opt-In

Step 1

pip install anthropic + set ANTHROPIC_API_KEY

Step 2

Replace SimulatedAnthropicClient with anthropic.Anthropic() — 2 lines

Step 3

Replace JSON reads in src/tools/read/ with real OMS / inventory / carrier API calls

Step 4

Swap full-doc policy read in retrieve/policy.py for a RAG vector-store lookup

Step 5

Wire config/models.py TRIAGE_MODEL to a lightweight intent router to shed peak load

All tool contracts, test suite assertions, and audit log format remain identical across simulation and production. No test changes required for steps 1–5.