Home / Articles

Multi-agent insurance claims triage on Microsoft Foundry

Insurance claims triage on Microsoft Foundry: a coordinator with coverage, fraud and settlement agents, checkpoints, App Insights and Container Apps hosting.

Publisher: REACTAPP.TOP13 min read2993 words

This article is published in English.

Sources: AZURE AI: Multi-agent claims triage on Microsoft Foundry

Imagine an insurer taking thousands of new claims daily from web forms and transcribed phone lines. Before payment, three checks matter: policy coverage on the incident date, fraud signals the SIU team maintains, and settlement band from the damage estimate. Manual sequential review is slow. A Foundry-hosted multi-agent triage can parallelize the specialists while keeping humans for exceptions.

Tools used

Azure AI Foundry model deployments, an agent/orchestration framework with handoffs, checkpointing for durable runs, Application Insights for traces, and Container Apps with managed identity for hosting.

Prerequisites

Azure subscription with Foundry access, ability to deploy container apps, App Insights workspace, and sample claim payloads for tests.

Project architecture

A coordinator agent routes work to three specialists (coverage, fraud, settlement). Tools wrap policy lookup, fraud list checks, and banding rules. Checkpoints persist mid-claim state. Telemetry spans each handoff.

Step 1. Create the Foundry resource and model deployments

Provision the project and deploy the chat model(s) the agents will use. Record deployment names carefully.

Step 2. Install the framework and point a chat client at Foundry

az group create \
  --name rg-claims-prod-uks \
  --location uksouth

az cognitiveservices account create \
  --name fdy-claims-prod-uks \
  --resource-group rg-claims-prod-uks \
  --location uksouth \
  --kind AIServices \
  --sku S0 \
  --assign-identity

az cognitiveservices account deployment create \
  --name fdy-claims-prod-uks \
  --resource-group rg-claims-prod-uks \
  --deployment-name gpt-4o \
  --model-name gpt-4o \
  --model-version 2024-08-06 \
  --model-format OpenAI \
  --sku-capacity 200 \
  --sku-name GlobalStandard

az cognitiveservices account deployment create \
  --name fdy-claims-prod-uks \
  --resource-group rg-claims-prod-uks \
  --deployment-name gpt-4o-mini \
  --model-name gpt-4o-mini \
  --model-version 2024-07-18 \
  --model-format OpenAI \
  --sku-capacity 400 \
  --sku-name GlobalStandard
python -m venv .venv
source .venv/bin/activate
pip install agent-framework azure-monitor-opentelemetry azure-identity azure-cosmos
# claims/clients.py
import os
from azure.identity import DefaultAzureCredential
from agent_framework.azure import AzureOpenAIChatClient

credential = DefaultAzureCredential()

coordinator_client = AzureOpenAIChatClient(
    endpoint=os.environ["FOUNDRY_ENDPOINT"],
    deployment_name="gpt-4o",
    credential=credential,
)

specialist_client = AzureOpenAIChatClient(
    endpoint=os.environ["FOUNDRY_ENDPOINT"],
    deployment_name="gpt-4o-mini",
    credential=credential,
)
# claims/agents.py
from claims.clients import coordinator_client, specialist_client

coordinator = coordinator_client.create_agent(
    name="triage",
    instructions=(
        "You are the first-line claims triage officer for a UK motor insurer. "
        "Read the claim and decide which specialist should handle it next. "
        "Route to 'policy_agent' when cover or policy dates are in question. "
        "Route to 'fraud_agent' when the description contains any indicator of "
        "staged loss, late notification, or inconsistent detail. "
        "Route to 'settlement_agent' when cover is confirmed and only the payout "
        "figure remains. Call the matching handoff tool, for example "
        "handoff_to_fraud_agent. Never state a settlement figure yourself."
    ),
)

policy_agent = specialist_client.create_agent(
    name="policy_agent",
    instructions=(
        "You verify whether the policy was in force on the incident date and "
        "whether the peril is covered. Use the lookup_policy tool. State the "
        "cover position plainly and cite the policy section. If cover is void, "
        "say so and stop; do not speculate on settlement."
    ),
)

fraud_agent = specialist_client.create_agent(
    name="fraud_agent",
    instructions=(
        "You score claims against the SIU signal list using the score_fraud_signals "
        "tool. Report each signal that fired and the aggregate score. Any score at "
        "or above 0.6 must be handed to policy_agent for a cover check before it "
        "goes anywhere near settlement."
    ),
)

settlement_agent = specialist_client.create_agent(
    name="settlement_agent",
    instructions=(
        "You band the damage estimate and propose a settlement using the "
        "band_settlement tool. Anything above 15000 GBP must be flagged for human "
        "authorisation in your response. Never approve a payment."
    ),
)
# claims/workflow.py
from agent_framework import HandoffBuilder
from claims.agents import coordinator, policy_agent, fraud_agent, settlement_agent

def build_triage_workflow():
    return (
        HandoffBuilder(
            name="claims_triage",
            description="Motor claims triage: cover, fraud signals, settlement banding.",
            participants=[coordinator, policy_agent, fraud_agent, settlement_agent],
        )
        .set_coordinator(coordinator)
        .add_handoff(coordinator, [policy_agent, fraud_agent, settlement_agent])
        .add_handoff(fraud_agent, policy_agent)
        .add_handoff(policy_agent, settlement_agent)
        .with_interaction_mode("autonomous", autonomous_turn_limit=12)
        .with_termination_condition(
            lambda conv: any(
                "AUTHORISATION REQUIRED" in (msg.text or "")
                for msg in conv[-2:]
            )
        )
        .build()
    )
# claims/tools.py
from typing import Annotated
from datetime import date
import httpx

POLICY_API = "https://pas.internal.claims-leeds.co.uk/v2"

async def lookup_policy(
    policy_number: Annotated[str, "The policy number, format MOT-00000000"],
    incident_date: Annotated[str, "Incident date as YYYY-MM-DD"],
) -> dict:
    """Return cover status and policy sections in force on the incident date."""
    async with httpx.AsyncClient(timeout=10.0) as client:
        r = await client.get(
            f"{POLICY_API}/policies/{policy_number}/cover",
            params={"as_at": incident_date},
        )
        r.raise_for_status()
        return r.json()

SIU_SIGNALS = {
    "late_notification_days": 14,
    "no_police_reference": True,
    "prior_claims_12m": 2,
}

def score_fraud_signals(
    description: Annotated[str, "Free-text claim description"],
    notified_after_days: Annotated[int, "Days between incident and notification"],
    prior_claims_12m: Annotated[int, "Claims by this policyholder in last 12 months"],
) -> dict:
    """Score a claim against the SIU signal list. Returns fired signals and aggregate."""
    fired = []
    if notified_after_days > SIU_SIGNALS["late_notification_days"]:
        fired.append("late_notification")
    if prior_claims_12m >= SIU_SIGNALS["prior_claims_12m"]:
        fired.append("claim_frequency")
    if "no witnesses" in description.lower():
        fired.append("no_independent_witness")
    return {"signals_fired": fired, "score": round(len(fired) / 3, 2)}

def band_settlement(
    estimate_gbp: Annotated[float, "Repair or replacement estimate in GBP"],
) -> dict:
    """Band an estimate and return the proposed settlement and authorisation route."""
    if estimate_gbp <= 2500:
        return {"band": "A", "proposed_gbp": estimate_gbp, "route": "auto"}
    if estimate_gbp <= 15000:
        return {"band": "B", "proposed_gbp": estimate_gbp, "route": "team_leader"}
    return {"band": "C", "proposed_gbp": estimate_gbp, "route": "AUTHORISATION REQUIRED"}
policy_agent = specialist_client.create_agent(
    name="policy_agent",
    instructions="...",
    tools=[lookup_policy],
)

fraud_agent = specialist_client.create_agent(
    name="fraud_agent",
    instructions="...",
    tools=[score_fraud_signals],
)

settlement_agent = specialist_client.create_agent(
    name="settlement_agent",
    instructions="...",
    tools=[band_settlement],
)
# claims/checkpoints.py
from agent_framework import InMemoryCheckpointStorage

# Tests and local runs
storage = InMemoryCheckpointStorage()
workflow = build_triage_workflow_with_storage(storage)

async for event in workflow.run_stream(claim_text, session_id=claim_id):
    handle(event)
# claims/telemetry.py
import os
from azure.monitor.opentelemetry import configure_azure_monitor
from agent_framework.observability import enable_instrumentation

def init_telemetry() -> None:
    configure_azure_monitor(
        connection_string=os.environ["APPLICATIONINSIGHTS_CONNECTION_STRING"],
    )
    enable_instrumentation()
dependencies
| where timestamp > ago(24h)
| where name startswith "invoke_agent"
| extend agent = tostring(customDimensions["gen_ai.agent.name"])
| extend claim = tostring(customDimensions["session.id"])
| summarize
    hops = count(),
    total_ms = sum(duration),
    p95_ms = percentile(duration, 95)
    by agent
| order by total_ms desc
# claims/run.py
import asyncio
from agent_framework import RequestInfoEvent
from claims.telemetry import init_telemetry
from claims.workflow import build_triage_workflow

CLAIM = """
Claim MOT-40118822. Policy MOT-00318844, incident 2026-07-29, notified 2026-08-19.
Insured reports offside wing and door damage in a supermarket car park, no witnesses,
no police reference. Third party details not supplied. Estimate 3,180 GBP.
"""

async def main() -> None:
    init_telemetry()
    workflow = build_triage_workflow()

    async for event in workflow.run_stream(CLAIM, session_id="MOT-40118822"):
        if isinstance(event, RequestInfoEvent):
            # Autonomous mode only surfaces this on escalation
            print(f"HUMAN NEEDED: {event.data.prompt}")
            break
        print(event)

asyncio.run(main())
az identity create \
  --name umi-claims-triage \
  --resource-group rg-claims-prod-uks

PRINCIPAL=$(az identity show -n umi-claims-triage -g rg-claims-prod-uks --query principalId -o tsv)
CLIENT=$(az identity show -n umi-claims-triage -g rg-claims-prod-uks --query clientId -o tsv)
FOUNDRY=$(az cognitiveservices account show -n fdy-claims-prod-uks -g rg-claims-prod-uks --query id -o tsv)

az role assignment create \
  --role "Azure AI User" \
  --assignee-object-id $PRINCIPAL \
  --assignee-principal-type ServicePrincipal \
  --scope $FOUNDRY

az containerapp create \
  --name ca-claims-triage \
  --resource-group rg-claims-prod-uks \
  --environment cae-claims-prod-uks \
  --image acrclaimsproduks.azurecr.io/claims-triage:1.0.0 \
  --user-assigned umi-claims-triage \
  --min-replicas 1 \
  --max-replicas 8 \
  --cpu 1.0 \
  --memory 2.0Gi \
  --env-vars \
    FOUNDRY_ENDPOINT=https://fdy-claims-prod-uks.openai.azure.com/ \
    AZURE_CLIENT_ID=$CLIENT \
    ENABLE_INSTRUMENTATION=true \
    OTEL_SERVICE_NAME=claims-triage \
    APPLICATIONINSIGHTS_CONNECTION_STRING=secretref:appi-conn
az group delete \
  --name rg-claims-prod-uks \
  --yes \
  --no-wait
az containerapp delete \
  --name ca-claims-triage \
  --resource-group rg-claims-prod-uks \
  --yes

az cognitiveservices account deployment delete \
  --name fdy-claims-prod-uks \
  --resource-group rg-claims-prod-uks \
  --deployment-name gpt-4o-mini

Configure endpoint, deployment, and Entra/managed identity auth—avoid shipping API keys in container images.

Step 3. Define the coordinator and the three specialists

Give each specialist a narrow system prompt and tool set. The coordinator only routes and aggregates; it should not re-implement specialist logic.

Step 4. Wire the handoff graph

Explicit handoff edges beat a free-for-all tool soup. Coverage → fraud → settlement is a sensible default; allow short-circuit to SIU when fraud score spikes.

Step 5. Give the specialists real tools

Tools must hit real systems or faithful stubs with schemas. Fake tools that always “approve” teach nothing.

Step 6. Checkpoint so a restart does not lose an in-flight claim

Persist thread/claim state after each specialist. Container restarts mid-claim should resume, not double-pay.

Step 7. Send traces and metrics to Application Insights

Emit custom events for handoffs, tool latency, and final band. Dashboards should show median time-to-triage versus the manual baseline.

Step 8. Run a claim through and read the result

Use a golden claim with known coverage and fraud outcomes. Assert the coordinator’s final package includes all three specialist results.

Step 9. Deploy on Container Apps with managed identity

Grant the identity rights to Foundry deployments and to any backend APIs the tools call. Lock down ingress.

Troubleshooting

Empty specialist output usually means wrong deployment name or truncated tool JSON. Loops often mean missing handoff termination conditions. Auth failures are RBAC, not “the model is down.”

Cost of running this

Model tokens dominate; checkpoint storage and App Insights are comparatively small. Cap max turns per claim and cache policy lookups.

Clean up

Delete container apps, identities, and deployments you created for the walkthrough so demo resources do not linger on the bill.

Closing

Multi-agent claims triage works when specialists are narrow, tools are real, checkpoints are durable, and telemetry proves latency wins. Foundry supplies the model home; the graph and operations discipline supply the product.

Add a human approval node for settlement bands above a threshold. Agents should accelerate triage, not silently authorize large payouts.

Version fraud rules separately from model prompts. When SIU updates a signal list, you should not need a prompt change to pick it up—tools should read the current list.

Load-test concurrent claims against rate limits on the Foundry deployment. Queue overflow behavior must be defined: reject, delay, or degrade to manual.

Privacy: claims text is sensitive. Ensure logs redact PANs/SSNs and that App Insights sampling does not store raw PII in custom dimensions.

Keep a chaos test that kills the container mid-claim and asserts resume from checkpoint without duplicate tool side effects—idempotency keys on writes are mandatory.

Add a human approval node for settlement bands above a threshold. Agents should accelerate triage, not silently authorize large payouts.

Version fraud rules separately from model prompts. When SIU updates a signal list, you should not need a prompt change to pick it up—tools should read the current list.

Load-test concurrent claims against rate limits on the Foundry deployment. Queue overflow behavior must be defined: reject, delay, or degrade to manual.

Privacy: claims text is sensitive. Ensure logs redact PANs/SSNs and that App Insights sampling does not store raw PII in custom dimensions.

Keep a chaos test that kills the container mid-claim and asserts resume from checkpoint without duplicate tool side effects—idempotency keys on writes are mandatory.

Add a human approval node for settlement bands above a threshold. Agents should accelerate triage, not silently authorize large payouts.

Version fraud rules separately from model prompts. When SIU updates a signal list, you should not need a prompt change to pick it up—tools should read the current list.

Load-test concurrent claims against rate limits on the Foundry deployment. Queue overflow behavior must be defined: reject, delay, or degrade to manual.

Privacy: claims text is sensitive. Ensure logs redact PANs/SSNs and that App Insights sampling does not store raw PII in custom dimensions.

Keep a chaos test that kills the container mid-claim and asserts resume from checkpoint without duplicate tool side effects—idempotency keys on writes are mandatory.

Add a human approval node for settlement bands above a threshold. Agents should accelerate triage, not silently authorize large payouts.

Version fraud rules separately from model prompts. When SIU updates a signal list, you should not need a prompt change to pick it up—tools should read the current list.

Load-test concurrent claims against rate limits on the Foundry deployment. Queue overflow behavior must be defined: reject, delay, or degrade to manual.

Privacy: claims text is sensitive. Ensure logs redact PANs/SSNs and that App Insights sampling does not store raw PII in custom dimensions.

Keep a chaos test that kills the container mid-claim and asserts resume from checkpoint without duplicate tool side effects—idempotency keys on writes are mandatory.

Add a human approval node for settlement bands above a threshold. Agents should accelerate triage, not silently authorize large payouts.

Version fraud rules separately from model prompts. When SIU updates a signal list, you should not need a prompt change to pick it up—tools should read the current list.

Load-test concurrent claims against rate limits on the Foundry deployment. Queue overflow behavior must be defined: reject, delay, or degrade to manual.

Privacy: claims text is sensitive. Ensure logs redact PANs/SSNs and that App Insights sampling does not store raw PII in custom dimensions.

Keep a chaos test that kills the container mid-claim and asserts resume from checkpoint without duplicate tool side effects—idempotency keys on writes are mandatory.

Add a human approval node for settlement bands above a threshold. Agents should accelerate triage, not silently authorize large payouts.

Version fraud rules separately from model prompts. When SIU updates a signal list, you should not need a prompt change to pick it up—tools should read the current list.

Load-test concurrent claims against rate limits on the Foundry deployment. Queue overflow behavior must be defined: reject, delay, or degrade to manual.

Privacy: claims text is sensitive. Ensure logs redact PANs/SSNs and that App Insights sampling does not store raw PII in custom dimensions.

