logicspike/docs

Contact Intelligence

Phase 3: Domain Model — Contact Intelligence

Last Updated: 2026-04-03 Status: Draft


1. Core Entities & Aggregates

1.1 Contact Domain

1.1.1 Contact

The root entity. Represents a single end-user interacting with a tenant's AI bot.

Field Type Required Description
id UUID Primary key
tenant_id UUID Owning business
external_id String Channel-specific identifier (phone number, widget session ID)
channel Enum whatsapp, telegram, widget
display_name String Name extracted from first interaction or channel profile
avatar_url String Profile picture URL (from channel, if available)
language String Detected language (ISO 639-1, e.g., en, hi)
timezone String Inferred from activity patterns (e.g., Asia/Kolkata)
outreach_disabled Boolean If true, NO proactive messages ever. Set when contact says "stop". Default false.
created_at Timestamp First interaction
updated_at Timestamp Last profile modification

Unique constraint: (tenant_id, external_id, channel) — one contact per identity per channel per tenant.

1.1.2 ContactState

The hot, frequently-updated state of a contact. One row per contact, updated on every interaction.

Field Type Required Description
id UUID Primary key
tenant_id UUID Owning business
contact_id UUID FK → Contact (unique per contact)
mood Enum happy, sad, anxious, excited, neutral, angry, frustrated, flirty, bored, grateful
energy Enum high, medium, low
conversation_style Enum playful, deep, casual, supportive, romantic, venting
relationship_stage Enum new, building, established, deep, fading, dormant
active_streak Integer Consecutive days with at least one message
total_messages Integer Lifetime message count
total_sessions Integer Lifetime session count (session = gap of 30+ min between messages)
avg_session_duration Integer Average session length in minutes
avg_messages_per_session Integer Average messages per session
preferred_hours Text[] Most active hours (e.g., ["22:00", "23:00"])
churn_risk Float 0.0–1.0, computed from engagement signals
last_active_at Timestamp Last message timestamp
last_mood_at Timestamp When mood was last classified
first_contact_at Timestamp Very first interaction ever
updated_at Timestamp Last state update

1.2 Memory Domain

1.2.1 ContactMemory

A stored piece of knowledge about a specific end-user. Same architecture as AI Brain's ai_memory, but scoped to contact_id.

