---
title: "Build a CallSphere-Style Multi-Agent for an MSP IT Helpdesk"
description: "Replace a single voicemail box with 4 specialists (auth, ticketing, password reset, escalation) that talk straight to ConnectWise/Halo PSA — full code."
canonical: https://callsphere.ai/blog/vw3h-build-callsphere-style-multi-agent-for-msp-it-helpdesk
category: "AI Voice Agents"
tags: ["MSP", "IT Helpdesk", "ConnectWise", "Multi-agent", "Tutorial"]
author: "CallSphere Team"
published: 2026-04-25T00:00:00.000Z
updated: 2026-05-07T09:59:36.906Z
---

# Build a CallSphere-Style Multi-Agent for an MSP IT Helpdesk

> Replace a single voicemail box with 4 specialists (auth, ticketing, password reset, escalation) that talk straight to ConnectWise/Halo PSA — full code.

> **TL;DR** — Single-vendor MSP voice tools (Thread Voice, MSP Process VoiceAssist) are great products but expensive at scale. A 4-specialist setup (auth, ticketing, password reset, escalation) on OpenAI Realtime + your PSA's API does the same job and lives inside your tenant.

## What you'll build

A 4-agent IT helpdesk that authenticates the caller against AD or Entra, opens a ticket in ConnectWise PSA or Halo, handles password resets via Microsoft Graph, and escalates to the on-call tech for outages.

## Prerequisites

1. PSA: ConnectWise Manage, Halo PSA, or Autotask.
2. Identity: Microsoft Graph or Okta.
3. OpenAI Realtime + Twilio.
4. Python 3.11+, `msal`, `httpx`, `openai-agents[voice]`.
5. A list of caller-id phone numbers per company for fast match.

## Architecture

```mermaid
flowchart TB
  C[Caller] --> TR[Triage]
  TR --> AU[Auth]
  AU --> TK[Ticketing]
  AU --> PW[Password Reset]
  AU --> ES[Escalation]
  TK --> PSA[(PSA API)]
  PW --> GRAPH[(MS Graph)]
  ES --> ONCALL[Twilio voice]
```

## Step 1 — Caller-ID auth

```python
@function_tool
async def identify_caller(phone: str) -> dict:
    contact = await psa.contacts.find_by_phone(phone)
    if not contact:
        return {"authenticated": False, "next_step": "ask_company_pin"}
    return {"authenticated": True, "company_id": contact.company_id,
            "name": contact.name, "tier": contact.support_tier}

@function_tool
async def verify_pin(company_pin: str, last_four_phone: str) -> dict:
    return await psa.auth.verify(pin=company_pin, last4=last_four_phone)
```

## Step 2 — Ticketing specialist

```python
@function_tool
async def open_ticket(company_id: str, contact_id: str, summary: str,
                      severity: str, category: str) -> dict:
    t = await psa.tickets.create(
        company_id=company_id, contact_id=contact_id,
        summary=summary, severity=severity, category=category,
        source="ai_voice")
    return {"ticket_id": t.id, "ref": t.ref}
```

## Step 3 — Password reset (Microsoft Graph)

```python
import msal, httpx

async def graph_token():
    app = msal.ConfidentialClientApplication(
        os.environ["AZ_CLIENT_ID"],
        authority=f"[https://login.microsoftonline.com/{os.environ['AZ_TENANT_ID']}](https://login.microsoftonline.com/%7Bos.environ%5B'AZ_TENANT_ID'%5D%7D)",
        client_credential=os.environ["AZ_CLIENT_SECRET"])
    r = app.acquire_token_for_client(scopes=["[https://graph.microsoft.com/.default"]](https://graph.microsoft.com/.default%22%5D))
    return r["access_token"]

@function_tool
async def reset_password(user_principal_name: str) -> dict:
    tok = await graph_token()
    new_pw = secrets.token_urlsafe(12)
    async with httpx.AsyncClient() as c:
        r = await c.patch(
            f"[https://graph.microsoft.com/v1.0/users/{user_principal_name}](https://graph.microsoft.com/v1.0/users/%7Buser_principal_name%7D)",
            headers={"Authorization": f"Bearer {tok}"},
            json={"passwordProfile": {"forceChangePasswordNextSignIn": True,
                  "password": new_pw}})
    if r.status_code in (200, 204):
        return {"ok": True, "temp_password": new_pw}
    return {"ok": False, "error": r.text}
```

## Step 4 — Outage escalation

```python
@function_tool
async def escalate_outage(company_id: str, summary: str) -> dict:
    t = await open_ticket(company_id, None, summary, "P1", "outage")
    oncall = await rotation.who_is_oncall()
    twilio.calls.create(to=oncall.phone, from_=BUSINESS_NUMBER,
                        url=f"[https://you/escalation?ticket={t['ref']}](https://you/escalation?ticket=%7Bt%5B'ref'%5D%7D)")
    return {"ticket_ref": t["ref"], "paged": oncall.name}
```

## Step 5 — Triage prompt

```md
You are the IT helpdesk front desk for [MSP]. Authenticate via
caller-id; if unknown, ask company PIN + last 4 of phone. Then
classify: password, outage, ticket. Hand off appropriately.
Never share temp passwords on the phone — always SMS.
```

## Step 6 — Twilio bridge

Same Realtime bridge pattern; pass `From` (caller-id) into the agent context as a session metadata field.

## Step 7 — Reporting

```sql
SELECT date_trunc('day', occurred_at) AS day,
  count(*) FILTER (WHERE outcome = 'ticket_opened') AS tickets,
  count(*) FILTER (WHERE outcome = 'pw_reset') AS resets,
  count(*) FILTER (WHERE outcome = 'escalated') AS escalations
FROM call_sessions GROUP BY 1 ORDER BY 1 DESC LIMIT 30;
```

## Common pitfalls

- **Voice-readable temp passwords.** Don't — SMS only.
- **Caller-ID spoofing.** Always require a second factor for password resets.
- **PSA rate limits.** ConnectWise caps API calls/min.

## How CallSphere does this in production

CallSphere's pattern of "triage → specialist → tool call → escalate" is exactly this design. Healthcare's FastAPI :8084 has 14 HIPAA-grade tools, OneRoof's 10 specialists run over WebRTC + Pion + NATS, Salon's 4 ElevenLabs agents emit `GB-YYYYMMDD-###` refs. 37 agents · 90+ tools · 115+ DB tables. Pricing $149/$499/$1499 with [14-day trial](/trial). [/affiliate](/affiliate) pays 22% to MSP partners.

## FAQ

**SOC2?** Use SOC2-compliant infrastructure; OpenAI Enterprise has SOC2.

**MFA?** Always — voice auth is necessary but not sufficient.

**Cost per ticket?** ~$0.20–$0.50 vs. $4–$8 for a human L1 touch.

**Time savings?** Reports of 25–40% L1 deflection in MSP studies.

**Multi-tenant?** `company_id` in every tool; row-level filtering.

## Sources

- [Thread Voice AI](https://www.getthread.com/blog/ai-service-unleashed-thread-voice-and-service-intelligence-revolutionize-the-msp-service-desk)
- [MSP Process VoiceAssist](https://mspprocess.com/ai-voiceassistant-for-msps/)
- [Microsoft Graph passwords](https://learn.microsoft.com/graph/api/user-update)
- [/affiliate](https://callsphere.ai/affiliate)

---

Source: https://callsphere.ai/blog/vw3h-build-callsphere-style-multi-agent-for-msp-it-helpdesk
