Skip to content

Engagement Engine

The Engagement Engine is AMP's inbound counterpart to the Content Pipeline. While the Content Pipeline handles outbound (Strategy → Content → Publish → Analytics → Optimize), the Engagement Engine handles inbound social signals through five distinct layers.

Overview

AMP Platform
├── Content Engine (Outbound)
│   └── Strategy → Content → Publish → Analytics → Optimize
└── Engagement Engine (Inbound)
    └── Sense → Understand → Decide → Act → Learn

The Engagement Engine transforms social signals into intelligent, automated responses while maintaining human oversight where needed.

Architecture

┌─────────────────────────────────────────────────────────────────────────┐
│                           SENSING LAYER                                  │
│  Mentions │ Comments │ DMs │ Ad Comments │ Keywords │ Competitor Signals │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│                        UNDERSTANDING LAYER                               │
│  Intent │ Sentiment │ Urgency │ Account Match │ Revenue │ Risk Flags    │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│                          DECISION LAYER                                  │
│  Policy Engine + Scoring → Respond │ Route │ Create Opp │ Suppress │ Adj│
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│                           ACTION LAYER                                   │
│  Reply │ DM │ Task Create │ CRM Update │ Ad Creative/Budget Adjustment  │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│                          LEARNING LAYER                                  │
│  Outcome Tracking │ Playbook Updates │ Threshold Calibration            │
└─────────────────────────────────────────────────────────────────────────┘

Layer Details

1. Sensing Layer

The Sensing Layer captures social signals from all connected platforms.

Signal Sources:

Source Description Collection Method
Mentions Direct @ mentions Pinchtab + API webhooks
Comments Replies to posts Pinchtab polling
DMs Direct messages Pinchtab (primary)
Ad Comments Comments on paid posts Pinchtab + Meta API
Brand Keywords Non-@ brand mentions Pinchtab search monitoring
Competitor Signals Competitor mentions/content Pinchtab monitoring

Signal Schema:

type Signal struct {
    ID           string          `json:"id"`
    TenantID     string          `json:"tenant_id"`
    Platform     Platform        `json:"platform"`
    SignalType   SignalType      `json:"signal_type"`
    MessageID    string          `json:"message_id"`
    AuthorID     string          `json:"author_id"`
    AuthorHandle string          `json:"author_handle"`
    AuthorName   string          `json:"author_name"`
    Content      string          `json:"content"`
    ParentID     *string         `json:"parent_id"`
    ThreadID     *string         `json:"thread_id"`
    URL          string          `json:"url"`
    MediaURLs    []string        `json:"media_urls"`
    Timestamp    time.Time       `json:"timestamp"`
    ReceivedAt   time.Time       `json:"received_at"`
    RawPayload   json.RawMessage `json:"raw_payload"`
}

Deduplication:

Signals are deduplicated using composite key: tenant_id + platform + message_id

func (s *SensingService) IsDuplicate(ctx context.Context, signal *Signal) bool {
    key := fmt.Sprintf("signal:seen:%s:%s:%s", signal.TenantID, signal.Platform, signal.MessageID)
    // Check Redis (fast) then PostgreSQL (durable)
}

2. Understanding Layer

The Understanding Layer enriches raw signals with AI-derived classifications.

Enrichments:

Field Method Output
Intent LLM Classification question, complaint, praise, purchase_intent, support_request, spam, other
Sentiment LLM + Rules Float -1.0 (negative) to +1.0 (positive)
Urgency LLM + Rules critical, high, normal, low
Account Match CRM Lookup existing_customer, prospect, vip, competitor, unknown
Revenue Potential Scoring Model Estimated value or tier
Risk Flags LLM + Rules legal_mention, influencer, journalist, competitor, profanity

Enriched Signal Schema:

type EnrichedSignal struct {
    Signal

    // Understanding enrichments
    Intent           Intent           `json:"intent"`
    Sentiment        float64          `json:"sentiment"`
    Urgency          Urgency          `json:"urgency"`
    AccountMatch     *AccountMatch    `json:"account_match"`
    RevenueScore     float64          `json:"revenue_score"`
    RiskFlags        []RiskFlag       `json:"risk_flags"`
    Topics           []string         `json:"topics"`
    Language         string           `json:"language"`

    // Processing metadata
    EnrichedAt       time.Time        `json:"enriched_at"`
    EnrichmentModel  string           `json:"enrichment_model"`
    Confidence       float64          `json:"confidence"`
}

Classification Prompt Template:

Analyze this social media message and classify:

Platform: {platform}
Author: {author_handle}
Content: {content}
Context: {thread_context}

Respond with JSON:
{
  "intent": "question|complaint|praise|purchase_intent|support_request|spam|other",
  "sentiment": -1.0 to 1.0,
  "urgency": "critical|high|normal|low",
  "risk_flags": ["legal_mention", "influencer", ...],
  "topics": ["topic1", "topic2"],
  "reasoning": "brief explanation"
}

3. Decision Layer

The Decision Layer applies business rules to determine actions.

Decision Outcomes:

Outcome Description
respond Auto-generate and send response
route Escalate to human with context
create_opportunity Create CRM deal/opportunity
suppress Log but take no action
trigger_adjustment Signal content/ad pipeline to adjust

Policy Engine:

Policies are evaluated in priority order. First matching policy wins.

policies:
  # High-risk always routes to human
  - name: "High Risk Escalation"
    priority: 100
    conditions:
      risk_flags:
        contains_any: [legal_mention, journalist, influencer]
    action: route
    route_to: pr_team
    sla_minutes: 30

  # VIP complaints get fast-tracked
  - name: "VIP Complaint"
    priority: 90
    conditions:
      account_match: vip
      sentiment: { lt: -0.3 }
      intent: complaint
    action: route
    route_to: customer_success
    sla_minutes: 15

  # Purchase intent creates opportunity
  - name: "Purchase Intent"
    priority: 80
    conditions:
      intent: purchase_intent
      risk_flags: { empty: true }
    action: respond
    response_template: purchase_interest
    also:
      - create_opportunity

  # Praise gets auto-response
  - name: "Auto Thank"
    priority: 50
    conditions:
      intent: praise
      sentiment: { gt: 0.5 }
      risk_flags: { empty: true }
    action: respond
    response_template: thank_you

  # Questions route for draft review
  - name: "Question Draft"
    priority: 40
    conditions:
      intent: question
    action: respond
    require_approval: true

  # Spam suppressed
  - name: "Suppress Spam"
    priority: 10
    conditions:
      intent: spam
    action: suppress
    reason: "Classified as spam"

  # Default: route to human
  - name: "Default Route"
    priority: 0
    conditions: {}
    action: route
    route_to: social_team

Policy Schema:

type Policy struct {
    ID            string                 `json:"id"`
    Name          string                 `json:"name"`
    Priority      int                    `json:"priority"`
    Conditions    PolicyConditions       `json:"conditions"`
    Action        ActionType             `json:"action"`
    ActionConfig  map[string]interface{} `json:"action_config"`
    AlsoActions   []ActionType           `json:"also_actions"`
    Enabled       bool                   `json:"enabled"`
}

type PolicyConditions struct {
    Intent         *Intent              `json:"intent,omitempty"`
    Sentiment      *NumericCondition    `json:"sentiment,omitempty"`
    Urgency        *Urgency             `json:"urgency,omitempty"`
    AccountMatch   *AccountMatchType    `json:"account_match,omitempty"`
    RiskFlags      *ArrayCondition      `json:"risk_flags,omitempty"`
    Platform       *Platform            `json:"platform,omitempty"`
    SignalType     *SignalType          `json:"signal_type,omitempty"`
}

Decision Record:

Every decision is logged for auditability and learning.

type Decision struct {
    ID              string          `json:"id"`
    SignalID        string          `json:"signal_id"`
    PolicyID        string          `json:"policy_id"`
    PolicyName      string          `json:"policy_name"`
    Action          ActionType      `json:"action"`
    ActionConfig    json.RawMessage `json:"action_config"`
    AlsoActions     []ActionType    `json:"also_actions"`
    DecidedAt       time.Time       `json:"decided_at"`
}

4. Action Layer

The Action Layer executes decisions across channels.

Action Types:

Action Execution Targets
Reply Pinchtab (native) or Ayrshare (API) Same thread/post
DM Pinchtab Direct to author
Task Create Internal queue or external (Linear, Asana) Team assignment
CRM Update HubSpot/Salesforce API Contact/Deal records
Ad Adjustment Meta/Google Ads API Budget, creative, targeting
Content Trigger Internal pipeline job New content generation

Response Generation:

type ResponseRequest struct {
    Signal          *EnrichedSignal      `json:"signal"`
    Template        string               `json:"template"`
    BrandContext    *BrandContext        `json:"brand_context"`
    ThreadHistory   []Signal             `json:"thread_history"`
    MaxLength       int                  `json:"max_length"`
    RequireApproval bool                 `json:"require_approval"`
}

type GeneratedResponse struct {
    ID              string    `json:"id"`
    SignalID        string    `json:"signal_id"`
    Content         string    `json:"content"`
    MediaURLs       []string  `json:"media_urls"`
    Status          string    `json:"status"` // draft, approved, sent, failed
    GeneratedAt     time.Time `json:"generated_at"`
    ApprovedAt      *time.Time `json:"approved_at"`
    ApprovedBy      *string   `json:"approved_by"`
    SentAt          *time.Time `json:"sent_at"`
    PlatformID      *string   `json:"platform_id"` // ID of sent message
}

Execution Flow:

sequenceDiagram
    participant Decision
    participant ActionQueue
    participant Executor
    participant Pinchtab
    participant Ayrshare
    participant CRM

    Decision->>ActionQueue: Queue action
    ActionQueue->>Executor: Dequeue

    alt Response Action
        alt Approval Required
            Executor->>Executor: Generate draft
            Executor->>Executor: Await approval
        end
        alt Native Execution
            Executor->>Pinchtab: Execute via browser
        else API Execution
            Executor->>Ayrshare: Execute via API
        end
    else CRM Action
        Executor->>CRM: Update record
    end

    Executor->>ActionQueue: Mark complete

5. Learning Layer

The Learning Layer measures outcomes and improves the system.

Tracked Outcomes:

Metric Description Measurement
Response Rate Did they reply to us? Track thread activity
Sentiment Shift Did sentiment improve? Re-analyze follow-ups
Conversion Did opportunity close? CRM outcome tracking
Escalation Rate How often auto-responses fail? Human takeover rate
Time to Response Meeting SLAs? Timestamp deltas
Engagement Lift Did our reply boost post? Engagement metrics

Feedback Schema:

type Outcome struct {
    ID              string                 `json:"id"`
    DecisionID      string                 `json:"decision_id"`
    SignalID        string                 `json:"signal_id"`
    ResponseID      *string                `json:"response_id"`

    // Outcome metrics
    GotReply        bool                   `json:"got_reply"`
    ReplySignalID   *string                `json:"reply_signal_id"`
    SentimentDelta  *float64               `json:"sentiment_delta"`
    Converted       *bool                  `json:"converted"`
    ConversionValue *float64               `json:"conversion_value"`
    Escalated       bool                   `json:"escalated"`
    TimeToResponse  *time.Duration         `json:"time_to_response"`

    // Timestamps
    MeasuredAt      time.Time              `json:"measured_at"`
}

Learning Loops:

  1. Template Effectiveness — Track which response templates get positive follow-ups
  2. Policy Tuning — Adjust thresholds based on escalation/success rates
  3. Classification Calibration — Compare predictions to human labels
  4. Content Feedback — Surface common questions to Content Pipeline for proactive content
type PlaybookUpdate struct {
    ID              string    `json:"id"`
    UpdateType      string    `json:"update_type"` // threshold, template, policy
    Target          string    `json:"target"`      // what's being updated
    OldValue        string    `json:"old_value"`
    NewValue        string    `json:"new_value"`
    Reason          string    `json:"reason"`
    TriggeredBy     string    `json:"triggered_by"` // metric that triggered
    AppliedAt       time.Time `json:"applied_at"`
}

Data Flow

sequenceDiagram
    participant Pinchtab
    participant Sensing
    participant Understanding
    participant Decision
    participant Action
    participant Learning

    Pinchtab->>Sensing: Raw signal
    Sensing->>Sensing: Deduplicate
    Sensing->>Understanding: Signal

    Understanding->>Understanding: Classify intent
    Understanding->>Understanding: Score sentiment
    Understanding->>Understanding: Check account match
    Understanding->>Understanding: Flag risks
    Understanding->>Decision: Enriched signal

    Decision->>Decision: Evaluate policies
    Decision->>Decision: Log decision
    Decision->>Action: Action + config

    Action->>Action: Generate response (if needed)
    Action->>Pinchtab: Execute
    Action->>Learning: Log execution

    Learning->>Learning: Track outcome
    Learning->>Decision: Update thresholds

NATS Topics