Field Type Required Description
id UUID Primary key
tenant_id UUID Owning business
contact_id UUID FK → Contact — the end-user this memory belongs to
memory_type Enum fact, preference, episode, pattern
content Text The memory itself (natural language)
embedding vector(1536) Semantic embedding (null until deferred processing completes)
importance Float 0.0–1.0, default varies by type
access_count Integer Times retrieved for context
decay_rate Float Importance decay per day. Default 0.005 (slower than Brain's 0.01 — relationships are long-term)
entity_ids Text[] Linked entities (e.g., ["dog:bruno", "workplace:infosys"])
source_type Enum conversation, observation, consolidation
source_id UUID Reference to originating conversation/interaction
accessed_at Timestamp Last retrieval timestamp
created_at Timestamp Record creation

Key difference from AI Brain memory: decay_rate is 0.005 vs 0.01. A contact's dog name should persist for months. Business metrics need faster refresh.

1.2.2 ContactEntity

An entity node in the contact's memory graph.

Field Type Required Description
id String Entity identifier (e.g., dog:bruno, workplace:infosys)
tenant_id UUID Owning business
contact_id UUID FK → Contact
entity_type Enum person, pet, place, workplace, hobby, event, preference, topic
display_name String Human-readable name (e.g., "Bruno")
metadata JSONB Additional structured data (e.g., { "breed": "golden retriever" })
memory_count Integer Number of memories linked to this entity
created_at Timestamp When first recognized

1.3 Outreach Domain

1.3.1 OutreachTrigger

A scheduled proactive message to be sent to a contact.

Field Type Required Description
id UUID Primary key
tenant_id UUID Owning business
contact_id UUID FK → Contact
trigger_type Enum scheduled, inactivity, milestone, recurring
channel Enum whatsapp, telegram, widget
message_template Text Static message text (can contain {name}, {memory_ref})
generate_with_llm Boolean If true, generate a contextual message at send time using contact memory
llm_context Text Additional context for LLM generation (e.g., "Arjun was upset yesterday")
scheduled_at Timestamp For scheduled type — when to send
cron_expression String For recurring type (e.g., 0 8 * * * = daily 8am)
inactivity_days Integer For inactivity type — trigger after N days of silence
milestone_type String For milestone type (e.g., 30_day_anniversary, 100_messages, birthday)
status Enum pending, sent, failed, cancelled, skipped
attempt_count Integer Number of send attempts (max 2 for inactivity)
sent_at Timestamp When the message was actually delivered
sent_message Text The actual message text that was sent (stored after generation/delivery)
created_at Timestamp Record creation

1.3.2 OutreachConfig

Tenant-level outreach configuration. Controls what types of proactive messaging are enabled.

Field Type Required Description
id UUID Primary key
tenant_id UUID Owning business (unique per tenant)
inactivity_enabled Boolean Allow inactivity re-engagement messages. Default true.
inactivity_days Integer Days of silence before triggering. Default 3.
inactivity_max_attempts Integer Max re-engagement messages per inactive period. Default 2.
milestone_enabled Boolean Allow milestone celebration messages. Default true.
recurring_enabled Boolean Allow recurring messages (good morning, etc.). Default false.
recurring_cron String Cron expression for recurring messages
quiet_hours_start String Don't send during these hours (e.g., "23:00")
quiet_hours_end String (e.g., "07:00")
updated_at Timestamp Last modification

1.4 Interaction Domain

1.4.1 InteractionLog

A lightweight log of every message exchange. Not the full conversation (that's in Chat Engine's chat_messages) — just the signals Contact Intelligence extracted.

Field Type Required Description
id UUID Primary key
tenant_id UUID Owning business
contact_id UUID FK → Contact
detected_mood Enum Mood classified from user message
detected_energy Enum Energy level classified
memories_extracted Integer Number of memories created from this interaction
memories_retrieved Integer Number of memories used for context
session_id UUID Groups messages into sessions (30min gap = new session)
created_at Timestamp Interaction timestamp

2. Entity Relationships


3. State Machines in Detail

3.1 RelationshipStage Lifecycle

3.2 OutreachTrigger.status Lifecycle

3.3 Mood Transition Tracking

Mood doesn't have a formal state machine — it's set on every message by the classifier. But transitions are tracked in InteractionLog for pattern detection:

Pattern detection:
  If mood transitions from happy/neutral → sad/anxious for 3+ consecutive messages
  → Create trigger: check-in message next session
  → Update memory: "contact went through a difficult period around {date}"
 
  If mood is consistently "bored" for 5+ messages
  → Signal to AI: change conversation style, introduce new topics
  → Flag churn_risk increase

4. Key Domain Rules & Invariants

  1. One ContactState per Contact. The (tenant_id, contact_id) pair on contact_state has a UNIQUE constraint. State is upserted, never duplicated. If a row doesn't exist, the first interaction creates it with defaults.

  2. Outreach respects "stop" absolutely. When contact.outreach_disabled = true, NO outreach triggers fire. The outreach engine checks this BEFORE generating any message. There is no override, no exception, no "just this once." This is a legal and trust requirement.

  3. Memories belong to contacts, not conversations. A memory's lifecycle is independent of the conversation it was extracted from. Deleting a conversation (in Chat Engine) does NOT delete the memories extracted from it. Memories are only deleted by explicit "forget me" request or importance decay.

  4. Inactivity outreach has hard limits. Max outreach_config.inactivity_max_attempts messages per inactive period (default 2). After the limit, outreach stops until the contact re-engages. No configuration allows unlimited inactivity messages.

  5. Quiet hours are enforced. If outreach_config.quiet_hours_start and quiet_hours_end are set, no outreach is delivered during those hours (in the contact's inferred timezone). Triggers scheduled during quiet hours are delayed to the next valid window.

  6. Contact deletion cascades completely. Deleting a Contact deletes: ContactState, all ContactMemory, all ContactEntity, all OutreachTrigger, all InteractionLog. No orphaned data.

  7. Mood classification never blocks response. If the mood classifier is slow or fails, the Chat Engine receives the previous mood from KV state. The user experience is never degraded by mood classification latency.

  8. Memory importance floor is 0.1. Memories below 0.1 importance are pruned by periodic consolidation. They are effectively "forgotten." This prevents unbounded memory growth while keeping meaningful knowledge.

Contact Intelligence