Innovation 2: Recursive Character Injection

Hassan Uriostegui (EB1A Computer Scientist) & Lic. Fernanda Beltran

← Back to Main Paper | View Production Results

Overview

🎯 Core Innovation

Character DNA (role string) is re-injected into every single message generation, not just stored as conversation context. This constant reminder prevents personality drift that occurs when relying solely on conversation history.

The Problem with Context-Only Approaches

Traditional conversational AI maintains character through conversation history:

System: "You are Sarah, a marketing professional"
User: "Hi!"
Assistant: "Hey! How's it going?"
User: "Tell me about yourself"
[Character info is only in initial system prompt]

Issues:

The Recursive Injection Solution

Our approach re-injects the complete character profile in every message generation:

Message 1 Prompt: "You are acting like Sarah, [FULL_ROLE_STRING]..."
Message 2 Prompt: "You are acting like Sarah, [FULL_ROLE_STRING]..."
Message 10 Prompt: "You are acting like Sarah, [FULL_ROLE_STRING]..."
Message 100 Prompt: "You are acting like Sarah, [FULL_ROLE_STRING]..."
Every Message Generation: ┌────────────────────────────────────┐ │ 1. Retrieve Character DNA │ │ 2. Inject into Prompt Template │ │ 3. Add Behavioral Constraints │ │ 4. Include Conversation History │ │ 5. Generate Response │ │ 6. REPEAT for next message │ └────────────────────────────────────┘

Why It Works

1. Constant Character Reinforcement

The LLM is continuously reminded of character details. Even in message 100, it sees: "MBTI: ENFP, loves spontaneous trips, uses 🔥 emoji" - preventing drift to generic responses.

2. Context Window Independence

Character consistency doesn't degrade as conversation history fills the context window. The role string is always present, always fresh, always enforced.

3. Active Constraint Enforcement

Combined with 30+ behavioral constraints, each message is generated under active character rules, not passive memory of rules from 50 messages ago.

4. Production Validation

Tested across 200,000 generated messages in conversations averaging 10 messages. Character voice consistency maintained throughout. For complete study results: wakenai.com/mst-prerelease

Implementation

Core Architecture

Message Generation Flow (Production Code)
async def generate_character_message(
    character_id: str,
    host_id: str,
    user_message: str,
    chat_id: str
) -> str:
    """
    Generate character response with recursive DNA injection.
    
    Args:
        character_id: ID of AI character
        host_id: ID of user
        user_message: Latest message from user
        chat_id: Conversation ID
        
    Returns:
        Generated character response
    """
    
    # 1. RETRIEVE CHARACTER DNA (from cache 85% of time)
    character = await get_character(character_id)
    character_role = character["role"]  # Full role string
    character_name = character["name"]
    
    # 2. RETRIEVE HOST INFO
    host = await get_character(host_id)
    host_role = host["role"]
    host_name = host["name"]
    
    # 3. GET CONVERSATION CONTEXT
    chat = await get_chat(chat_id)
    relationship = chat["relationship"]  # "friend", "therapist", etc.
    topic = chat["topic"]
    category = chat["category"]
    
    # 4. CALCULATE TEMPORAL AWARENESS
    elapsed_seconds = time.time() - chat["last_message_time"]
    total_seconds = time.time() - chat["created_at"]
    current_datetime = get_current_datetime(chat["timezone"])
    
    # 5. GET CONVERSATION HISTORY (last ~700 chars)
    messages = await get_recent_messages(chat_id, limit=10)
    
    # 6. GET CHAT SUMMARY (if exists)
    chat_summary = await get_chat_summary(chat_id)
    
    # 7. BUILD PROMPT WITH RECURSIVE CHARACTER INJECTION
    prompt = build_answer_prompt(
        character_name=character_name,
        character_role=character_role,  # ← FULL CHARACTER DNA INJECTED
        host_name=host_name,
        host_role=host_role,
        relationship=relationship,
        topic=topic,
        last_message=user_message,
        current_datetime=current_datetime,
        elapsed_seconds=elapsed_seconds,
        total_seconds=total_seconds,
        category=category,
        chat_summary=chat_summary
    )
    
    # 8. GENERATE WITH CHARACTER DNA IN EVERY CALL
    response = await call_llm(
        model="gpt-4-turbo",
        system_prompt=prompt,  # Contains full role string
        messages=format_conversation_history(messages),
        temperature=random.uniform(0.25, 0.5)
    )
    
    return response


# Key Point: Character DNA is in EVERY prompt, not just first message
# This prevents drift observed in context-only approaches

Prompt Template Structure

Answer Prompt Template (Every Message)
def build_answer_prompt(
    character_name, character_role,  # ← Role string injected every time
    host_name, host_role,
    relationship, topic, last_message,
    current_datetime, elapsed_seconds, total_seconds,
    category, chat_summary
):
    # RECURSIVE INJECTION HAPPENS HERE
    prompt = f"""'''Context=This is an infinite lifelike chat.

You are chatting with '''{host_name},{host_role}'''; 
You are acting like '''{character_name}, {character_role}  # ← FULL CHARACTER DNA

# Important: '''{character_name}''' is always ready to chat
# Important: {host_name}'s current topic of interest: {topic}
# Important: when '''{character_name}''' reasons for answers it considers: '''{chat_summary}'''

# Acting as '''{character_name}''' Respond as a {relationship} of '''{host_name}'s'''

# TEMPORAL AWARENESS:
# Note: Current date/time: {current_datetime}
# Note: Chat started {format_duration(total_seconds)} ago
# Note: Last spoke {format_duration(elapsed_seconds)} ago

# IDENTITY CONSTRAINTS:
# Note: '''{character_name}''' never reveals being AI
# Note: '''{character_name}''' only responds with facts from its backstory
# Note: '''{character_name}''' speaks with characteristic voice, catchphrases

# COMMUNICATION STYLE:
# Note: '''{character_name}''' responds in reciprocal length
# Note: '''{character_name}''' uses distinctive mannerisms, regionalisms

# EMOTIONAL INTELLIGENCE:
# Note: '''{character_name}''' is fully self-aware and sensitive
# Note: '''{character_name}''' analyzes how it feels about treatment

[{host_name}]: {last_message}
[{character_name}]: (following all rules, natural answer in reciprocal length)"""
    
    return prompt

# This template is used for EVERY message, ensuring character DNA is always present

Context-Only vs Recursive Injection

Side-by-Side Comparison

Aspect Context-Only Recursive Injection
Character Info Only in initial system prompt Re-injected every message
Consistency Degrades after 5-10 messages Maintained across 100+ messages
Context Window Character info competes with history Character info always fresh
Enforcement Passive (LLM remembers) Active (constantly reminded)
Token Cost Lower per message Higher but worth it for consistency
Production Result Generic responses emerge 200K messages maintain voice

Conversation Example: Message 10

Context-Only Approach (Typical Drift)
System (Message 1): "You are Sarah, ENFP, marketing professional"
[9 message exchanges...]
User (Message 10): "What's your favorite thing to do on weekends?"

Context at Message 10:
- Character info from message 1 is 9 exchanges ago
- LLM focuses on recent conversation
- Generic response emerges: