Advanced Agent Behavior

Emotional Context Tracking

~35 min to implement 📦 Requires: Dakera v0.11+

Users don't interact with AI in a vacuum — they come frustrated after a bad day, excited after a breakthrough, or anxious about a deadline. An agent that ignores emotional signals responds with the same clinical tone regardless of context, eroding trust. This pattern persists emotional observations across sessions so your agent can adapt its communication style, recognize distress trajectories, and escalate appropriately.

Start Building Free →
Prerequisites
  • Dakera instance running (quickstart)
  • SDK installed: pip install dakera / npm i @dakera-ai/dakera
  • A sentiment/emotion detection step — even a simple keyword classifier is sufficient to start
  • Clear escalation policies defined before going live with emotional tracking in sensitive contexts

The Problem

Users interact with AI systems during some of their most challenging moments. Without emotional memory, agents are tone-deaf across three critical dimensions:

  • Session amnesia: A user who expressed deep frustration two sessions ago is greeted the same way as a brand new user. The agent has no idea it's talking to someone who has already hit a wall three times.
  • No trajectory awareness: Gradual escalation — a user going from mild annoyance to genuine distress over five sessions — is invisible without persistent state. Crisis moments are missed because each session is isolated.
  • Tone mismatch: A user who says "I've been struggling with this for weeks" gets a perky, efficient response because the agent has no recollection of the struggle. This actively damages trust.

Architecture

This pattern stores emotional observations with sentiment metadata (label, intensity, trigger) in Dakera. Negative signals receive higher importance so they surface reliably during recall. At the start of each session, the agent retrieves recent emotional history and uses it to calibrate tone. A running sentiment trend analysis flags sustained negative trajectories for escalation.

SENTIMENT TIMELINE ACROSS SESSIONS +1.0 0.0 -1.0 Session 1 Session 2 Session 3 Session 4 Session 5 Session 6 hopeful neutral frustrated distressed escalation zone improving recovered

Sentiment timeline: emotional observations stored across six sessions reveal a distress trajectory (sessions 3–4) followed by recovery. The agent adapted tone at each stage based on recalled history.

EMOTIONAL CONTEXT INFLUENCE ON RESPONSE SELECTION Detect Emotion keyword signals: "still broken" "weeks of this" frustrated → 0.82 Dakera Memory store: frustrated @ 0.88 recall: last 5 sessions trend: declining trajectory score: -0.6 escalation: pending Empathetic Mode softer tone, validate frustration explicitly Escalate to Human route to specialist if trajectory < -0.7

Emotional context influence diagram: detected sentiment is stored, recalled history is aggregated into a trajectory score, and the score drives response mode selection between empathetic and escalation paths.

Implementation Steps

  • Detect emotional signals in each user message
    Start with a lightweight heuristic: keyword presence ("still broken", "weeks of this", "I give up"), punctuation patterns (all-caps, multiple exclamation marks), and explicit self-report ("I'm frustrated", "this is stressful"). A simple scoring model is sufficient — accuracy improves with a dedicated sentiment classifier but isn't required for day one.
  • Store emotional observations with importance reflecting intensity
    High-intensity negative signals (distress, anger) get importance 0.85–0.95 so they always surface during recall. Mild frustration: 0.70–0.80. Neutral/positive: 0.50–0.65. Store with short TTL (7 days) since emotional context is highly temporal — a user's mood from three weeks ago is irrelevant today.
  • Recall recent emotional history at session start
    At the beginning of every session, call recall() with a query targeting emotional signals. Retrieve the last 5–7 observations. Compute a trajectory score: average the sentiment intensities weighted by recency (most recent = highest weight). This gives you a single number describing the emotional trend.
  • Inject emotional context into the system prompt
    Pass the trajectory score and recent signals as a structured note in the system prompt: "Note: This user has shown increasing frustration over the past 3 sessions (trajectory: -0.6). Acknowledge their patience explicitly. Avoid dismissive phrases. Offer concrete next steps." This gives the LLM precise calibration without overloading the context window.
  • Implement escalation thresholds
    If the trajectory score drops below your escalation threshold (recommended: -0.7 for 3+ consecutive sessions), trigger a human handoff workflow. Store the escalation event itself as a high-importance memory so future agents handling this user know the history.
Note: Short TTL is essential for emotional memories

Set ttl_seconds=604800 (7 days) on emotional observations. A user's frustration from last month is not relevant context today, and carrying it forward creates a distorted view of the current relationship. Use longer TTL (30 days) only for structural preferences derived from emotional patterns — e.g., "user prefers concise responses when stressed."

