Files
hold-slayer/docs/call-flows.md
Robert Helewka dff21f7d5c Serve dashboard at /, version the REST API under /api/v1
The SvelteKit build was always made for the root (no base path); it
now mounts at / — registered last so /api/v1, /ws, /health, and /mcp
match first — and the JSON root endpoint is gone (its info lives in
/health and gateway_status). REST routers move from /api/* to
/api/v1/*; /ws and /health stay put; /mcp/ unchanged. Dashboard API
client, tests, README, and docs updated; dashboard rebuilt (build/ is
gitignored).

Verified live: / serves the UI, /api/v1 answers 200/401, the old
/api paths 404, MCP still lists 15 tools.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 14:57:45 -04:00

6.0 KiB

Call Flows

Call flows are reusable IVR navigation trees that tell Hold Slayer exactly how to navigate a company's phone menu. Once a flow is learned (manually or via exploration), subsequent calls to the same number skip the LLM analysis and follow the stored steps directly.

Data Model

CallFlowStep

A single step in the IVR navigation:

class CallFlowStep(BaseModel):
    id: str                          # Unique step identifier
    type: CallFlowStepType           # DTMF, WAIT, LISTEN, HOLD, SPEAK, TRANSFER
    description: str                 # Human-readable description
    dtmf: Optional[str] = None       # Digits to press (for DTMF steps)
    timeout: float = 10.0            # Max seconds to wait
    next_step: Optional[str] = None  # ID of the next step
    conditions: dict = {}            # Conditional branching rules
    metadata: dict = {}              # Extra data (transcript patterns, etc.)

Step Types

Type Purpose Key Fields
DTMF Press touch-tone digits dtmf="3"
WAIT Pause for a duration timeout=5.0
LISTEN Record + transcribe + decide timeout=15.0, optional dtmf for hardcoded response
HOLD Wait on hold, monitor for human timeout=7200 (max hold time)
SPEAK Play audio to the call metadata={"audio_file": "greeting.wav"}
TRANSFER Bridge call to user's device metadata={"device": "sip_phone"}

CallFlow

A complete IVR navigation tree:

class CallFlow(BaseModel):
    id: str                          # "chase_bank_main"
    name: str                        # "Chase Bank — Main Menu"
    company: Optional[str]           # "Chase Bank"
    phone_number: Optional[str]      # "+18005551234"
    description: Optional[str]       # "Navigate to disputes department"
    steps: list[CallFlowStep]        # Ordered list of steps
    created_at: datetime
    updated_at: datetime
    version: int = 1
    tags: list[str] = []             # ["banking", "disputes"]
    success_count: int = 0           # Times this flow succeeded
    fail_count: int = 0              # Times this flow failed

Example Call Flow

{
  "id": "chase_bank_disputes",
  "name": "Chase Bank — Disputes",
  "company": "Chase Bank",
  "phone_number": "+18005551234",
  "steps": [
    {
      "id": "wait_greeting",
      "type": "WAIT",
      "description": "Wait for greeting to finish",
      "timeout": 5.0,
      "next_step": "main_menu"
    },
    {
      "id": "main_menu",
      "type": "LISTEN",
      "description": "Listen to main menu options",
      "timeout": 15.0,
      "next_step": "press_3"
    },
    {
      "id": "press_3",
      "type": "DTMF",
      "description": "Press 3 for account services",
      "dtmf": "3",
      "next_step": "sub_menu"
    },
    {
      "id": "sub_menu",
      "type": "LISTEN",
      "description": "Listen to account services sub-menu",
      "timeout": 15.0,
      "next_step": "press_1"
    },
    {
      "id": "press_1",
      "type": "DTMF",
      "description": "Press 1 for disputes",
      "dtmf": "1",
      "next_step": "hold"
    },
    {
      "id": "hold",
      "type": "HOLD",
      "description": "Wait on hold for disputes agent",
      "timeout": 7200,
      "next_step": "transfer"
    },
    {
      "id": "transfer",
      "type": "TRANSFER",
      "description": "Transfer to user's phone"
    }
  ]
}

Call Flow Learner (services/call_flow_learner.py)

Automatically builds call flows from exploration data.

How It Works

  1. Exploration mode records "discoveries" — what the Hold Slayer encountered and did at each step
  2. The learner converts discoveries into CallFlowStep objects
  3. Steps are ordered and linked (next_step pointers)
  4. The resulting CallFlow is saved for future calls

Discovery Types

Discovery Becomes Step
Heard IVR prompt, pressed DTMF LISTENDTMF
Detected hold music HOLD
Detected silence (waiting) WAIT
Heard speech (human) TRANSFER
Sent DTMF digits DTMF

Building a Flow

learner = CallFlowLearner()

# After an exploration call completes:
discoveries = [
    {"type": "wait", "duration": 3.0, "description": "Initial silence"},
    {"type": "ivr_menu", "transcript": "Press 1 for billing...", "dtmf_sent": "1"},
    {"type": "ivr_menu", "transcript": "Press 3 for disputes...", "dtmf_sent": "3"},
    {"type": "hold", "duration": 480.0},
    {"type": "human_detected", "transcript": "Thank you for calling..."},
]

flow = learner.build_flow(
    discoveries=discoveries,
    phone_number="+18005551234",
    company="Chase Bank",
    intent="dispute a charge",
)
# Returns a CallFlow with 5 steps: WAIT → LISTEN/DTMF → LISTEN/DTMF → HOLD → TRANSFER

Merging Discoveries

When the same number is called again with exploration, new discoveries can be merged into the existing flow:

updated_flow = learner.merge_discoveries(
    existing_flow=flow,
    new_discoveries=new_discoveries,
)

This handles:

  • New menu options discovered
  • Changed IVR structure
  • Updated timing information
  • Success/failure tracking

REST API

List Call Flows

GET /api/v1/call-flows
GET /api/v1/call-flows?company=Chase+Bank
GET /api/v1/call-flows?tag=banking

Get Call Flow

GET /api/v1/call-flows/{flow_id}

Create Call Flow

POST /api/v1/call-flows
Content-Type: application/json

{
  "name": "Chase Bank — Disputes",
  "company": "Chase Bank",
  "phone_number": "+18005551234",
  "steps": [ ... ]
}

Update Call Flow

PUT /api/v1/call-flows/{flow_id}
Content-Type: application/json

{ ... updated flow ... }

Delete Call Flow

DELETE /api/v1/call-flows/{flow_id}

Learn Flow from Exploration

POST /api/v1/call-flows/learn
Content-Type: application/json

{
  "call_id": "call_abc123",
  "phone_number": "+18005551234",
  "company": "Chase Bank"
}

This triggers the Call Flow Learner to build a flow from the call's exploration data.