All data models are defined in data_models.py using Pydantic.
Description: Base class for all timeline events.
class TimelineEvent(BaseModel):
timeline_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
timestamp: datetime = Field(default_factory=datetime.now)
Fields:
timeline_id (str): Unique identifier for the eventtimestamp (datetime): When the event occurredDescription: Represents a character’s spoken dialogue with accompanying action.
class Message(TimelineEvent):
character: str
dialouge: str
action_description: str
Fields:
character (str): Name of the speaking characterdialouge (str): What they sayaction_description (str): Physical action or body languageExample:
message = Message(
character="Captain",
dialouge="Avast! We sail at dawn!",
action_description="slams fist on the table"
)
Description: Represents an environmental or transitional narrative event.
class Scene(TimelineEvent):
scene_type: str
location: str
description: str
Fields:
scene_type (str): Type of scene - “transition” or “environmental”location (str): Where the scene takes placedescription (str): What happens in the sceneExample:
scene = Scene(
scene_type="environmental",
location="Ship Deck",
description="Dark storm clouds gather on the horizon as waves crash against the hull"
)
Description: Represents a character’s physical action or decision point.
class Action(TimelineEvent):
character: str
description: str
Fields:
character (str): Name of the acting characterdescription (str): Details about the actionExample:
action = Action(
character="Marina",
description="carefully unfurls the ancient map on the table"
)
Description: Represents a character joining the conversation.
class CharacterEntry(TimelineEvent):
character: str
description: str
Fields:
character (str): Name of entering characterdescription (str): How they enterExample:
entry = CharacterEntry(
character="Jack",
description="Jack bursts through the door, out of breath"
)
Description: Represents a character leaving the conversation.
class CharacterExit(TimelineEvent):
character: str
description: str
Fields:
character (str): Name of leaving characterdescription (str): How they leaveDescription: Defines a character’s immutable personality and characteristics.
class CharacterPersona(BaseModel):
name: str
traits: List[str]
relationships: Dict[str, str]
speaking_style: str
background: str
goals: Optional[List[str]] = None
knowledge_base: Optional[Dict[str, Any]] = None
temperature: Optional[float] = 0.75
top_p: Optional[float] = 0.9
frequency_penalty: Optional[float] = 0.2
Fields:
name (str): Character’s full nametraits (List[str]): Personality traitsrelationships (Dict[str, str]): Character name → relationship descriptionspeaking_style (str): How the character speaksbackground (str): Character’s history and contextgoals (List[str], optional): Long-term motivationsknowledge_base (Dict, optional): Special knowledge or secretstemperature (float, optional): LLM creativity parameter (default: 0.75)top_p (float, optional): Sampling parameter (default: 0.9)frequency_penalty (float, optional): Repetition control (default: 0.2)Example:
{
"name": "Captain Morgan",
"traits": ["wise", "authoritative", "protective"],
"relationships": {
"Marina": "Impressed by her skills",
"Jack": "Appreciates his spirit"
},
"speaking_style": "Speaks with gravitas, uses seafaring expressions",
"background": "Decades of sailing experience",
"goals": ["Lead crew to fortune", "Keep everyone safe"],
"knowledge_base": {
"secret_routes": "Knows hidden passages through dangerous waters"
}
}
Description: Stores what a character has witnessed from their perspective.
class CharacterMemory(BaseModel):
name: str
event: List[TimelineEvent] = Field(default_factory=list)
Fields:
name (str): Character nameevent (List[TimelineEvent]): Events this character witnessedNote: Each character only remembers events they were present for.
Description: Represents a character’s current, mutable state.
class CharacterState(BaseModel):
name: str
current_objective: Optional[str] = None
Fields:
name (str): Character namecurrent_objective (str, optional): Current goal or taskDescription: Complete representation of an AI character.
class Character(BaseModel):
persona: CharacterPersona
memory: Optional[CharacterMemory] = None
state: Optional[CharacterState] = None
Fields:
persona (CharacterPersona): Personality and traitsmemory (CharacterMemory, optional): What they’ve witnessedstate (CharacterState, optional): Current condition and objectivesDescription: Master timeline containing all chronological events.
class TimelineHistory(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
title: Optional[str] = None
events: List[TimelineEvent] = Field(default_factory=list)
participants: List[str] = Field(default_factory=list)
current_participants: List[str] = Field(default_factory=list)
timeline_summary: Optional[str] = None
visible_to_user: bool = True
Fields:
id (str): Unique timeline identifiertitle (str, optional): Timeline titleevents (List[TimelineEvent]): All events in chronological orderparticipants (List[str]): All characters who participatedcurrent_participants (List[str]): Characters currently presenttimeline_summary (str, optional): Auto-generated summaryvisible_to_user (bool): Whether user can view (default: True)Description: Story structure with sequential objectives.
class Story(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
title: str
description: str
objectives: List[str]
current_objective_index: int = 0
Fields:
id (str): Unique story identifiertitle (str): Story titledescription (str): Overall story descriptionobjectives (List[str]): Sequential list of story objectivescurrent_objective_index (int): Current progress (default: 0)Example:
{
"title": "The Quest for the Phantom Pearl",
"description": "A treasure hunt through cursed waters",
"objectives": [
"Decipher the mysterious map",
"Navigate through the Siren's Strait",
"Find the abandoned island"
],
"current_objective_index": 0
}
Location: managers/characterManager.py
Description: Manages character lifecycle, memory, and decision-making.
__init__()def __init__(self) -> None
Initialize CharacterManager with default model configuration.
create_character()def create_character(
persona: CharacterPersona,
memory: Optional[CharacterMemory] = None,
state: Optional[CharacterState] = None
) -> Character
Create a new AI character instance.
Parameters:
persona (CharacterPersona): Character personality definitionmemory (CharacterMemory, optional): Initial memorystate (CharacterState, optional): Initial stateReturns: Character instance
Example:
persona = CharacterPersona(name="Jack", traits=["brave", "reckless"], ...)
character = character_manager.create_character(persona)
update_character_memory()def update_character_memory(
character: Character,
event: TimelineEvent
) -> None
Add a timeline event to character’s memory.
Parameters:
character (Character): Character to updateevent (TimelineEvent): Event to add to memoryExample:
message = Message(character="Captain", dialouge="Set sail!", ...)
character_manager.update_character_memory(jack, message)
update_character_state()def update_character_state(
character: Character,
current_objective: Optional[str] = None
) -> None
Update character’s current state.
Parameters:
character (Character): Character to updatecurrent_objective (str, optional): New objectivebuild_persona_context()def build_persona_context(character: Character) -> str
Build character personality context for LLM prompts.
Parameters:
character (Character): Character to build context forReturns: Formatted string with persona information
build_state_context()def build_state_context(character: Character) -> str
Build character state context including current objective.
Returns: Formatted string with state information
build_memory_context()def build_memory_context(
character: Character,
last_n_messages: Optional[int] = None
) -> str
Build memory context from character’s perspective.
Parameters:
character (Character): Character whose memory to buildlast_n_messages (int, optional): Number of recent events (None = all)Returns: Formatted string with memory from character’s POV
decide_turn_response()def decide_turn_response(
character: Character
) -> Tuple[str, float, str, Optional[str], Optional[str]]
Determine if character should speak, act, or stay silent.
Parameters:
character (Character): Character making decisionReturns: Tuple of:
response_type (str): “speak”, “act”, or “silent”priority (float): 0.0-1.0 importance scorereasoning (str): Why they chose thisdialogue (str, optional): What they say (if speaking)action (str, optional): Physical actiongenerate_character_response()def generate_character_response(
character: Character,
response_type: str
) -> Tuple[str, str]
Generate actual character response (dialogue and/or action).
Parameters:
character (Character): Character respondingresponse_type (str): “speak” or “act”Returns: Tuple of (dialogue, action)
Location: managers/timelineManager.py
Description: Manages timeline events and context building.
__init__()def __init__(self) -> None
Initialize TimelineManager.
create_timeline_history()def create_timeline_history(
title: Optional[str] = None,
participants: Optional[List[str]] = None,
visible_to_user: bool = True
) -> TimelineHistory
Create a new timeline.
Parameters:
title (str, optional): Timeline titleparticipants (List[str], optional): Initial participantsvisible_to_user (bool): Visibility flag (default: True)Returns: TimelineHistory instance
add_event()def add_event(
timeline: TimelineHistory,
event: TimelineEvent
) -> None
Add event to timeline and update participants.
Parameters:
timeline (TimelineHistory): Timeline to add toevent (TimelineEvent): Event to addcreate_message()def create_message(
character: str,
dialouge: str,
action_description: str
) -> Message
Factory method for creating messages.
Parameters:
character (str): Speaker namedialouge (str): What they sayaction_description (str): Body language/actionReturns: Message instance
create_scene()def create_scene(
scene_type: str,
location: str,
description: str
) -> Scene
Factory method for creating scenes.
Parameters:
scene_type (str): “transition” or “environmental”location (str): Where it happensdescription (str): What happensReturns: Scene instance
get_recent_events()def get_recent_events(
timeline: TimelineHistory,
n: Optional[int] = 10,
event_type: Optional[str] = None
) -> List[TimelineEvent]
Get recent events from timeline.
Parameters:
timeline (TimelineHistory): Timeline to queryn (int, optional): Number of events (None = all)event_type (str, optional): Filter by “message”, “scene”, “action”, “entry”, “exit”Returns: List of events
get_current_location()def get_current_location(timeline: TimelineHistory) -> Optional[str]
Get current location from most recent scene.
Returns: Location string or None
get_timeline_context()def get_timeline_context(
timeline: TimelineHistory,
recent_event_count: int = 10
) -> str
Build formatted timeline string.
Parameters:
timeline (TimelineHistory): Timeline to formatrecent_event_count (int): Number of events to includeReturns: Formatted timeline string
Location: managers/turn_manager.py
Description: Coordinates conversation flow and turn selection.
__init__()def __init__(
characters: List[Character],
timeline: TimelineHistory,
max_consecutive_ai_turns: int = None,
priority_randomness: float = None,
save_callback: Optional[callable] = None
)
Initialize turn manager.
Parameters:
characters (List[Character]): AI characters in conversationtimeline (TimelineHistory): Main timelinemax_consecutive_ai_turns (int, optional): Max AI turns (default: Config.MAX_CONSECUTIVE_AI_TURNS)priority_randomness (float, optional): Random factor (default: Config.PRIORITY_RANDOMNESS)save_callback (callable, optional): Function to save conversationprocess_ai_responses()def process_ai_responses(
max_turns: Optional[int] = None,
force_one_response: bool = False
) -> List[Message]
Main conversation loop - process consecutive AI responses.
Parameters:
max_turns (int, optional): Override max turnsforce_one_response (bool): Force at least one responseReturns: List of generated messages
Flow:
Location: managers/storyManager.py
Description: Manages story progression and objectives.
__init__()def __init__(story: Optional[Story] = None)
Initialize story manager.
Parameters:
story (Story, optional): Story to manageget_current_objective()def get_current_objective() -> Optional[str]
Get current story objective.
Returns: Current objective string or None
is_story_complete()def is_story_complete() -> bool
Check if all objectives are complete.
Returns: True if story is complete
get_progress_percentage()def get_progress_percentage() -> float
Get completion percentage.
Returns: 0.0-100.0 progress percentage
get_story_context()def get_story_context() -> str
Get formatted story context for LLM prompts.
Returns: Story context string
evaluate_and_assign_objectives()def evaluate_and_assign_objectives(
active_characters: List[Character],
timeline: TimelineHistory
) -> Dict[str, Any]
Evaluate completion and assign objectives.
Parameters:
active_characters (List[Character]): Currently active characterstimeline (TimelineHistory): Current timelineReturns: Dictionary with:
{
"character_updates": {
"CharacterName": {
"objective": "...",
"status": "assigned|completed|continuing",
"reasoning": "..."
}
},
"story_objective_complete": bool,
"reasoning": "..."
}
Location: loaders/character_loader.py
Description: Load character personas from JSON files.
__init__()def __init__(base_dir: str)
Initialize loader.
Parameters:
base_dir (str): Story directory path (e.g., “Pirate Adventure”)Automatically looks in [base_dir]/characters/ subdirectory.
load_character()def load_character(character_name: str) -> CharacterPersona
Load single character.
Parameters:
character_name (str): Character name without .json extensionReturns: CharacterPersona instance
Raises:
FileNotFoundError: Character file doesn’t existValueError: Invalid JSON or missing fieldsload_multiple_characters()def load_multiple_characters(character_names: List[str]) -> List[CharacterPersona]
Load multiple characters.
Parameters:
character_names (List[str]): List of character namesReturns: List of CharacterPersona instances
list_available_characters()def list_available_characters() -> List[str]
List all available character files.
Returns: List of character names
Location: loaders/story_loader.py
Description: Load story configuration from JSON.
__init__()def __init__(base_dir: str)
Initialize loader.
Parameters:
base_dir (str): Story directory pathAutomatically looks in [base_dir]/story/ subdirectory.
load_story()def load_story() -> Story
Load story from JSON file.
Returns: Story instance
Raises:
FileNotFoundError: No story file foundValueError: Multiple story files or invalid JSONNote: Only one story file allowed per directory.
Location: roleplay_system.py
Description: Main coordinator for the roleplay system.
__init__()def __init__(
player_name: str,
characters: List[CharacterPersona],
model_name: str = None,
chat_storage_dir: str = None,
story_manager = None,
story_name: str = "default",
initial_location: str = "Common Room",
initial_scene_description: str = None
)
Initialize roleplay system.
Parameters:
player_name (str): Human player namecharacters (List[CharacterPersona]): AI character personasmodel_name (str, optional): LLM model (default: Config.DEFAULT_MODEL)chat_storage_dir (str, optional): Storage directory (default: Config.CHAT_STORAGE_DIR)story_manager (StoryManager, optional): Story manager instancestory_name (str): Story name for file naminginitial_location (str): Starting locationinitial_scene_description (str, optional): Initial scene textRaises:
ValueError: OPENROUTER_API_KEY not setExample:
system = RoleplaySystem(
player_name="Henry",
characters=[captain_persona, jack_persona, marina_persona],
story_manager=story_manager,
story_name="Pirate Adventure",
initial_location="Ship Deck",
initial_scene_description="The adventure begins..."
)
get_conversation_file_path()def get_conversation_file_path() -> Path
Get path to conversation save file.
Returns: Path object
Location: config.py
class Config:
# API Settings
OPENROUTER_API_KEY: Optional[str]
OPENROUTER_BASE_URL: str = "https://openrouter.ai/api/v1"
# Model Settings
DEFAULT_MODEL: str = "x-ai/grok-4.1-fast"
MODEL_TEMPERATURE: float = 0.7
MAX_TOKENS: int = 1024
RESPONSE_TIMEOUT: int = 20
# Conversation Settings
DEFAULT_CONTEXT_WINDOW: int = 100
MAX_CONSECUTIVE_AI_TURNS: int = 3
PRIORITY_RANDOMNESS: float = 0.1
# Storage Settings
CHAT_STORAGE_DIR: str = "Chat_Logs"
Create .env file:
OPENROUTER_API_KEY=your_api_key_here
Location: helpers/response_parser.py
parse_json_response()def parse_json_response(response_text: str) -> Dict[str, Any]
Parse JSON from LLM response, handling markdown code blocks.
Parameters:
response_text (str): Raw LLM responseReturns: Parsed JSON dictionary
Raises:
json.JSONDecodeError: Invalid JSONExample:
response = model.generate_content(prompt)
data = parse_json_response(response.text)
Location: openrouter_client.py
__init__()def __init__(model_name: str, api_key: Optional[str] = None)
Initialize OpenRouter API client.
Parameters:
model_name (str): Model to useapi_key (str, optional): API key (default: Config.OPENROUTER_API_KEY)generate_content()def generate_content(prompt: str, **kwargs)
Generate content from prompt.
Parameters:
prompt (str): Text prompt**kwargs: Additional parameters
temperature (float): Creativity (default: 0.7)max_tokens (int): Max response length (default: 1024)top_p (float): Sampling parameter (default: 1.0)frequency_penalty (float): Repetition control (default: 0.0)Returns: Response object with .text attribute
Raises:
Exception: API errors (rate limit, invalid key, etc.)FileNotFoundError:
ValueError:
Exception (API Errors):
ResourceExhausted: 429 - Rate limit exceededInvalidAPIKey: 401 - Invalid API keyif not character_name:
raise ValueError("character_name cannot be empty")
raise FileNotFoundError(
f"Character file not found: {filepath}\n"
f"Available characters: {self.list_available_characters()}"
)
try:
response = model.generate_content(prompt)
except Exception as e:
if "429" in str(e):
print("Rate limit exceeded. Please wait and try again.")
else:
raise
Last Updated: December 2025
Author: Jit Roy
License: MIT