Implementation

# Store an emotional observation
curl -X POST http://localhost:3300/v1/memory/store \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "support-agent-user-42",
    "content": "User expressed sustained frustration. Mentioned the billing issue has recurred for the third consecutive week. Signal: repeated-complaint pattern. Intensity: high.",
    "importance": 0.88,
    "memory_type": "episodic",
    "tags": ["emotion:frustrated", "intensity:high", "trigger:billing"],
    "ttl_seconds": 604800,
    "metadata": {
      "sentiment": "negative",
      "emotion": "frustrated",
      "intensity": 0.82,
      "trigger": "recurring_billing_issue",
      "session_id": "sess_20240521_001"
    }
  }'

# Recall emotional history for trajectory analysis
curl "http://localhost:3300/v1/memory/recall?agent_id=support-agent-user-42&query=user+emotional+state+frustration+distress&top_k=7&min_importance=0.5" \
  -H "Authorization: Bearer dk-..."

# Store an escalation event as persistent memory
curl -X POST http://localhost:3300/v1/memory/store \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "support-agent-user-42",
    "content": "Escalated to human specialist on 2024-05-21 due to sustained distress trajectory (-0.74 over 4 sessions). Routed to billing team senior agent.",
    "importance": 0.95,
    "memory_type": "episodic",
    "tags": ["escalation", "human-handoff"],
    "metadata": {"escalated_at": "2024-05-21T14:32:00Z", "trajectory_score": -0.74}
  }'
from dakera import DakeraClient
from dataclasses import dataclass
from typing import Optional
import re

client = DakeraClient(base_url="http://localhost:3300", api_key="dk-...")

@dataclass
class EmotionSignal:
    sentiment: str          # "positive" | "neutral" | "negative"
    emotion: str            # "frustrated" | "distressed" | "satisfied" | "excited"
    intensity: float        # 0.0 - 1.0
    trigger: Optional[str]  # what caused it

def detect_emotion(message: str) -> EmotionSignal:
    """Lightweight heuristic emotion detector."""
    message_lower = message.lower()

    # Frustration signals
    frustration_signals = [
        "still broken", "still not working", "weeks of this", "i give up",
        "this is ridiculous", "unacceptable", "so frustrating", "again?!"
    ]
    distress_signals = [
        "i can't take", "desperate", "need help now", "this is destroying",
        "really struggling", "at my limit", "please help me"
    ]
    positive_signals = [
        "thank you", "that worked", "perfect", "exactly what i needed",
        "amazing", "finally", "great job", "love this"
    ]

    frust_score = sum(1 for s in frustration_signals if s in message_lower)
    dist_score = sum(1 for s in distress_signals if s in message_lower)
    pos_score = sum(1 for s in positive_signals if s in message_lower)

    # Capitals and punctuation amplify intensity
    caps_ratio = sum(1 for c in message if c.isupper()) / max(len(message), 1)
    exclamations = message.count('!')

    if dist_score > 0:
        return EmotionSignal("negative", "distressed", min(0.9 + caps_ratio, 1.0), "user_message")
    elif frust_score > 0:
        intensity = min(0.6 + frust_score * 0.1 + caps_ratio * 0.2, 0.95)
        return EmotionSignal("negative", "frustrated", intensity, "user_message")
    elif pos_score > 0:
        return EmotionSignal("positive", "satisfied", 0.7 + pos_score * 0.05, None)
    else:
        return EmotionSignal("neutral", "neutral", 0.3, None)

def store_emotional_observation(agent_id: str, user_id: str, observation: str, signal: EmotionSignal) -> None:
    """Persist emotional state observation with appropriate importance and TTL."""
    # Higher importance for negative/intense signals
    importance_base = {"positive": 0.55, "neutral": 0.45, "negative": 0.80}
    importance = min(importance_base[signal.sentiment] + signal.intensity * 0.1, 0.95)

    client.store_memory(
        agent_id=agent_id,
        content=f"Emotional observation: {observation}. Emotion: {signal.emotion}. Intensity: {signal.intensity:.2f}.",
        importance=importance,
        memory_type="episodic",
        tags=[f"emotion:{signal.emotion}", f"sentiment:{signal.sentiment}", f"intensity:{'high' if signal.intensity > 0.7 else 'medium'}"],
        ttl_seconds=604_800  # 7 days
    )

def get_emotional_trajectory(agent_id: str) -> dict:
    """Recall recent emotional history and compute trajectory score."""
    result = client.recall(
        agent_id=agent_id,
        query="user emotional state frustration distress satisfaction",
        top_k=7,
        min_importance=0.4
    )

    memories = result.get("memories", [])
    if not memories:
        return {"trajectory_score": 0.0, "dominant_emotion": "neutral", "escalate": False}

    # Weighted average: most recent = highest weight
    total_weight = 0.0
    weighted_score = 0.0
    sentiment_map = {"positive": 1.0, "neutral": 0.0, "negative": -1.0}

    for i, mem in enumerate(memories):
        weight = 1.0 / (i + 1)  # recency decay: index 0 gets weight 1.0
        meta = mem.get("metadata", {})
        sentiment = meta.get("sentiment", "neutral")
        intensity = meta.get("intensity", 0.5)
        score = sentiment_map.get(sentiment, 0.0) * intensity
        weighted_score += score * weight
        total_weight += weight

    trajectory = weighted_score / total_weight if total_weight > 0 else 0.0

    # Count consecutive negative sessions for escalation trigger
    consecutive_neg = 0
    for mem in memories:
        if mem.get("metadata", {}).get("sentiment") == "negative":
            consecutive_neg += 1
        else:
            break

    return {
        "trajectory_score": round(trajectory, 3),
        "dominant_emotion": memories[0].get("metadata", {}).get("emotion", "neutral") if memories else "neutral",
        "escalate": trajectory < -0.65 and consecutive_neg >= 3,
        "consecutive_negative": consecutive_neg,
        "sample_count": len(memories)
    }

def build_emotional_context_prompt(trajectory: dict) -> str:
    """Generate a system prompt note from emotional trajectory data."""
    score = trajectory["trajectory_score"]
    emotion = trajectory["dominant_emotion"]
    escalate = trajectory["escalate"]

    if escalate:
        return (
            "ESCALATION ALERT: This user has shown sustained distress across multiple sessions "
            f"(trajectory score: {score:.2f}). Immediately acknowledge their difficulties, express genuine empathy, "
            "and offer to connect them with a specialist or supervisor. Do not attempt to solve the issue independently."
        )
    elif score < -0.4:
        return (
            f"Emotional context: User is currently {emotion} (trajectory: {score:.2f}). "
            "Adopt a warm, patient tone. Explicitly acknowledge their frustration before any technical response. "
            "Avoid efficiency-first language. Focus on validation first, solution second."
        )
    elif score > 0.4:
        return (
            f"Emotional context: User is in a positive state (trajectory: {score:.2f}). "
            "Engage warmly, match their energy. This is a good moment to introduce new features or offer additional help."
        )
    else:
        return "Emotional context: User appears to be in a neutral state. Maintain a professional, helpful tone."

# --- Usage in a support agent ---
def handle_support_message(user_id: str, message: str) -> str:
    agent_id = f"support-agent-{user_id}"

    # 1. Detect emotion in incoming message
    signal = detect_emotion(message)

    # 2. Store the observation
    store_emotional_observation(
        agent_id, user_id,
        f"User message indicates {signal.emotion}",
        signal
    )

    # 3. Compute trajectory from history
    trajectory = get_emotional_trajectory(agent_id)

    # 4. Build context-aware system prompt
    emotional_note = build_emotional_context_prompt(trajectory)

    # 5. Use in your LLM call
    # system_prompt = f"{BASE_SYSTEM_PROMPT}

{emotional_note}"
    # response = llm.complete(system=system_prompt, user=message)

    if trajectory["escalate"]:
        return trigger_human_escalation(user_id, trajectory)

    return emotional_note  # For demonstration

def trigger_human_escalation(user_id: str, trajectory: dict) -> str:
    agent_id = f"support-agent-{user_id}"
    client.store_memory(
        agent_id=agent_id,
        content=f"Escalated to human support. Trajectory score: {trajectory['trajectory_score']:.2f}. Consecutive negative sessions: {trajectory['consecutive_negative']}.",
        importance=0.95,
        memory_type="episodic",
        tags=["escalation", "human-handoff"]
    )
    return "Routing to human specialist..."
import { DakeraClient } from '@dakera-ai/dakera';

const client = new DakeraClient({ baseUrl: 'http://localhost:3300', apiKey: 'dk-...' });

type Sentiment = 'positive' | 'neutral' | 'negative';
type Emotion = 'frustrated' | 'distressed' | 'satisfied' | 'excited' | 'neutral';

