Creating a Customer Context Agent: Unifying Support Data

Creating a Customer Context Agent: Unifying Support Data Across Systems

Support agents switch between CRM, billing, product usage, and ticket history to understand a customer. Each system has different data formats, schemas, and access patterns. Context is fragmented across four or five different tools. The result: agents spend 5 minutes gathering context before they can solve a problem, first response time suffers, and customer satisfaction drops.

The problem: A customer emails support about a billing issue. The agent opens CRM to check account details, switches to billing to check payment status, opens product analytics to see usage, checks ticket history for previous issues. By the time they have full context, 5 minutes have passed. The customer is waiting. Other tickets are piling up.

The solution: A unified customer context view that joins data from CRM, billing, product analytics, and support systems into a single queryable interface. Support agents get complete customer context in seconds, not minutes. This post walks through building a repeatable architecture that transforms fragmented customer data into unified intelligence.


The Problem with Fragmented Customer Data

Customer data lives in silos. Here's what support agents face:

The Multi-System Problem

To understand a customer, agents need to check:

The math: If checking each system takes 1 minute and you handle 50 tickets per day, that's 200 minutes (3.3 hours) just switching between tools—before solving any problems.

The Schema Mismatch Problem

Each system uses different schemas:

The impact: Agents waste time mapping between systems, making mistakes, missing critical context.

The Access Control Problem

Different systems have different access controls:

The cost: Agents request access, wait for approvals, use workarounds. Security teams spend time managing permissions across systems.

The Real-Time Problem

Data is stale:

The missed opportunity: Agents make decisions based on outdated data. A customer's payment status changed 2 hours ago, but the agent doesn't know.


A Unified Customer Context Architecture

The solution is a unified view architecture that normalizes data across systems, joins related information, and provides a single queryable interface.

Architecture Components

1. Data Normalization Layer

2. Customer Context View

3. Real-Time Update Pipeline

4. Access Control Layer

5. MCP Tool Interface


Normalizing Data Across Systems

The first step is normalizing data from different systems into a unified schema. Each system has different field names, data types, and structures.

Schema Mapping

Map source system fields to unified schema:

CRM Fields → Unified Schema:

-- CRM (HubSpot/Salesforce) mapping
SELECT
    account_id as customer_id,
    account_name as customer_name,
    email,
    company_name,
    account_owner,
    plan_name,
    mrr,
    contract_value,
    renewal_date,
    customer_segment,
    'crm' as source_system
FROM hubspot_accounts;

Billing Fields → Unified Schema:

-- Billing (Stripe/Billing DB) mapping
SELECT
    customer_id,
    billing_status,
    last_payment_date,
    payment_method,
    EXTRACT(DAY FROM (CURRENT_DATE - last_payment_date)) as days_since_last_payment,
    total_revenue,
    'billing' as source_system
FROM stripe_customers;

Product Analytics Fields → Unified Schema:

-- Product Analytics (Postgres/Snowflake) mapping
SELECT
    customer_id,
    COUNT(DISTINCT user_id) as active_users,
    MAX(last_login_date) as last_login_date,
    EXTRACT(DAY FROM (CURRENT_DATE - MAX(last_login_date))) as days_since_last_login,
    AVG(feature_adoption_score) as feature_adoption_score,
    CASE
        WHEN AVG(daily_active_users) > LAG(AVG(daily_active_users), 7) OVER (PARTITION BY customer_id ORDER BY date) THEN 'increasing'
        WHEN AVG(daily_active_users) < LAG(AVG(daily_active_users), 7) OVER (PARTITION BY customer_id ORDER BY date) THEN 'declining'
        ELSE 'stable'
    END as usage_trend,
    'product' as source_system
FROM product_usage_events
GROUP BY customer_id;

Support History Fields → Unified Schema:

-- Support History (Zendesk/Intercom) mapping
SELECT
    customer_id,
    COUNT(*) as total_tickets,
    COUNT(*) FILTER (WHERE status = 'open') as open_tickets,
    AVG(EXTRACT(EPOCH FROM (resolved_at - created_at)) / 3600) as avg_resolution_time_hours,
    MAX(created_at) as last_ticket_date,
    MODE() WITHIN GROUP (ORDER BY ticket_type) as last_ticket_type,
    AVG(satisfaction_score) as satisfaction_score_avg,
    COUNT(*) FILTER (WHERE ticket_type = LAG(ticket_type) OVER (PARTITION BY customer_id ORDER BY created_at)) as repeat_issue_count,
    'support' as source_system
FROM support_tickets
GROUP BY customer_id;

