Now domain-agnostic — configure any practice with Domain Packs (immigration is the reference pack)
Workflows
Client Intake

Client Intake Workflow

Audience: Engineers building or debugging the client intake flow; operations staff configuring intake triggers.
What you will accomplish: Understand the 9-step intake workflow — from lead parsing to welcome email send — including gates, idempotency, and failure handling.
Prerequisites: Read Workflow Engine and MCP Surface first.
Estimated time: ~8 minutes.

Turn a lead — an email, a web form, a referral — into a structured matter, CRM contact, and opening task list in under 10 minutes. The AI extracts structured fields, deduplicates against existing contacts, creates records, computes deadlines, and drafts a welcome email — all behind a human approval gate.

Trigger paths

  1. MCP tool: intake.run — invoked by the AI agent directly
  2. Webhook: lead.created event from a connected CRM or web form platform

Both converge on the same workflow definition and idempotency key.

Workflow steps

  1. Step 1: parse_lead

    Extract structured fields (name, email, phone, case type, incident date) from the lead source using the extraction pipeline. Low-confidence fields become blocking gaps.

    document.extract → IntakeFields
  2. Step 2: dedupe_contact

    Search the CRM for existing contacts. Email exact match → A-number match → name similarity. Ambiguous results create a blocking gap for human triage.

    CRMConnector.find_contact
  3. Step 3: create_contact

    If no existing contact found, upsert a new CRM contact. Idempotent on (run_id, step). Skipped if reuse_contact was selected in step 2.

    contact.upsert — write (confirm)
  4. Step 4: create_matter

    Create a new matter in the case management system. Practice area derived from case_type. Privileged flag set on create. Idempotent on (run_id, step).

    matter.create — write (confirm)
  5. Step 5: compute_deadlines

    Apply deadline rule sets based on case type and jurisdiction. Uses business-day calendar, not naive date math. Past-due-on-create is flagged but does not block.

    deadline.compute + deadline.schedule
  6. Step 6: open_tasks

    Render the opening task checklist from CaseTypeConfig. Tasks are idempotent (uuid5 namespace + run_id + matter_id + title). Completeness invariant enforced.

    Task store — write (confirm)
  7. Step 7: draft_welcome

    Compose a context-aware welcome email using the status_update_email prompt template. Saved as draft only — no send.

    email.draft — draft only
  8. Step 8: GATE:human_review

    The run parks in awaiting_approval. An attorney reviews the extracted data, contact/matter creation, and draft email. Blocking gaps prevent approval.

    Required role: attorney — TTL: 24h
  9. Step 9: send_welcome

    Only after gate approval: send the drafted welcome email. Exactly-once delivery via (run_id, send_welcome) idempotency key.

    email.send — gated (human)

Idempotency keys

StepIdempotency keyCollision behaviour
Runsha256(source_channel:provider_event_id)[:16]Return existing run
Contact(run_id, create_contact)Return existing contact
Matter(run_id, create_matter)Return existing matter
Taskuuid5(namespace, run_id:matter_id:title)Return existing task
Send(run_id, send_welcome)Return existing send receipt

Gate configuration

gate: human_review
required_role: attorney
channels: [mcp, web, email]
ttl_seconds: 86400  # 24 hours
blocking_conditions:
  - ambiguous_dedupe  # step 2 returned ambiguous
  - extraction_gaps   # step 1 had low-confidence required fields

If any blocking condition is present, the gate cannot be approved until the condition is resolved (human triage via MCP tool, web UI, or email action).

Failure handling

FailureResponseRecovery
ConnectorError(transient)Retry with backoff (engine)Automatic, max 5 attempts
ConnectorError(auth)Park run; alert operationsHuman triage required
ConnectorError(fatal)Park run; alert operationsHuman triage required
Ambiguous dedupeBlocking gap at gateIntake coordinator resolves via triage UI
Past-due-on-create deadlineFlagged, logged, run continuesAttorney reviews flagged deadlines
Engine error on deadline computePark runRetry after fix

ASSUMPTION (confirm)

The following decisions are implemented with reasonable defaults but require firm sign-off before production:

  • I-001 — Exact intake field set per case type
  • I-002 — Case type enumeration (family-based, employment-based, other/uncategorised)
  • I-003 — Per-type opening task checklists
  • I-004 — Write-confirm steps auto-confirmed inside authorised intake.run
  • I-005 — Internal "matter opened" notification scope and gating
  • I-006 — Ambiguous dedupe — always human disambiguation (no auto-merge)
  • I-007 — Dedupe thresholds (email exact=1.0, name+DOB=0.90, A-number=1.0)

See docs/ASSUMPTIONS.md (opens in a new tab) for full tracking.

Example: invoking via MCP

Invoke intake.run via MCP tool
uv run python -c " import asyncio from cam.core.workflows.intake import tool_intake_run from cam.core.workflows.intake.types import LeadPayload  result = asyncio.run(tool_intake_run( lead=LeadPayload( channel="web_form", raw={ "email": "jane.doe@example.com", "name": "Jane Doe", "phone": "+1-555-123-4567", "case_type": "family-based", "incident_date": "2024-01-15" } ), idem_key=\"intake:web:2026-06-01:jane-doe\" )) print(result.status)  # awaiting_approval "

Verification

After gate approval, verify:

  1. matter.get(result.matter_id) returns the created matter
  2. contact.find("jane.doe@example.com") returns the created contact
  3. deadline.list(result.matter_id) shows computed deadlines
  4. Audit log shows create_contact, create_matter, compute_deadlines, draft_welcome, workflow.approve, send_welcome in sequence
  5. Hash chain is valid (AuditService.verify())

Next steps


Last updated: 2026-06-01