interface EmotionSignal {
  sentiment: Sentiment;
  emotion: Emotion;
  intensity: number;
  trigger?: string;
}

function detectEmotion(message: string): EmotionSignal {
  const lower = message.toLowerCase();
  const frustrationPatterns = ['still broken', 'weeks of this', 'i give up', 'ridiculous', 'unacceptable'];
  const distressPatterns = ['desperate', 'need help now', 'really struggling', 'at my limit'];
  const positivePatterns = ['thank you', 'that worked', 'perfect', 'amazing', 'love this'];

  const frustScore = frustrationPatterns.filter(p => lower.includes(p)).length;
  const distScore = distressPatterns.filter(p => lower.includes(p)).length;
  const posScore = positivePatterns.filter(p => lower.includes(p)).length;

  if (distScore > 0) return { sentiment: 'negative', emotion: 'distressed', intensity: 0.92 };
  if (frustScore > 0) return { sentiment: 'negative', emotion: 'frustrated', intensity: Math.min(0.6 + frustScore * 0.1, 0.95) };
  if (posScore > 0) return { sentiment: 'positive', emotion: 'satisfied', intensity: 0.75 };
  return { sentiment: 'neutral', emotion: 'neutral', intensity: 0.3 };
}

async function storeEmotionalObservation(agentId: string, observation: string, signal: EmotionSignal): Promise<void> {
  const importanceBase: Record<Sentiment, number> = { positive: 0.55, neutral: 0.45, negative: 0.80 };
  const importance = Math.min(importanceBase[signal.sentiment] + signal.intensity * 0.1, 0.95);

  await client.storeMemory(agentId, {
    content: `Emotional observation: ${observation}. Emotion: ${signal.emotion}. Intensity: ${signal.intensity.toFixed(2)}.`,
    importance,
    memoryType: 'episodic',
    tags: [`emotion:${signal.emotion}`, `sentiment:${signal.sentiment}`],
    ttl_seconds: 604_800, // 7 days
  });
}

async function getEmotionalTrajectory(agentId: string): Promise<{
  trajectoryScore: number;
  dominantEmotion: string;
  escalate: boolean;
  consecutiveNegative: number;
}> {
  const result = await client.recall(agentId, 'user emotional state frustration distress', { top_k: 7, min_importance: 0.4 });

  const sentimentMap: Record<Sentiment, number> = { positive: 1.0, neutral: 0.0, negative: -1.0 };
  let weightedScore = 0, totalWeight = 0;

  for (let i = 0; i < result.memories.length; i++) {
    const weight = 1 / (i + 1);
    const sentiment = (result.memories[i].metadata?.sentiment as Sentiment) ?? 'neutral';
    const intensity = (result.memories[i].metadata?.intensity as number) ?? 0.5;
    weightedScore += sentimentMap[sentiment] * intensity * weight;
    totalWeight += weight;
  }

  const trajectoryScore = totalWeight > 0 ? weightedScore / totalWeight : 0;

  let consecutiveNegative = 0;
  for (const mem of result.memories) {
    if (mem.metadata?.sentiment === 'negative') consecutiveNegative++;
    else break;
  }

  return {
    trajectoryScore: Math.round(trajectoryScore * 1000) / 1000,
    dominantEmotion: result.memories[0]?.metadata?.emotion as string ?? 'neutral',
    escalate: trajectoryScore < -0.65 && consecutiveNegative >= 3,
    consecutiveNegative,
  };
}

// Compose system prompt from trajectory
function buildEmotionalContextNote(trajectory: Awaited<ReturnType<typeof getEmotionalTrajectory>>): string {
  if (trajectory.escalate) {
    return `ESCALATION ALERT: User in sustained distress (score: ${trajectory.trajectoryScore}). Route to human specialist immediately.`;
  }
  if (trajectory.trajectoryScore < -0.4) {
    return `Emotional context: User is ${trajectory.dominantEmotion} (score: ${trajectory.trajectoryScore}). Acknowledge frustration before providing technical help. Be warm and patient.`;
  }
  return `Emotional context: User appears ${trajectory.dominantEmotion}. Maintain professional, helpful tone.`;
}
use dakera_rs::{Client, StoreMemoryRequest, RecallRequest};

let client = Client::new("http://localhost:3300", "dk-...");