amp.engagement.signals.raw           # From Pinchtab/webhooks
amp.engagement.signals.enriched      # After Understanding
amp.engagement.decisions             # Decision records
amp.engagement.actions.pending       # Awaiting execution
amp.engagement.actions.approval      # Needs human approval
amp.engagement.actions.executed      # Completed actions
amp.engagement.outcomes              # Measured outcomes
amp.engagement.learning.updates      # Playbook changes

Database Tables

-- Signals
CREATE TABLE engagement_signals (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    platform TEXT NOT NULL,
    signal_type TEXT NOT NULL,
    message_id TEXT NOT NULL,
    author_id TEXT,
    author_handle TEXT,
    author_name TEXT,
    content TEXT,
    parent_id TEXT,
    thread_id TEXT,
    url TEXT,
    media_urls JSONB,
    timestamp TIMESTAMPTZ,
    received_at TIMESTAMPTZ NOT NULL,
    raw_payload JSONB,

    -- Enrichments
    intent TEXT,
    sentiment FLOAT,
    urgency TEXT,
    account_match_type TEXT,
    account_match_id UUID,
    revenue_score FLOAT,
    risk_flags TEXT[],
    topics TEXT[],
    language TEXT,
    enriched_at TIMESTAMPTZ,
    enrichment_model TEXT,
    confidence FLOAT,

    UNIQUE(tenant_id, platform, message_id)
);

-- Decisions
CREATE TABLE engagement_decisions (
    id UUID PRIMARY KEY,
    signal_id UUID NOT NULL REFERENCES engagement_signals(id),
    policy_id TEXT NOT NULL,
    policy_name TEXT NOT NULL,
    action TEXT NOT NULL,
    action_config JSONB,
    also_actions TEXT[],
    decided_at TIMESTAMPTZ NOT NULL
);

-- Responses
CREATE TABLE engagement_responses (
    id UUID PRIMARY KEY,
    signal_id UUID NOT NULL REFERENCES engagement_signals(id),
    decision_id UUID NOT NULL REFERENCES engagement_decisions(id),
    content TEXT NOT NULL,
    media_urls TEXT[],
    status TEXT NOT NULL, -- draft, pending_approval, approved, sent, failed
    generated_at TIMESTAMPTZ NOT NULL,
    approved_at TIMESTAMPTZ,
    approved_by UUID,
    sent_at TIMESTAMPTZ,
    platform_id TEXT,
    error_message TEXT
);

-- Outcomes
CREATE TABLE engagement_outcomes (
    id UUID PRIMARY KEY,
    decision_id UUID NOT NULL REFERENCES engagement_decisions(id),
    signal_id UUID NOT NULL REFERENCES engagement_signals(id),
    response_id UUID REFERENCES engagement_responses(id),
    got_reply BOOLEAN,
    reply_signal_id UUID,
    sentiment_delta FLOAT,
    converted BOOLEAN,
    conversion_value FLOAT,
    escalated BOOLEAN,
    time_to_response_ms BIGINT,
    measured_at TIMESTAMPTZ NOT NULL
);

-- Policies
CREATE TABLE engagement_policies (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    name TEXT NOT NULL,
    priority INT NOT NULL,
    conditions JSONB NOT NULL,
    action TEXT NOT NULL,
    action_config JSONB,
    also_actions TEXT[],
    enabled BOOLEAN DEFAULT true,
    created_at TIMESTAMPTZ NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL
);

Integration Points

With Twenty CRM

Twenty CRM serves as the central data hub for customer/prospect data.

Data Flow:

sequenceDiagram
    participant Signal
    participant Understanding
    participant Twenty
    participant Decision
    participant Action
    participant Learning

    Signal->>Understanding: Raw signal
    Understanding->>Twenty: Lookup author (GraphQL)
    Twenty-->>Understanding: Contact/Company data
    Understanding->>Understanding: Enrich with account match
    Understanding->>Decision: Enriched signal

    Decision->>Action: create_opportunity
    Action->>Twenty: POST /rest/opportunities
    Twenty-->>Action: Opportunity created

    Note over Twenty,Learning: Later...
    Twenty->>Learning: Webhook: opportunity.won
    Learning->>Learning: Update outcome.converted = true

Twenty Objects Used:

Twenty Object AMP Usage
People Social authors, contacts
Companies Author's organization (from LinkedIn, etc.)
Opportunities Deals created from purchase intent signals
Tasks Human follow-up tasks from routed signals
Notes Signal context attached to contacts

