By Sagar Shankaran, Founder of CallSphere
Learn how to build an AI intake agent for law firms that screens potential clients with structured questions, performs basic conflict checks, evaluates case viability, and schedules consultations — all while maintaining attorney-client confidentiality.
Key takeaways
Law firms lose potential clients at the intake stage more than anywhere else. A prospective client calls, gets voicemail, and moves on to the next firm. Studies show that the first firm to respond wins the client over 70 percent of the time. An AI intake agent answers every call immediately, asks the right qualifying questions, checks for conflicts of interest, and books a consultation — turning a missed call into a scheduled meeting.
This tutorial builds a legal intake agent that handles the complete client screening workflow while respecting the unique ethical requirements of legal practice.
Legal intake involves collecting structured information about the potential client, their legal matter, and any parties involved. The data model must capture enough detail for an attorney to decide whether to take the case.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent for law firm in your browser — 60 seconds, no signup.
flowchart LR
CALLER(["Prospective Client"])
subgraph TEL["Telephony"]
SIP["Twilio SIP and PSTN"]
end
subgraph BRAIN["Legal Intake AI Agent"]
STT["Streaming STT<br/>Deepgram or Whisper"]
NLU{"Intent and<br/>Entity Extraction"}
TOOLS["Tool Calls"]
TTS["Streaming TTS<br/>ElevenLabs or Rime"]
end
subgraph DATA["Live Data Plane"]
CRM[("CRM and Notes")]
CAL[("Calendar and<br/>Schedule")]
KB[("Knowledge Base<br/>and Policies")]
end
subgraph OUT["Outcomes"]
O1(["Consultation booked"])
O2(["Conflict check passed"])
O3(["Attorney callback queued"])
end
CALLER --> SIP --> STT --> NLU
NLU -->|Lookup| TOOLS
TOOLS <--> CRM
TOOLS <--> CAL
TOOLS <--> KB
NLU --> TTS --> SIP --> CALLER
NLU -->|Resolved| O1
NLU -->|Schedule| O2
NLU -->|Escalate| O3
style CALLER fill:#f1f5f9,stroke:#64748b,color:#0f172a
style NLU fill:#4f46e5,stroke:#4338ca,color:#fff
style O1 fill:#059669,stroke:#047857,color:#fff
style O2 fill:#0ea5e9,stroke:#0369a1,color:#fff
style O3 fill:#f59e0b,stroke:#d97706,color:#1f2937
from dataclasses import dataclass, field
from datetime import datetime, date
from enum import Enum
from typing import Optional
class PracticeArea(Enum):
FAMILY_LAW = "family_law"
PERSONAL_INJURY = "personal_injury"
CRIMINAL_DEFENSE = "criminal_defense"
BUSINESS_LAW = "business_law"
ESTATE_PLANNING = "estate_planning"
EMPLOYMENT_LAW = "employment_law"
REAL_ESTATE = "real_estate"
class IntakeStatus(Enum):
NEW = "new"
SCREENING = "screening"
CONFLICT_CHECK = "conflict_check"
QUALIFIED = "qualified"
DISQUALIFIED = "disqualified"
CONSULTATION_SCHEDULED = "consultation_scheduled"
@dataclass
class PotentialClient:
name: str
phone: str
email: str = ""
practice_area: Optional[PracticeArea] = None
matter_description: str = ""
opposing_parties: list[str] = field(default_factory=list)
urgency: str = "normal"
statute_of_limitations: Optional[date] = None
referral_source: str = ""
status: IntakeStatus = IntakeStatus.NEW
intake_timestamp: datetime = field(default_factory=datetime.now)
notes: str = ""
@dataclass
class ConflictRecord:
client_name: str
opposing_party: str
case_number: str
practice_area: str
is_active: bool
Each practice area requires different screening questions. We define question flows that the agent follows to gather the information attorneys need.
INTAKE_QUESTIONS = {
PracticeArea.PERSONAL_INJURY: {
"required": [
"When did the incident occur?",
"Where did the incident take place?",
"What type of incident was it (car accident, slip and fall, medical, other)?",
"Have you sought medical treatment?",
"Have you spoken with the other party's insurance company?",
"Are there any witnesses?",
],
"statute_years": 2,
"qualifier": "incident_date",
},
PracticeArea.FAMILY_LAW: {
"required": [
"What type of family matter (divorce, custody, adoption, support)?",
"Are there minor children involved?",
"Is there a current court order in place?",
"Has the other party retained an attorney?",
"Are there concerns about safety or domestic violence?",
],
"statute_years": None,
"qualifier": "matter_type",
},
PracticeArea.CRIMINAL_DEFENSE: {
"required": [
"Have you been charged, arrested, or are you under investigation?",
"What is the charge or suspected offense?",
"When is your next court date, if any?",
"Are you currently in custody or on bail?",
"Have you spoken with law enforcement about this matter?",
],
"statute_years": None,
"qualifier": "court_date",
},
}
Conflict of interest checking is an ethical obligation for law firms. The agent must verify that the firm does not already represent the opposing party in any matter.
class ConflictChecker:
def __init__(self):
self.records: list[ConflictRecord] = []
def add_record(self, record: ConflictRecord):
self.records.append(record)
def check_conflicts(
self, client_name: str, opposing_parties: list[str]
) -> dict:
conflicts_found = []
all_names = [client_name] + opposing_parties
for name in all_names:
name_lower = name.lower()
for record in self.records:
if (name_lower in record.client_name.lower()
or name_lower in record.opposing_party.lower()):
conflicts_found.append({
"matched_name": name,
"existing_client": record.client_name,
"case": record.case_number,
"active": record.is_active,
})
if conflicts_found:
return {
"has_conflict": True,
"conflicts": conflicts_found,
"recommendation": (
"Potential conflict of interest detected. "
"This matter must be reviewed by a senior attorney "
"before proceeding."
),
}
return {
"has_conflict": False,
"conflicts": [],
"recommendation": "No conflicts found. Safe to proceed with intake.",
}
conflict_checker = ConflictChecker()
conflict_checker.add_record(ConflictRecord(
"Smith Industries", "Johnson Corp", "2025-CV-1234", "business_law", True
))
from agents import Agent, Runner, function_tool
@function_tool
def get_intake_questions(practice_area: str) -> str:
"""Get the screening questions for a specific practice area."""
try:
area = PracticeArea(practice_area)
except ValueError:
areas = [a.value for a in PracticeArea]
return f"Unknown practice area. Available areas: {', '.join(areas)}"
questions = INTAKE_QUESTIONS.get(area, {}).get("required", [])
numbered = "\n".join(f"{i+1}. {q}" for i, q in enumerate(questions))
return f"Intake questions for {area.value}:\n{numbered}"
@function_tool
def run_conflict_check(
client_name: str, opposing_parties: str
) -> str:
"""Run a conflict of interest check against the firm's records."""
parties = [p.strip() for p in opposing_parties.split(",") if p.strip()]
result = conflict_checker.check_conflicts(client_name, parties)
if result["has_conflict"]:
details = "\n".join(
f" - {c['matched_name']} matches case {c['case']}"
for c in result["conflicts"]
)
return f"CONFLICT DETECTED:\n{details}\n{result['recommendation']}"
return "No conflicts found. Safe to proceed."
@function_tool
def evaluate_case(
practice_area: str, incident_date: str = "",
description: str = ""
) -> str:
"""Evaluate basic case viability based on statute of limitations and details."""
try:
area = PracticeArea(practice_area)
except ValueError:
return "Invalid practice area."
statute_years = INTAKE_QUESTIONS.get(area, {}).get("statute_years")
if statute_years and incident_date:
incident = date.fromisoformat(incident_date)
deadline = incident.replace(year=incident.year + statute_years)
days_remaining = (deadline - date.today()).days
if days_remaining < 0:
return f"WARNING: Statute of limitations likely expired ({deadline.isoformat()}). Attorney review required."
if days_remaining < 90:
return f"URGENT: Only {days_remaining} days until statute deadline ({deadline.isoformat()}). Expedite consultation."
return f"Statute of limitations: {days_remaining} days remaining (deadline: {deadline.isoformat()})."
return "Statute check not applicable for this practice area. Proceeding with intake."
@function_tool
def schedule_consultation(
client_name: str, phone: str, practice_area: str,
preferred_date: str, notes: str = ""
) -> str:
"""Schedule a consultation with an attorney."""
return (
f"Consultation scheduled for {client_name}\n"
f"Practice area: {practice_area}\n"
f"Date: {preferred_date}\n"
f"Phone: {phone}\n"
f"Pre-consultation notes: {notes}\n\n"
f"Please bring any relevant documents, correspondence, "
f"and a photo ID to your consultation."
)
legal_intake_agent = Agent(
name="Legal Intake Specialist",
instructions="""You are a professional legal intake specialist.
1. Begin by asking about the nature of the caller's legal matter.
Determine the practice area, then use get_intake_questions.
2. Work through the screening questions conversationally, not
as a rigid checklist.
3. Collect the names of all opposing parties, then run_conflict_check.
4. If relevant, use evaluate_case to check statute of limitations.
5. If no conflicts and the case appears viable, offer to
schedule_consultation.
CRITICAL ETHICAL RULES:
- Never provide legal advice. Say: "I can help you schedule a
consultation with one of our attorneys who can advise you."
- If a conflict is detected, do NOT proceed. Explain that the firm
may have a conflict and recommend the caller contact another firm.
- All information shared is confidential.
- Do not promise outcomes or make statements about case strength.""",
tools=[get_intake_questions, run_conflict_check, evaluate_case, schedule_consultation],
)
The agent instructions include a hard rule against providing legal advice. When callers press for advice, the agent responds with variations of "I understand the urgency. Our attorneys can address that specific question during your consultation. Let me get you scheduled as soon as possible." This redirects the conversation toward the actionable step — booking the meeting.
Still reading? Stop comparing — try CallSphere live.
See the law firm AI agent handle a real call — complete, industry-specific, and live in your browser. No signup.
The agent stops the intake process immediately and informs the caller that the firm may have a conflict that prevents representation. It does not disclose details about the existing client or case. It recommends the caller contact another firm and can optionally provide referral numbers. The intake record is flagged for attorney review.
Yes. Some callers have overlapping legal needs — for example, a car accident victim may need both personal injury and insurance coverage advice. The agent collects information for all relevant practice areas, runs conflict checks against all parties mentioned, and schedules the consultation with a note covering all areas so the attorney can prepare accordingly.
#LegalTech #ClientIntake #ConflictChecking #AIScheduling #Python #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
A step-by-step guide for German law firms in Berlin, Munich and beyond: use CallSphere AI voice and chat agents for DSGVO-compliant client intake, conflict pre-screening and after-hours Beratung booking in German and English.
For American law firms, a missed intake call is a lost case that goes to a competitor. See how CallSphere AI voice and chat agents capture every lead 24/7 and book consults.
UK law firms in Leeds, London and Manchester lose high-value clients the moment an intake call hits voicemail. How a CallSphere AI voice agent captures and books legal enquiries 24/7.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
Smolagents lets agents write Python instead of JSON. Why code-as-action reduces tool errors and where the security trade-offs are for production deployments.
Modal turns a Python function into autoscaling serverless compute with optional GPU. Deploy a LiveKit Agent with one command and get pay-per-second billing.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco