2 Data Flow 04 Message Classification
blightbow edited this page 2025-12-09 02:36:45 +00:00

Data Flow 04: Message Classification

Engineering documentation series - Data flows in the AI Assistant system


Overview

The message classification system routes incoming messages to appropriate handlers based on source type, sender permissions, and addressing. Classification determines whether a message triggers AI action, provides context, or is ignored.

Document Description
Data-Flow-02-ReAct-Loop How triggers lead to execution

1. Message Entry Point

Hook Chain

Evennia Message System
  └─> AssistantCharacter.at_msg_receive()
        └─> classify_message()
              └─> Handler based on classification

2. Classification Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│ MESSAGE RECEIVED                                                            │
│ assistant_character.py::at_msg_receive()                                    │
│ Input: text, from_obj, **kwargs                                             │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ EXTRACT OOB DATA                                                            │
│ ─────────────────────────────────────────────────────────────────────────── │
│ Message formats:                                                            │
│   - str: Plain text                                                         │
│   - tuple: (text_str, oob_dict) - Evennia's standard format                 │
│                                                                             │
│ Merge OOB sources (kwargs take precedence):                                 │
│   merged_oob = {**oob_data, **kwargs}                                       │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ DETECT SOURCE TYPE                                                          │
│ messaging/classification.py::detect_source_type()                           │
│ ─────────────────────────────────────────────────────────────────────────── │
│ Priority order:                                                             │
│   1. from_channel presence → "channel"                                      │
│   2. type key in OOB tuple → "say"/"pose"/"whisper"                         │
│   3. type key in kwargs → fallback                                          │
│   4. Pattern matching → "page" (HEURISTIC ONLY)                             │
│                                                                             │
│ Returns: "channel" | "say" | "pose" | "whisper" | "page" | "emit" | "unknown│
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ CLASSIFICATION RULES (ORDER MATTERS)                                        │
│ messaging/classification.py::classify_message()                             │
└─────────────────────────────────────────────────────────────────────────────┘

3. Classification Rules

Rules are evaluated in order. First match wins.

Rule 0: Disabled State Check

┌─────────────────────────────────────────────────────────────────────────────┐
│ RULE 0: INTERACTION DISABLED                                                │
│ ─────────────────────────────────────────────────────────────────────────── │
│ Condition: interaction_enabled == False                                     │
│ Result: IGNORE                                                              │
│ Reason: "interaction_disabled"                                              │
│                                                                             │
│ This overrides ALL other rules including permissions and addressing.        │
└─────────────────────────────────────────────────────────────────────────────┘

Rule 1: Self-Message Handling

┌─────────────────────────────────────────────────────────────────────────────┐
│ RULE 1: SELF-MESSAGE DETECTION                                              │
│ ─────────────────────────────────────────────────────────────────────────── │
│ Condition: sender == self                                                   │
│                                                                             │
│ Sub-rules:                                                                  │
│                                                                             │
│ 1a. If assistant_message flag set:                                          │
│     → IGNORE (prevent conversation loops)                                   │
│     Reason: "self_message_conversational"                                   │
│                                                                             │
│ 1b. If command_output_buffer active:                                        │
│     → CAPTURE (return True, not tuple)                                      │
│     Action: Append to ndb.command_output_buffer                             │
│     Used by: CommandExecutionTool                                           │
│                                                                             │
│ 1c. Otherwise (uncaptured command output):                                  │
│     → CONTEXT                                                               │
│     Reason: "self_command_output_uncaptured"                                │
└─────────────────────────────────────────────────────────────────────────────┘

Rule 2: Direct Addressing (@mention)

┌─────────────────────────────────────────────────────────────────────────────┐
│ RULE 2: DIRECT ADDRESSING                                                   │
│ ─────────────────────────────────────────────────────────────────────────── │
│ Condition: enable_addressing == True                                        │
│            AND @mention matches assistant_key                               │
│                                                                             │
│ Detection: messaging/classification.py::detect_addressing()                 │
│   1. OOB metadata "addressing" key (trustworthy)                            │
│   2. @mention pattern in text (user-generated)                              │
│                                                                             │
│ Result: TRIGGER                                                             │
│ Reason: "direct_addressing"                                                 │
│ Metadata: {addressing, sender, source_type}                                 │
└─────────────────────────────────────────────────────────────────────────────┘

Rule 3: Direct Messages

┌─────────────────────────────────────────────────────────────────────────────┐
│ RULE 3: DIRECT/PRIVATE MESSAGES                                             │
│ ─────────────────────────────────────────────────────────────────────────── │
│ Condition: source_type in ["whisper", "page"]                               │
│                                                                             │
│ Result: TRIGGER                                                             │
│ Reason: "direct_message"                                                    │
│ Metadata: {source_type, sender}                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Rule 4: Assistant Cross-Talk

┌─────────────────────────────────────────────────────────────────────────────┐
│ RULE 4: OTHER ASSISTANT MESSAGES                                            │
│ ─────────────────────────────────────────────────────────────────────────── │
│ Condition: assistant_message flag set (from another assistant)              │
│                                                                             │
│ Result: CONTEXT                                                             │
│ Reason: "assistant_crosstalk"                                               │
│ Metadata: {assistant_key, sender, source_type}                              │
│                                                                             │
│ Purpose: Prevent assistant-to-assistant response loops                      │
└─────────────────────────────────────────────────────────────────────────────┘

Rule 5: Permission-Based Trigger

┌─────────────────────────────────────────────────────────────────────────────┐
│ RULE 5: SENDER HAS TRIGGER PERMISSION                                       │
│ ─────────────────────────────────────────────────────────────────────────── │
│ Check: messaging/trust.py::sender_has_trigger_permission()                  │
│                                                                             │
│ Permission sources:                                                         │
│   - Global permissions (db.trigger_permissions["global"])                   │
│   - Channel-specific permissions (db.trigger_permissions["channels"])       │
│                                                                             │
│ Default global: ["Developer", "Admin"]                                      │
│                                                                             │
│ Result: TRIGGER                                                             │
│ Reason: "permitted_sender"                                                  │
│ Metadata: {permission_level, sender, source_type}                           │
└─────────────────────────────────────────────────────────────────────────────┘

Rule 6: Channel Configuration

┌─────────────────────────────────────────────────────────────────────────────┐
│ RULE 6: CHANNEL-SPECIFIC CONFIGURATION                                      │
│ ─────────────────────────────────────────────────────────────────────────── │
│ Condition: source_type == "channel" AND channel in channel_configs          │
│                                                                             │
│ Config options per channel:                                                 │
│   - default_action: "TRIGGER" | "CONTEXT" | "IGNORE"                        │
│   - keywords: List of trigger keywords                                      │
│                                                                             │
│ Result: Based on channel config                                             │
│ Reason: "channel_default"                                                   │
└─────────────────────────────────────────────────────────────────────────────┘

Rule 7: Room Message Default

┌─────────────────────────────────────────────────────────────────────────────┐
│ RULE 7: ROOM MESSAGES                                                       │
│ ─────────────────────────────────────────────────────────────────────────── │
│ Condition: source_type in ["say", "pose"]                                   │
│                                                                             │
│ Result: CONTEXT                                                             │
│ Reason: "room_message_default"                                              │
│                                                                             │
│ Purpose: Be aware of room activity without auto-responding                  │
└─────────────────────────────────────────────────────────────────────────────┘

Rule 8: Default Fallback

┌─────────────────────────────────────────────────────────────────────────────┐
│ RULE 8: DEFAULT                                                             │
│ ─────────────────────────────────────────────────────────────────────────── │
│ No other rules matched                                                      │
│                                                                             │
│ Result: IGNORE                                                              │
│ Reason: "unclassified_unknown"                                              │
└─────────────────────────────────────────────────────────────────────────────┘

4. Classification Results

Classification Meaning Action
TRIGGER Message requires AI response Queue to assistant script via handle_trigger_message()
CONTEXT Informational, store for context Add to context buffer via handle_context_message()
CAPTURE Command output being captured Append to tool buffer (returns True, not tuple)
IGNORE Discard message No action

5. Trust Scoring

TRIGGER and CONTEXT messages include a source_trust score for memory reliability (O-Mem style).

Trust Calculation

messaging/trust.py::calculate_source_trust()

Factors:
  - Source type base score (SOURCE_TRUST_SCORES dict)
  - Sender trust modifier (history-based)
  - Trust override flag (for verified sources)

Source Trust Scores

Source Type Base Score Rationale
whisper 0.95 Direct, private communication
page 0.90 Cross-room private message
say 0.80 In-room speech, witnessed
pose 0.75 In-room action, interpreted
channel 0.70 Public broadcast
emit 0.50 Anonymous message
unknown 0.30 Unverified source

6. Security Notes

┌─────────────────────────────────────────────────────────────────────────────┐
│ SECURITY-CRITICAL                                                           │
│ ─────────────────────────────────────────────────────────────────────────── │
│                                                                             │
│ OOB Metadata Precedence (trustworthiness):                                  │
│   1. **kwargs (highest) - Server-generated via message path                 │
│   2. tuple OOB data - Embedded by Evennia's message system                  │
│   3. Pattern matching (HEURISTIC ONLY) - Fallback for legacy                │
│                                                                             │
│ WARNING: Pattern matching is NOT secure for:                                │
│   - Authentication decisions                                                │
│   - Authorization decisions                                                 │
│   - Security-critical routing                                               │
│                                                                             │
│ User-controlled message content is UNSAFE and susceptible to injection.     │
│ Always use server-side permission checks for security operations.           │
└─────────────────────────────────────────────────────────────────────────────┘

7. Key Files

File Key Functions Purpose
messaging/classification.py classify_message() Main classification logic
messaging/classification.py detect_source_type() Source type detection
messaging/classification.py detect_addressing() @mention detection
messaging/classification.py handle_trigger_message() Queue TRIGGER to script
messaging/classification.py handle_context_message() Buffer CONTEXT messages
messaging/trust.py calculate_source_trust() Trust score calculation
messaging/trust.py sender_has_trigger_permission() Permission checking
messaging/trust.py get_sender_trust_modifier() History-based modifier
messaging/batching.py process_message_batch() Batch processing
messaging/batching.py filter_and_sanitize_batch() Content sanitization
assistant_character.py at_msg_receive() Entry point hook

Document created: 2025-12-06 Updated: 2025-12-09 - Migrated references to messaging/ package