Account Matching (Understanding Layer):

// Query Twenty to match social author to existing contact
func (u *UnderstandingService) MatchAccount(ctx context.Context, signal *Signal) (*AccountMatch, error) {
    // Search by social handle
    query := `
        query FindPerson($handle: String!, $platform: String!) {
            people(filter: {
                or: [
                    { twitterHandle: { eq: $handle } }
                    { linkedinHandle: { eq: $handle } }
                    { instagramHandle: { eq: $handle } }
                ]
            }) {
                edges {
                    node {
                        id
                        name
                        email
                        company { id name }
                        isVIP: customFields(name: "is_vip")
                        lifetimeValue: customFields(name: "ltv")
                    }
                }
            }
        }
    `
    // Returns AccountMatch with type (vip, existing_customer, prospect, unknown)
}

Opportunity Creation (Action Layer):

// Create opportunity when purchase intent detected
func (a *ActionService) CreateOpportunity(ctx context.Context, signal *EnrichedSignal) error {
    opp := map[string]interface{}{
        "name":        fmt.Sprintf("Inbound: %s via %s", signal.AuthorHandle, signal.Platform),
        "stage":       "NEW",
        "personId":    signal.AccountMatch.AccountID,
        "source":      string(signal.Platform),
        "amount":      signal.RevenueScore,
        "customFields": map[string]interface{}{
            "signal_id":    signal.ID,
            "signal_type":  signal.SignalType,
            "intent":       signal.Intent,
            "original_url": signal.URL,
        },
    }

    return a.twenty.CreateOpportunity(ctx, opp)
}

Webhook Handlers (Learning Layer):

// Handle Twenty webhooks for outcome tracking
func (l *LearningService) HandleTwentyWebhook(ctx context.Context, event TwentyWebhookEvent) error {
    switch event.Type {
    case "opportunity.updated":
        if event.Data.Stage == "WON" {
            // Find original signal from custom field
            signalID := event.Data.CustomFields["signal_id"]
            return l.RecordConversion(ctx, signalID, event.Data.Amount)
        }
    case "task.completed":
        // Track human follow-up completion
        return l.RecordTaskCompletion(ctx, event.Data.ID)
    }
    return nil
}

Twenty Custom Fields for AMP:

Add these custom fields to Twenty objects:

Object Field Type Purpose
People twitter_handle Text Twitter/X username
People linkedin_handle Text LinkedIn username
People instagram_handle Text Instagram username
People tiktok_handle Text TikTok username
People is_vip Boolean VIP flag for priority routing
People ltv Currency Lifetime value for scoring
Opportunities signal_id Text Link back to engagement signal
Opportunities signal_type Text Type of signal that created opp
Opportunities original_url URL Link to original social post
Tasks signal_id Text Link to routed signal
Tasks urgency Select From signal urgency classification

With Content Pipeline

The Engagement Engine feeds back to the Content Pipeline:

  1. Common Questions → Generate FAQ content
  2. Product Feedback → Inform content strategy
  3. Trending Topics → Real-time content opportunities
  4. Competitor Mentions → Competitive content response

With External Systems

System Integration Purpose
HubSpot/Salesforce API Create/update contacts, deals
Linear/Asana API Create tasks for human follow-up
Slack Webhook Real-time alerts for critical signals
Meta Ads API Adjust ad spend based on engagement
Google Ads API Adjust campaigns based on signals

Automation Levels

Tenants can configure their automation level:

Level Sensing Understanding Decision Action
0 - Monitor Auto Auto Log only None
1 - Triage Auto Auto Auto Route all
2 - Draft Auto Auto Auto Draft, human approves
3 - Selective Auto Auto Auto Auto low-risk, route high
4 - Full Auto Auto Auto Auto Auto all except critical

Security Considerations

  1. Session Isolation — Each tenant's Pinchtab sessions are isolated
  2. Credential Storage — Social credentials encrypted at rest
  3. Audit Trail — All decisions and actions logged
  4. Rate Limiting — Per-tenant action limits prevent abuse
  5. Human Oversight — Critical signals always route to humans
  6. Content Moderation — Generated responses checked before send

Monitoring

Key metrics to track:

  • Signal ingestion rate (per platform, per tenant)
  • Enrichment latency (p50, p95, p99)
  • Decision latency
  • Action success rate
  • Human approval queue depth
  • Outcome tracking coverage
  • Policy match distribution