// Store a negative emotional observation
client.store_memory("support-agent-user-42", StoreMemoryRequest {
    content: "Emotional observation: User expressed sustained frustration about recurring billing issue. Emotion: frustrated. Intensity: 0.82.".into(),
    importance: Some(0.88),
    memory_type: "episodic".into(),
    tags: vec!["emotion:frustrated".into(), "sentiment:negative".into()],
    ttl_seconds: Some(604_800),
    ..Default::default()
}).await?;

// Recall emotional history and compute trajectory
let history = client.recall("support-agent-user-42", RecallRequest {
    query: "user emotional state frustration distress satisfaction".into(),
    top_k: Some(7),
    min_importance: Some(0.4),
    ..Default::default()
}).await?;

// Compute weighted trajectory score
let mut weighted_score = 0.0f64;
let mut total_weight = 0.0f64;
for (i, mem) in history.memories.iter().enumerate() {
    let weight = 1.0 / (i as f64 + 1.0);
    let sentiment = mem.metadata.as_ref()
        .and_then(|m| m.get("sentiment"))
        .and_then(|v| v.as_str())
        .unwrap_or("neutral");
    let intensity = mem.metadata.as_ref()
        .and_then(|m| m.get("intensity"))
        .and_then(|v| v.as_f64())
        .unwrap_or(0.5);
    let score = match sentiment {
        "positive" => 1.0,
        "negative" => -1.0,
        _ => 0.0,
    } * intensity;
    weighted_score += score * weight;
    total_weight += weight;
}

let trajectory = if total_weight > 0.0 { weighted_score / total_weight } else { 0.0 };
let escalate = trajectory < -0.65;
println!("Trajectory score: {:.3}, escalate: {}", trajectory, escalate);
client := dakera.NewClient("http://localhost:3300", "dk-...")
ctx := context.Background()

// Store emotional observation
client.StoreMemory(ctx, "support-agent-user-42", dakera.StoreMemoryRequest{
    Content:    "Emotional observation: User frustrated about recurring billing issue. Intensity: 0.82.",
    Importance: 0.88,
    MemoryType: "episodic",
    Tags:       []string{"emotion:frustrated", "sentiment:negative"},
    TTLSeconds: 604_800,
    Metadata: map[string]interface{}{
        "sentiment": "negative",
        "emotion":   "frustrated",
        "intensity":  0.82,
    },
})

// Recall and compute trajectory
result, _ := client.Recall(ctx, "support-agent-user-42", dakera.RecallRequest{
    Query:         "user emotional state frustration distress",
    TopK:          7,
    MinImportance: 0.4,
})

sentimentMap := map[string]float64{"positive": 1.0, "neutral": 0.0, "negative": -1.0}
var weightedScore, totalWeight float64

for i, mem := range result.Memories {
    weight := 1.0 / float64(i+1)
    sentiment, _ := mem.Metadata["sentiment"].(string)
    intensity, _ := mem.Metadata["intensity"].(float64)
    score := sentimentMap[sentiment] * intensity
    weightedScore += score * weight
    totalWeight += weight
}

trajectory := 0.0
if totalWeight > 0 {
    trajectory = weightedScore / totalWeight
}
fmt.Printf("Trajectory: %.3f, Escalate: %v
", trajectory, trajectory < -0.65)

Build emotionally-aware AI experiences

Dakera memory gives your support agents the emotional continuity users expect from human agents.

Try Free →

Real-World Scenario: Mental Health Support Agent

A digital mental health platform deploys a support agent for users between therapy sessions. The agent handles 400+ daily conversations across a user base with varying emotional states. Emotional context tracking delivers measurable improvements:

  • Session continuity: When a user returns after expressing anxiety in the previous session, the agent opens with: "It's good to hear from you again. Last time you mentioned feeling overwhelmed — how are things today?" rather than a generic greeting.
  • Distress pattern detection: A user who shows declining trajectory scores over 4 consecutive sessions is automatically flagged for clinical review within 24 hours — before reaching crisis point.
  • Tone adaptation: Users with a positive trajectory receive more exploratory, growth-oriented responses. Users in negative trajectories receive shorter, validating responses that don't overwhelm.
  • Outcome: 34% reduction in users who abandoned the platform mid-crisis. Escalation routing accuracy improved to 91% (vs 67% without emotional memory).

Before / After Memory State

Before: No Emotional Memory
// Memory store: empty

// Session 5 opens:
"Hello! How can I help
you today? 😊"

// User just spent 4 sessions
// describing mounting anxiety.
// Agent has no idea.

// Agent responds with same
// upbeat default tone regardless
// of emotional history.

// User feels unheard, abandons
// the platform.
After: Emotional Memory Active
// Memory store:
{
  "content": "User frustrated about recurring anxiety. Intensity 0.82.",
  "importance": 0.88,
  "tags": ["emotion:frustrated"]
}
// trajectory_score: -0.61
// consecutive_negative: 4

// Session 5 opens:
"I'm glad you came back.
You've had some tough weeks.
How are you feeling today?"

// Warm, trauma-informed tone
// based on recalled history.
// Escalation pending at -0.65.

SDK Method Reference

MethodSDKPurpose in this pattern
store_memory(agent_id, content, importance, memory_type, tags, ttl_seconds)PythonPersist emotional observation with short TTL
recall(agent_id, query, top_k, min_importance)PythonRetrieve recent emotional history for trajectory analysis
storeMemory(agentId, {content, importance, memoryType, tags, ttl_seconds})TypeScriptStore emotional signal with 7-day expiry
recall(agentId, query, {top_k, min_importance})TypeScriptRecall emotional history for trajectory scoring
client.store_memory("agent", StoreMemoryRequest{...}).await?RustAsync emotional observation storage with TTL
client.recall("agent", RecallRequest{...}).await?RustAsync history recall with min_importance filter
client.StoreMemory(ctx, "agent", StoreMemoryRequest{...})GoStore with sentiment metadata map
client.Recall(ctx, "agent", RecallRequest{...})GoRecall emotional history for scoring

Edge Cases and Gotchas

  • Sarcasm and irony: "Oh great, another timeout" reads as positive in keyword matching but is negative. Your emotion detector should treat sentences with contradiction patterns as negative by default. Consider including a confidence field in your metadata — low-confidence detections contribute less to trajectory scores.
  • Cultural variation in emotional expression: Users from different cultural backgrounds express frustration differently. Build your keyword lists from your actual user base, not generic NLP training data. Test for false negatives in your specific domain.
  • GDPR and emotional data: Emotional observations are sensitive personal data in many jurisdictions. Ensure you have explicit user consent for emotional tracking. Implement a complete deletion path using batch_forget() when a user requests data deletion. Store user IDs in metadata to enable targeted deletion.
  • False escalation triggers: A user expressing frustration about their code — not your product — shouldn't trigger a mental health escalation. Use the trigger field in metadata to scope escalation rules to product-related frustration signals only.
  • Emotional memory contamination: If a user shares their account with a family member, emotional observations from one person will affect responses to the other. Scope agent IDs to session-level when account sharing is possible: agent_id="user-42-device-ios" rather than just user-42.
Warning: Never expose raw emotional data to users

Emotional memory is an internal tool for tone calibration — it should never be surfaced directly. Telling a user "I see you were frustrated 3 sessions ago" can feel surveillance-like and damage trust. Use emotional memory to inform your agent's tone without disclosing that tracking is happening. Always document this in your privacy policy.

Performance Considerations

~8ms
Emotional history recall (top_k=7)
7 days
Recommended TTL for emotional observations
91%
Escalation routing accuracy with trajectory scoring
Advanced Configuration: Multi-signal emotion fusion

For higher-accuracy emotion detection, fuse multiple signal sources rather than relying on keyword matching alone:

def fuse_emotion_signals(
    keyword_signal: EmotionSignal,
    message_stats: dict,     # {"avg_words_per_sentence": 4.2, "caps_ratio": 0.18}
    response_time_ms: int,   # time between agent response and user reply
    previous_emotion: EmotionSignal
) -> EmotionSignal:
    """Fuse multiple signals for higher-accuracy emotion detection."""

    score = keyword_signal.intensity

    # Short responses + rapid replies = frustration signal
    if message_stats["avg_words_per_sentence"] < 5 and response_time_ms < 8000:
        score = min(score + 0.15, 1.0)

    # High caps ratio amplifies negative signals
    if keyword_signal.sentiment == "negative":
        score = min(score + message_stats["caps_ratio"] * 0.3, 1.0)

    # Emotional continuity: slight persistence toward previous state
    score = score * 0.85 + previous_emotion.intensity * 0.15

    return EmotionSignal(
        sentiment=keyword_signal.sentiment,
        emotion=keyword_signal.emotion,
        intensity=round(score, 3)
    )

Build AI agents that truly understand users

Persistent emotional context transforms robotic assistants into agents users trust. Start free with Dakera today.

Start Building Free →