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:
- Character details fade as conversation lengthens
- LLM focuses on recent messages, not initial character
- Personality drift after 5-10 messages
- Generic responses as context window fills
- No active enforcement of character constraints
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]..."
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
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
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
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: