---
audience: engineers
summary: "End-to-end ScaiCore example \u2014 a stateful (entity-mode) customer support\
  \ agent with memory, plugins, and escalation."
title: 'Example: customer support agent'
path: reference/language/examples/customer-support-agent
status: published
---

A multi-channel customer support Core with conversation management,
intent routing, complaint handling, escalation, and learning.

```scaicore
// ============================================================================
// customer-support/main.scaicore
// ============================================================================

@core CustomerSupport {
    version = "1.0.0"
    description = "Handles customer inquiries across channels"

    // Per-customer instance: each customer gets isolated state
    instance = :entity(key = "customer_id") {
        idle_timeout = 30m
        max_concurrent = 1       // one conversation at a time per customer
        max_active_instances = 10000
        overflow = :deactivate_lru
    }

    @plugins {
        crm = company/salesforce@1.0
        knowledge = scailabs/knowledge-base@1.0
        billing = company/billing-system@2.0
        scaisend = scailabs/scaisend@1.0
        escalation = company/agent-routing@1.0
    }

    @llm {
        primary = {
            model = "scailabs/poolnoodle-omni"
            temperature = 0.5
            role = "general"
        }
        fast = {
            model = "scailabs/poolnoodle-mini"
            temperature = 0.3
            role = "classification"
        }
    }

    @memory {
        // Per-customer memory (exclusive to this customer's instance)
        interaction_history: array[InteractionRecord]
        preferences: CustomerPreferences
        escalation_count: int
        satisfaction_trend: array[float]
        active_conversation: ConversationState | null
    }

    // Read-only data — same for all instances, immutable at runtime
    @reference {
        product_knowledge: array[KnowledgeEntry]
        resolution_templates: map[string, Template]
        escalation_rules: array[EscalationRule]
        faq: map[string, FAQEntry]
    }

    @config {
        @param sentiment_escalation_threshold: float = -0.7 @hot_reload @runtime_configurable {
            description = "Sentiment threshold for automatic escalation"
            validation = value >= -1.0 && value <= 0.0
        }

        @param max_auto_responses: int = 10 @hot_reload {
            description = "Maximum automated responses before escalating"
        }

        @param escalation_keywords: array[string] = ["legal", "lawyer", "sue"] @hot_reload @runtime_configurable {
            description = "Keywords that trigger immediate escalation"
        }
    }

    @constraints {
        never = [
            "promise refund > €100 without approval",
            "share customer data with other customers",
            "admit legal liability",
            "make commitments about unannounced features",
            "diagnose issues as 'bug' without verification"
        ]
        always = [
            "log all interactions to CRM",
            "identify customer before discussing account details",
            "respect GDPR data access/deletion requests"
        ]
        prefer = [
            "resolve without escalation when possible",
            "de-escalation over defensiveness",
            "teach customer self-service when appropriate"
        ]
    }

    identity = {
        name = "Alex"
        personality = """
            Friendly, professional, solution-oriented.
            Acknowledges frustration without being defensive.
            Concise but thorough. Doesn't over-apologize.
        """
        languages = ["nl", "en", "de"]
    }

    @triggers {
        @webhook chat_message {
            flow = handle_message
            path = "/webhooks/chat"
        }
        @webhook email_message {
            flow = handle_message
            path = "/webhooks/email"
        }
        @api submit_ticket {
            flow = handle_message
        }
    }

    // Subscribe to events from other Cores
    @on billing_update from core://billing-system {
        flow = handle_billing_update
    }

    @conversation_policy {
        routing = {
            match_strategy = "flexible"
            match_on = [sender, session_id, topic_similarity]
            similarity_threshold = 0.8
            no_match = "new_conversation"
        }

        context = {
            always = [customer.profile, customer.tier, customer.recent_tickets(30d)]
            if_relevant = [customer.billing_status, customer.product_usage]
            on_demand = [customer.full_history]
            max_history_turns = 50
        }

        timeout = {
            idle = 30m
            absolute = 4h
            idle_warning = 25m
            idle_message = "Are you still there? I'm happy to continue helping."
        }

        channels = [chat, email, api, internal]
    }
}


// ============================================================================
// Types
// ============================================================================

@types {
    type Intent = enum[
        question, complaint, request, feedback,
        cancellation, billing_inquiry, bug_report,
        feature_request, other
    ]

    type Urgency = enum[low, medium, high, critical]

    type ConversationMode = enum[standard, empathetic, de_escalation]

    type Understanding = {
        primary_intent: Intent,
        secondary_intents: array[Intent]?,
        sentiment: float,
        urgency: Urgency,
        entities: {
            product: string?,
            order_id: string?,
            feature: string?,
            amount: money?,
            date_reference: date?
        },
        language: string
    }

    type HandlerResult = {
        status: enum[resolved, partial, pending, escalated],
        approach: string,
        follow_up: string?
    }

    type InteractionRecord = {
        timestamp: datetime,
        intent: Intent,
        sentiment: float,
        resolution_status: string,
        approach: string
    }

    type ResolutionPattern = {
        intent: Intent,
        context_signals: map[string, string],
        successful_approach: string,
        confirmed: bool
    }

    type KnowledgeEntry = {
        topic: string,
        content: string,
        source: "kb" | "learned",
        confidence: float
    }

    type EscalationOutcome = {
        reason: string,
        human_resolution: string,
        could_automate: bool,
        learned_pattern: string?
    }
}


// ============================================================================
// Main Interaction Flow
// ============================================================================

@flow handle_message(customer_id: string, incoming: Message): HandlerResult {
    @budget {
        max_duration = 30s
        on_exceeded = "warn"
    }

    // Establish context — customer_id already routed us to the right instance
    @rigid {
        customer = crm.get_profile(customer_id)
        conversation = conversation_policy.route(incoming, customer)
        conversation.append(role = "customer", content = incoming)
    }

    // Understand intent
    understanding = @flexible {
        goal = "Understand what the customer needs"
        llm = fast
        input = incoming
        context = {
            history: conversation.history,
            profile: customer.profile,
            previous_interactions: memory.interaction_history.last(5)
        }
        output = Understanding

        on_failure = {
            low_confidence = retry(max = 1, with = "focus on the primary intent")
            parse_error = retry(max = 2)
        }
    }

    @debug {
        log.info("Intent: ${understanding.primary_intent}, Sentiment: ${understanding.sentiment}")
    }

    // Sentiment-driven mode adjustment
    @rigid {
        conversation.mode = match understanding.sentiment {
            s if s < -0.5 => :de_escalation
            s if s < 0.0 => :empathetic
            _ => :standard
        }

        if understanding.sentiment < config.sentiment_escalation_threshold {
            crm.flag_ticket(
                id = conversation.ticket_id,
                flag = "frustrated_customer",
                notify = "support_lead"
            )
        }

        // Check for escalation keywords
        has_escalation_keyword = config.escalation_keywords.any(
            kw => incoming.content.lower().contains(kw)
        )
        if has_escalation_keyword {
            return @call escalate_to_human(
                conversation, customer, "Escalation keyword detected"
            )
        }

        // Check response count limit
        agent_responses = conversation.messages.filter(m => m.role == "agent").length
        if agent_responses >= config.max_auto_responses {
            return @call escalate_to_human(
                conversation, customer, "Maximum automated responses reached"
            )
        }
    }

    // Route to appropriate handler
    handler_result = match understanding.primary_intent {
        :question => @call handle_question(understanding, customer, conversation)
        :complaint => @call handle_complaint(understanding, customer, conversation)
        :billing_inquiry => @call handle_billing(understanding, customer, conversation)
        :cancellation => @call handle_cancellation(understanding, customer, conversation)
        :bug_report => @call handle_bug_report(understanding, customer, conversation)
        _ => @call handle_general(understanding, customer, conversation)
    }

    // Generate and send response
    response = @flexible {
        goal = "Formulate response to customer"
        input = { handler_result: handler_result, understanding: understanding }
        context = conversation.history
        identity = core.identity

        guidance = match conversation.mode {
            :de_escalation => """
                Acknowledge frustration explicitly.
                Lead with what you CAN do.
                Offer escalation to human proactively.
            """
            :empathetic => """
                Acknowledge the inconvenience.
                Be warm but stay solution-focused.
            """
            :standard => """
                Friendly and efficient.
                Get to the solution quickly.
            """
        }

        output = {
            message: string,
            suggested_actions: array[string]?,
            internal_notes: string?,
            follow_up_needed: bool
        }

        constraints = {
            inherit = true
        }
    }

    // Deliver response
    @rigid {
        conversation.append(role = "agent", content = response.message)

        deliver(
            channel = conversation.channel,
            message = response.message,
            actions = response.suggested_actions
        )

        crm.log_interaction({
            ticket_id = conversation.ticket_id,
            customer_id = customer.id,
            intent = understanding.primary_intent,
            sentiment = understanding.sentiment,
            resolution_status = handler_result.status,
            internal_notes = response.internal_notes
        })
    }

    // Record interaction in this customer's memory
    @rigid {
        memory.interaction_history.add({
            intent = understanding.primary_intent,
            sentiment = understanding.sentiment,
            resolution_status = handler_result.status,
            timestamp = now()
        })

        memory.satisfaction_trend.add(understanding.sentiment)
    }

    // Emit event for resolved interactions (knowledge Core can learn from this)
    if handler_result.status == :resolved {
        emit interaction_resolved {
            customer_id = customer.id
            intent = understanding.primary_intent
            context_signals = understanding.entities
            approach = handler_result.approach
            resolved_at = now()
        }
    }

    return handler_result
}


// ============================================================================
// Intent Handlers
// ============================================================================

@flow handle_question(understanding: Understanding, customer: Customer, conversation: Conversation): HandlerResult {
    // Search knowledge base and reference data in parallel
    @parallel {
        kb_results = knowledge.search(
            query = understanding.entities,
            context = customer.product_tier,
            limit = 5
        )

        // Reference data: read-only, same for all instances, fast local reads
        faq_matches = reference.faq.search(
            query = understanding.entities,
            limit = 3
        )

        // Instance memory: this customer's past interactions
        past_interactions = memory.interaction_history.search(
            query = "question ${understanding.entities}",
            limit = 3
        )
    }

    answer = @flexible {
        goal = "Answer the customer's question"
        input = {
            understanding: understanding,
            kb_results: kb_results,
            faq_matches: faq_matches,
            customer_history: past_interactions
        }
        output = {
            answered: bool,
            answer: string?,
            follow_up_question: string?,
            kb_article_ids: array[string]?
        }

        constraints = {
            prefer = ["cite specific KB articles", "link to self-service if applicable"]
            never = ["guess if KB has no answer"]
        }

        on_failure = {
            low_confidence = retry(max = 1)
        }
    }

    if answer.answered {
        return { status = :resolved, approach = "kb_answer" }
    } else if answer.follow_up_question != null {
        return { status = :partial, approach = "clarification_needed", follow_up = answer.follow_up_question }
    } else {
        return @call escalate_to_human(
            conversation, customer, "Could not find answer in knowledge base"
        )
    }
}


@flow handle_complaint(understanding: Understanding, customer: Customer, conversation: Conversation): HandlerResult {
    // Gather context
    @parallel {
        recent_orders = crm.orders(customer_id = customer.id, limit = 5)
        recent_tickets = crm.tickets(customer_id = customer.id, limit = 5)
        customer_value = crm.lifetime_value(customer.id)
    }

    // Assess the complaint
    assessment = @flexible {
        goal = "Understand the root cause and determine appropriate remedy"
        input = { understanding: understanding, orders: recent_orders, tickets: recent_tickets }
        output = {
            root_cause: string,
            our_fault: bool,
            suggested_remedy: enum[apology, discount, refund, replacement, escalate, investigation],
            remedy_value: money?,
            confidence: float
        }
    }

    // Apply business rules with guardrails
    remedy = @guarded {
        goal = "Determine appropriate compensation"
        input = { assessment: assessment, customer_value: customer_value }
        output = {
            approved: bool,
            remedy_type: string,
            remedy_value: money?,
            message: string
        }

        validate = {
            output.remedy_value == null || output.remedy_value <= 100.00
        }

        guidance = """
            High-value customers (>€5000 LTV) may warrant more generous remedies.
            Balance cost against retention value.
            Never exceed €100 without human approval.
        """

        on_validation_failure = {
            // Over €100 — send to human
            @checkpoint {
                type = "approval"
                assignee = "support_lead"
                present = {
                    customer = customer,
                    complaint = assessment,
                    proposed_remedy = assessment.remedy_value
                }
            }
        }
    }

    return {
        status = remedy.approved ? :resolved : :pending,
        approach = "complaint_${assessment.suggested_remedy}"
    }
}


@flow handle_billing(understanding: Understanding, customer: Customer, conversation: Conversation): HandlerResult {
    // Delegate to billing specialist Core (with instance routing)
    @try {
        billing_response = @core_call {
            target = core://billing-specialist
            instance_key = customer.billing_account_id
            version = "^2.0"
            input = {
                customer_id = customer.id,
                inquiry = understanding,
                conversation_context = conversation.summary
            }
            timeout = 30s
        }

        return {
            status = billing_response.status,
            approach = "billing_specialist_delegation"
        }
    } catch (TimeoutError e) {
        // Fallback to local handling
        local_response = @flexible {
            goal = "Address billing question with available information"
            input = understanding
            context = billing.customer_summary(customer.id)
            output = { answer: string, resolved: bool }
            constraints = {
                never = ["make changes to billing without verification"]
            }
        }

        return {
            status = local_response.resolved ? :resolved : :partial,
            approach = "billing_local_fallback"
        }
    }
}


// ============================================================================
// Escalation
// ============================================================================

@internal
@flow escalate_to_human(conversation: Conversation, customer: Customer, reason: string): HandlerResult {
    // Prepare handoff summary
    summary = @flexible {
        goal = "Summarize conversation for human agent handoff"
        llm = fast
        input = conversation.history
        output = {
            customer_issue: string,
            attempted_resolutions: array[string],
            customer_sentiment: string,
            recommended_approach: string
        }
    }

    @rigid {
        agent = escalation.find_agent(
            skills = [understanding.primary_intent],
            urgency = understanding.urgency,
            language = conversation.language
        )

        escalation.handoff(
            agent = agent,
            conversation = conversation,
            summary = summary
        )

        // Track escalations in this customer's instance memory
        memory.escalation_count = memory.escalation_count + 1
    }

    // Warm handoff message
    handoff_msg = @flexible {
        goal = "Write a warm handoff message for the customer"
        llm = fast
        input = { agent_name: agent.name, reason: reason }
        output = { message: string }
        guidance = """
            Let customer know they're being connected to a specialist.
            Reassure them their issue is being prioritized.
            Give realistic wait time if queue exists.
        """
    }

    @rigid {
        deliver(
            channel = conversation.channel,
            message = handoff_msg.message
        )
    }

    // Notify other systems about the escalation
    emit customer_escalated {
        customer_id = customer.id
        reason = reason
        escalation_count = memory.escalation_count
        sentiment = conversation.sentiment_trend
        agent = agent.name
        escalated_at = now()
    }

    return { status = :escalated, approach = "human_escalation: ${reason}" }
}


// ============================================================================
// Event Handlers
// ============================================================================

// Handles billing_update events from the billing-system Core
// This flow is triggered by the @on subscription in the @core block
@internal
@flow handle_billing_update(event: BillingUpdateEvent): void {
    @rigid {
        // Update this customer's memory with billing context
        memory.interaction_history.add({
            intent = :billing_update,
            sentiment = 0.0,
            resolution_status = :informational,
            timestamp = now(),
            notes = "Billing update: ${event.update_type}"
        })
    }
}
```


---