Keep a chaos test that kills the container mid-claim and asserts resume from checkpoint without duplicate tool side effects—idempotency keys on writes are mandatory.

Add a human approval node for settlement bands above a threshold. Agents should accelerate triage, not silently authorize large payouts.

Version fraud rules separately from model prompts. When SIU updates a signal list, you should not need a prompt change to pick it up—tools should read the current list.

Load-test concurrent claims against rate limits on the Foundry deployment. Queue overflow behavior must be defined: reject, delay, or degrade to manual.

Privacy: claims text is sensitive. Ensure logs redact PANs/SSNs and that App Insights sampling does not store raw PII in custom dimensions.

Keep a chaos test that kills the container mid-claim and asserts resume from checkpoint without duplicate tool side effects—idempotency keys on writes are mandatory.

Add a human approval node for settlement bands above a threshold. Agents should accelerate triage, not silently authorize large payouts.

Version fraud rules separately from model prompts. When SIU updates a signal list, you should not need a prompt change to pick it up—tools should read the current list.

Load-test concurrent claims against rate limits on the Foundry deployment. Queue overflow behavior must be defined: reject, delay, or degrade to manual.

Privacy: claims text is sensitive. Ensure logs redact PANs/SSNs and that App Insights sampling does not store raw PII in custom dimensions.

Keep a chaos test that kills the container mid-claim and asserts resume from checkpoint without duplicate tool side effects—idempotency keys on writes are mandatory.

Specialist prompts should forbid inventing policy clauses. If the coverage tool returns empty, the specialist must escalate—not guess “probably covered.”

Put schema validation on tool arguments. A mistyped claim id should fail the tool call before it becomes a hallucinated investigation narrative.

Separate read tools from write tools. Triage agents default to read-only; settlement writes require the approval node and an idempotency key.

Map Application Insights failures to action: spike in fraud-tool timeouts pages SIU integration owners, not the LLM platform team by default.

Keep synthetic claims in a fixture pack that includes edge cases: lapsed policy, borderline fraud score, and multi-vehicle incidents that stress routing.

When demoing to executives, show the trace waterfall for one claim. The story is orchestration and controls, not model brand names.

Specialist prompts should forbid inventing policy clauses. If the coverage tool returns empty, the specialist must escalate—not guess “probably covered.”

Put schema validation on tool arguments. A mistyped claim id should fail the tool call before it becomes a hallucinated investigation narrative.

Separate read tools from write tools. Triage agents default to read-only; settlement writes require the approval node and an idempotency key.

Map Application Insights failures to action: spike in fraud-tool timeouts pages SIU integration owners, not the LLM platform team by default.

Keep synthetic claims in a fixture pack that includes edge cases: lapsed policy, borderline fraud score, and multi-vehicle incidents that stress routing.

When demoing to executives, show the trace waterfall for one claim. The story is orchestration and controls, not model brand names.

Specialist prompts should forbid inventing policy clauses. If the coverage tool returns empty, the specialist must escalate—not guess “probably covered.”

Put schema validation on tool arguments. A mistyped claim id should fail the tool call before it becomes a hallucinated investigation narrative.

Separate read tools from write tools. Triage agents default to read-only; settlement writes require the approval node and an idempotency key.

Map Application Insights failures to action: spike in fraud-tool timeouts pages SIU integration owners, not the LLM platform team by default.

Keep synthetic claims in a fixture pack that includes edge cases: lapsed policy, borderline fraud score, and multi-vehicle incidents that stress routing.

When demoing to executives, show the trace waterfall for one claim. The story is orchestration and controls, not model brand names.

Specialist prompts should forbid inventing policy clauses. If the coverage tool returns empty, the specialist must escalate—not guess “probably covered.”

Put schema validation on tool arguments. A mistyped claim id should fail the tool call before it becomes a hallucinated investigation narrative.

Separate read tools from write tools. Triage agents default to read-only; settlement writes require the approval node and an idempotency key.

Map Application Insights failures to action: spike in fraud-tool timeouts pages SIU integration owners, not the LLM platform team by default.

Keep synthetic claims in a fixture pack that includes edge cases: lapsed policy, borderline fraud score, and multi-vehicle incidents that stress routing.

When demoing to executives, show the trace waterfall for one claim. The story is orchestration and controls, not model brand names.