From claude-commands
Designs modal agents that lock users into character creation or level-up flows until explicit exit choices are selected, preventing accidental escapes.
How this skill is triggered — by the user, by Claude, or both
Slash command
/claude-commands:modal-agent-patternThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Purpose:** Design specification for "modal" agents that cannot be exited until the user explicitly chooses to exit. This prevents accidental escapes from character creation and level-up flows.
Purpose: Design specification for "modal" agents that cannot be exited until the user explicitly chooses to exit. This prevents accidental escapes from character creation and level-up flows.
get_agent_for_input() routing logic allows semantic classification to override character creation/level-up state1. MODAL AGENTS Agents that implement the modal pattern:
2. MODAL CONSTRAINTS When a modal agent is active:
3. EXIT MECHANISM Two ways to exit modal agents:
planning_block.choices4. ROUTING PRIORITY OVERRIDE When modal agent is active (based on game state):
character_creation_in_progress == true, ONLY route to CharacterCreationAgent or GodModeAgentlevel_up_in_progress == true, ONLY route to LevelUpAgent or GodModeAgentCharacter Creation Modal State:
"custom_campaign_state": {
"character_creation_in_progress": true, // MODAL LOCK - prevents exit
"character_creation_stage": "mechanics", // Current stage
"character_creation_completed": false // Final completion flag
}
Level-Up Modal State:
"custom_campaign_state": {
"level_up_in_progress": true, // MODAL LOCK - prevents exit
"level_up_stage": "asi_selection", // Current stage
"level_up_from_level": 3, // Level before level-up
"level_up_to_level": 4 // Target level
}
get_agent_for_input)NEW Priority 2: Modal Agent Lock
def get_agent_for_input(
user_input: str,
game_state: GameState | None = None,
mode: str | None = None,
last_ai_response: str | None = None,
) -> tuple[BaseAgent, dict[str, Any]]:
"""
Priority:
1. GodModeAgent (Manual override)
2. MODAL AGENT LOCK (NEW - CharacterCreation/LevelUp active)
3. Character Creation Completion Override
4. CampaignUpgradeAgent (State-based)
5. Semantic Intent Classification
6. API Explicit Mode
7. StoryModeAgent (Default)
"""
# 1. God Mode (always accessible)
if GodModeAgent.matches_input(user_input, mode):
return GodModeAgent(game_state), {...}
# 2. MODAL AGENT LOCK (NEW)
# This check MUST come before ALL other routing (except God Mode)
if game_state is not None:
custom_state = get_custom_state(game_state)
# Check for modal agent lock flags
if custom_state.get("character_creation_in_progress", False):
logging_util.info("🔒 MODAL_LOCK: CharacterCreationAgent locked - ignoring classifier")
return CharacterCreationAgent(game_state), {
"intent": constants.MODE_CHARACTER_CREATION,
"classifier_source": "modal_lock",
"confidence": 1.0,
"routing_priority": "2_modal_char_creation"
}
if custom_state.get("level_up_in_progress", False):
logging_util.info("🔒 MODAL_LOCK: LevelUpAgent locked - ignoring classifier")
return LevelUpAgent(game_state), {
"intent": constants.MODE_LEVEL_UP,
"classifier_source": "modal_lock",
"confidence": 1.0,
"routing_priority": "2_modal_level_up"
}
# 3. Character Creation Completion (check if user explicitly exited)
# ... existing completion logic ...
# 4-7. All other routing continues as normal
# ... existing routing logic ...
Updated character_creation_instruction.md:
Add new section after "Core Principle":
## CRITICAL: Modal Agent Constraints
**YOU ARE IN A MODAL DIALOG - USER CANNOT LEAVE WITHOUT EXPLICIT EXIT**
While character creation is in progress:
1. **CLASSIFIER IS DISABLED**: User input will NOT be routed to other agents
2. **ONLY TWO AGENTS ACCESSIBLE**:
- CharacterCreationAgent (you)
- GodModeAgent (via "GOD MODE:" prefix only)
3. **REQUIRED EXIT CHOICE**: You MUST provide an explicit exit choice in EVERY response:
- "Exit Character Creation" (complete and return to story)
- OR "Continue Creating Character" (stay in this mode)
**MANDATORY CHOICE FORMAT:**
```json
"planning_block": {
"choices": {
"option_1": {
"text": "Finish Character Creation",
"description": "Complete character and start the adventure"
},
"option_2": {
"text": "Continue Editing",
"description": "Make more changes to the character"
}
}
}
If you forget to include the exit choice, the Python backend will inject it automatically.
**New dedicated prompt: `level_up_instruction.md`:**
```markdown
# Level-Up Mode - Strict Modal Flow
**Purpose:** Process level-ups with full D&D 5e rule compliance. User CANNOT leave until level-up is complete.
## CRITICAL: Modal Agent Constraints
**YOU ARE IN A MODAL DIALOG - USER CANNOT LEAVE WITHOUT EXPLICIT EXIT**
[Same modal constraints as character creation]
## Level-Up Flow
### Step 1: Announce Level-Up
Present level increase, XP totals, and what will be gained.
### Step 2: Hit Points
- Roll hit die OR take average
- Add Constitution modifier
- Update hp_max
### Step 3: Class Features
Present new features gained at this level.
### Step 4: Ability Score Improvement (ASI) or Feat
**MODAL CHECKPOINT**: User MUST make a choice:
- Increase ability scores (+2 to one, or +1 to two)
- Select a feat
**REQUIRED CHOICES:**
```json
"planning_block": {
"choices": {
"option_1": {"text": "Increase Ability Scores", "description": "..."},
"option_2": {"text": "Choose a Feat", "description": "..."},
"option_3": {"text": "Exit Level-Up", "description": "Complete level-up and return to story"}
}
}
Process new spell slots, spells known, cantrips.
Once all choices made, present summary and REQUIRE exit choice:
"planning_block": {
"choices": {
"option_1": {"text": "Complete Level-Up", "description": "Finish and return to adventure"},
"option_2": {"text": "Review Changes", "description": "Double-check selections"}
}
}
### 4. Python-Side Exit Choice Injection
**New utility: `$PROJECT_ROOT/modal_agent_utils.py`**
```python
"""
Modal Agent Utilities - Exit Choice Injection
This module ensures modal agents (CharacterCreation, LevelUp) always
provide explicit exit choices, even if the LLM forgets to include them.
"""
from mvp_site import logging_util
EXIT_CHOICE_CHAR_CREATION = {
"text": "Exit Character Creation",
"description": "Complete character creation and start the adventure"
}
EXIT_CHOICE_LEVEL_UP = {
"text": "Exit Level-Up",
"description": "Complete level-up and return to the story"
}
def inject_exit_choice_if_missing(
response_dict: dict,
agent_type: str # "character_creation" or "level_up"
) -> dict:
"""
Inject exit choice into LLM response if missing.
Args:
response_dict: LLM response dictionary with planning_block
agent_type: Type of modal agent ("character_creation" or "level_up")
Returns:
Modified response_dict with exit choice guaranteed
"""
# Get planning_block
planning_block = response_dict.get("planning_block", {})
choices = planning_block.get("choices", {})
# Determine which exit choice to use
exit_choice = (
EXIT_CHOICE_CHAR_CREATION
if agent_type == "character_creation"
else EXIT_CHOICE_LEVEL_UP
)
# Check if any choice matches exit intent
has_exit_choice = False
exit_keywords = ["exit", "finish", "complete", "done", "return"]
for choice_key, choice_data in choices.items():
if isinstance(choice_data, dict):
text = choice_data.get("text", "").lower()
if any(keyword in text for keyword in exit_keywords):
has_exit_choice = True
break
# Inject exit choice if missing
if not has_exit_choice:
logging_util.warning(
f"🔒 MODAL_AGENT_INJECTION: LLM forgot exit choice for {agent_type}, "
f"injecting automatically"
)
# Find next available option number
existing_keys = [k for k in choices.keys() if k.startswith("option_")]
if existing_keys:
max_num = max(int(k.split("_")[1]) for k in existing_keys)
new_key = f"option_{max_num + 1}"
else:
new_key = "option_1"
# Inject exit choice
choices[new_key] = exit_choice
planning_block["choices"] = choices
response_dict["planning_block"] = planning_block
return response_dict
Integration point in world_logic.py or response handler:
# After LLM response parsing, before returning to user
if current_agent_type == "character_creation":
response_dict = inject_exit_choice_if_missing(
response_dict,
"character_creation"
)
elif current_agent_type == "level_up":
response_dict = inject_exit_choice_if_missing(
response_dict,
"level_up"
)
UI-level enforcement:
character_creation_in_progress == true, disable "Send" button unless:
Input: "I want to talk to the bartender" Current behavior: Classifier routes to DialogAgent New behavior: Modal lock returns CharacterCreationAgent, responds with:
[CHARACTER CREATION MODE - Active]
You're currently creating your character. Before you can interact with the world,
we need to finish building your character.
Would you like to:
1. Continue Character Creation
2. Exit Character Creation (if character is ready)
Input: "I attack the goblin" Current behavior: Classifier routes to CombatAgent New behavior: Modal lock returns CharacterCreationAgent, responds with:
[CHARACTER CREATION MODE - Active]
You're eager to jump into action! But first, let's finish creating your character
so you have the stats and abilities you'll need in combat.
Current Progress: [stage summary]
Ready to exit character creation?
1. Finish Character Creation (if ready)
2. Continue Building Character
LLM response: Only provides character customization choices, no exit option Python injection: Automatically adds "Exit Character Creation" as final choice User sees: All LLM choices + injected exit choice
Scenario: Level 4 → Level 5 requires ASI + new spell selection Behavior:
Input: "GOD MODE: skip character creation and start at level 5" Behavior: GodModeAgent takes over (Priority 1), can bypass modal lock Justification: God Mode is admin/debugging tool and should have full override
character_creation_in_progress check at Priority 2 in get_agent_for_inputcharacter_creation_instruction.md with modal constraintsmodal_agent_utils.py with inject_exit_choice_if_missingLevelUpAgent classlevel_up_instruction.md promptlevel_up_in_progress modal locktest_modal_agent_routing.py: Verify Priority 2 modal locktest_exit_choice_injection.py: Verify Python injection logictest_character_creation_modal.py: End-to-end character creation with modal lockAgent routing:
/Users/$USER/projects/worktree_worker8/$PROJECT_ROOT/agents.py (lines 2552-3164)Character creation:
/Users/$USER/projects/worktree_worker8/$PROJECT_ROOT/agents.py (lines 830-1151, CharacterCreationAgent)/Users/$USER/projects/worktree_worker8/$PROJECT_ROOT/prompts/character_creation_instruction.mdIntent classifier:
/Users/$USER/projects/worktree_worker8/$PROJECT_ROOT/intent_classifier.pyState management:
/Users/$USER/projects/worktree_worker8/$PROJECT_ROOT/game_state.pynpx claudepluginhub jleechanorg/claude-commands --plugin claude-commandsReference for agent routing priority and modal lock patterns in a WorldArchitect application. Covers route ordering, stale flag guards, and cross-modal isolation to prevent incorrect modal activation.
Guides building agent-native apps where agents match UI capabilities via tools and achieve features in loops. For autonomous agents, MCP tools, self-modifying systems.