Building the Unified View

Join normalized data into a comprehensive customer context view:

CREATE MATERIALIZED VIEW customer_context_comprehensive AS
SELECT
    -- Core Customer Info (from CRM)
    COALESCE(crm.customer_id, billing.customer_id, product.customer_id, support.customer_id) as customer_id,
    crm.customer_name,
    crm.email,
    crm.company_name,
    crm.account_owner,
    crm.plan_name,
    crm.mrr,
    crm.contract_value,
    crm.renewal_date,
    crm.customer_segment,

-- Billing Status
    billing.billing_status,
    billing.last_payment_date,
    billing.payment_method,
    billing.days_since_last_payment,
    billing.total_revenue,

-- Product Usage
    product.active_users,
    product.last_login_date,
    product.days_since_last_login,
    product.feature_adoption_score,
    product.usage_trend,

-- Support History
    support.total_tickets,
    support.open_tickets,
    support.avg_resolution_time_hours,
    support.last_ticket_date,
    support.last_ticket_type,
    support.satisfaction_score_avg,
    support.repeat_issue_count,

-- Calculated Fields
    CASE
        WHEN crm.mrr > 10000 THEN 'enterprise'
        WHEN crm.mrr > 1000 THEN 'mid_market'
        ELSE 'self_serve'
    END as customer_tier,

CASE
        WHEN support.open_tickets > 5 THEN 'high_support_load'
        WHEN support.open_tickets > 2 THEN 'medium_support_load'
        ELSE 'low_support_load'
    END as support_load_category,

CASE
        WHEN billing.billing_status != 'current' THEN 'billing_issue'
        WHEN product.days_since_last_login > 30 THEN 'inactive'
        WHEN support.repeat_issue_count > 3 THEN 'repeat_issues'
        ELSE 'healthy'
    END as risk_indicators,

-- Data Freshness
    GREATEST(
        crm.last_updated,
        billing.last_updated,
        product.last_updated,
        support.last_updated
    ) as context_last_updated,

CURRENT_TIMESTAMP as view_refreshed_at

FROM (
    SELECT * FROM hubspot_accounts_normalized
) crm
FULL OUTER JOIN (
    SELECT * FROM stripe_customers_normalized
) billing ON crm.customer_id = billing.customer_id
FULL OUTER JOIN (
    SELECT * FROM product_usage_normalized
) product ON COALESCE(crm.customer_id, billing.customer_id) = product.customer_id
FULL OUTER JOIN (
    SELECT * FROM support_history_normalized
) support ON COALESCE(crm.customer_id, billing.customer_id, product.customer_id) = support.customer_id
WHERE COALESCE(crm.customer_status, 'active') = 'active';

Building the Customer Context View

The customer context view is the foundation. It joins data from all systems, normalizes schemas, and provides calculated fields for quick insights.

View Structure

The view includes:

1. Core Customer Information (from CRM):

2. Billing Status (from billing system):

3. Product Usage (from analytics):

4. Support History (from support system):

5. Calculated Fields:

Materialization Strategy

Materialize the view for performance:

-- Create materialized view
CREATE MATERIALIZED VIEW customer_context_comprehensive AS
-- ... (view definition from above)

-- Create indexes for common queries
CREATE INDEX idx_customer_context_email ON customer_context_comprehensive(email);
CREATE INDEX idx_customer_context_tier ON customer_context_comprehensive(customer_tier);
CREATE INDEX idx_customer_context_risk ON customer_context_comprehensive(risk_indicators);

-- Refresh strategy: Incremental updates every 15 minutes
REFRESH MATERIALIZED VIEW CONCURRENTLY customer_context_comprehensive;

Refresh frequency:


Real-Time Context Updates

Customer context needs to stay fresh. Implement an incremental update pipeline that refreshes data as it changes.

Update Pipeline Architecture

Critical

Standard

Low Priority

Source System Change

Change Detection

Update Type

Immediate Refresh

Scheduled Refresh

Batch Refresh

Update Materialized View

Invalidate Cache

Agent Query

Incremental Refresh Logic

-- Incremental refresh for customer context
CREATE OR REPLACE FUNCTION refresh_customer_context_incremental()
RETURNS void AS $$
BEGIN
    -- Refresh only changed customers
    REFRESH MATERIALIZED VIEW CONCURRENTLY customer_context_comprehensive
    WHERE customer_id IN (
        -- Customers with recent changes in any source system
        SELECT DISTINCT customer_id
        FROM (
            SELECT customer_id FROM hubspot_accounts
            WHERE last_updated > CURRENT_TIMESTAMP - INTERVAL '15 minutes'
            UNION
            SELECT customer_id FROM stripe_customers
            WHERE last_updated > CURRENT_TIMESTAMP - INTERVAL '15 minutes'
            UNION
            SELECT customer_id FROM product_usage_events
            WHERE event_timestamp > CURRENT_TIMESTAMP - INTERVAL '15 minutes'
            UNION
            SELECT customer_id FROM support_tickets
            WHERE created_at > CURRENT_TIMESTAMP - INTERVAL '15 minutes'
        ) changed_customers
    );
END;
$$ LANGUAGE plpgsql;

-- Schedule incremental refresh every 15 minutes
SELECT cron.schedule(
    'refresh-customer-context',
    '*/15 * * * *',
    'SELECT refresh_customer_context_incremental();'
);

Event-Driven Updates

For critical changes, trigger immediate updates:

-- Trigger on support ticket creation
CREATE OR REPLACE FUNCTION on_ticket_created()
RETURNS TRIGGER AS $$
BEGIN
    -- Immediately refresh context for this customer
    REFRESH MATERIALIZED VIEW CONCURRENTLY customer_context_comprehensive
    WHERE customer_id = NEW.customer_id;

RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER ticket_created_trigger
AFTER INSERT ON support_tickets
FOR EACH ROW
EXECUTE FUNCTION on_ticket_created();

Agent Integration and Tool Design

Expose the customer context view through an MCP tool that agents can query. The tool should be self-documenting, flexible, and secure.

MCP Tool Definition

{
  "name": "get_customer_context",
  "description": "Retrieves comprehensive customer context including account details, billing status, product usage, and support history. Use when support agents need complete customer information to answer questions, troubleshoot issues, or understand customer situation.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "customer_email": {
        "type": "string",
        "description": "Customer email address to look up"
      },
      "customer_id": {
        "type": "string",
        "description": "Customer ID (alternative to email)"
      },
      "include_history": {
        "type": "boolean",
        "description": "Include detailed support history (default: true)",
        "default": true
      }
    },
    "required": ["customer_email"]
  },
  "query": "SELECT * FROM customer_context_comprehensive WHERE (email = $1 OR customer_id = $2) LIMIT 1",
  "policies": ["authenticated", "role:support"]
}

Tool Usage Examples

Example 1: Basic Customer Lookup

Agent query: "Get context for customer@example.com"
Tool call: get_customer_context(customer_email: "customer@example.com")
Response: Complete customer context with all fields

Example 2: Customer ID Lookup

Agent query: "What's the status of customer 12345?"
Tool call: get_customer_context(customer_id: "12345")
Response: Customer context filtered by ID

Example 3: Quick Context (No History)

Agent query: "Quick check on customer@example.com"
Tool call: get_customer_context(customer_email: "customer@example.com", include_history: false)
Response: Customer context without detailed support history

Response Formatting

Format the response for natural language consumption:

{
  "customer": {
    "name": "Acme Corp",
    "email": "contact@acme.com",
    "tier": "enterprise",
    "mrr": 15000,
    "plan": "Enterprise Pro"
  },
  "billing": {
    "status": "current",
    "last_payment": "2025-01-15",
    "method": "credit_card"
  },
  "usage": {
    "active_users": 250,
    "last_login": "2025-01-20",
    "trend": "increasing"
  },
  "support": {
    "open_tickets": 2,
    "total_tickets": 15,
    "avg_resolution_time": "4.5 hours",
    "satisfaction_score": 4.2
  },
  "risk_indicators": ["healthy"],
  "context_updated": "2025-01-20T10:30:00Z"
}

Schema Overview

The customer context architecture uses a hierarchical schema that traces from source systems to unified context.

Key relationships:

Traceability: Every customer context query can be traced back to source systems, normalization logic, and update timestamps.


Closing Thoughts

Building a unified customer context view transforms support operations from fragmented to cohesive. The key is treating customer data as a unified resource—not separate silos.

Key takeaways:

  1. Normalization is foundational: Map different schemas to a unified format. This makes joins possible and queries consistent.

  2. Materialization enables performance: Pre-compute joins and aggregations. Support agents need fast queries, not complex joins at query time.

  3. Real-time updates matter: Customer context changes frequently. Implement incremental refreshes and event-driven updates to keep data fresh.

  4. Tool design affects adoption: Make the MCP tool self-documenting and flexible. Support agents should understand when and how to use it.

  5. Access control is critical: Customer data is sensitive. Implement row-level and column-level security. Audit all queries.