How to Create Dynamic Persona Transitions in AstrBot
You can create dynamic persona transitions in AstrBot by writing session-level rules to the async key-value store sp, which overrides conversation-level and provider-default personas at runtime without restarting the bot.
AstrBot's persona architecture separates static configuration from runtime selection logic, enabling mid-conversation personality switches based on triggers, content analysis, or scheduling. This guide explains how to implement dynamic persona transitions in the AstrBotDevs/AstrBot repository using session overrides, conversation management, and the resolution hierarchy defined in the core persona manager.
Understanding AstrBot's Persona Resolution Hierarchy
AstrBot determines the active persona for each message through a four-tier resolution chain implemented in PersonaManager.resolve_selected_persona (astrbot/core/persona_mgr.py, lines 64-112). The system evaluates these sources in strict priority order:
- Session-level overrides – Rules stored in the async key-value store
spunder theumo(user-message-origin) scope with the keysession_service_config. - Conversation-level persona – The
persona_idpersisted in the current conversation record viaconversation_manager. - Provider defaults – The
default_personalitysetting defined in the provider configuration. - WebChat special fallback – The internal
_chatui_default_persona injected automatically when the platform is "webchat" and no explicit match exists.
The resolve_selected_persona method returns a tuple containing the final persona_id, the Personality object (or None), the force_applied_persona_id from session rules, and a boolean use_webchat_special_default indicating whether the webchat fallback triggered.
Implementing Session-Level Persona Overrides
Dynamic transitions rely on step one of the resolution hierarchy: session-level rules that temporarily override all other settings.
How resolve_selected_persona Processes Overrides
When processing a message, the method executes this flow (lines 81-109 in persona_mgr.py):
- Load session config:
session_service_config = await sp.get_async(scope="umo", scope_id=str(umo), key="session_service_config", default={}) - Apply forced persona: If
session_service_configcontainspersona_id, that value becomesforce_applied_persona_idand overrides the conversation setting. - Fallback chain: If no session rule exists, the system checks
conversation_persona_id, then providerdefault_personality. - WebChat handling: If still unresolved and the platform is "webchat", the method sets
persona_id = "_chatui_default_".
Because session rules are stored in the async sp store rather than the conversation database, you can modify them instantly without persisting changes to the conversation record.
Writing Dynamic Rules with sp.set_async
To trigger a transition programmatically, write a rule to the session scope:
from astrbot.api import sp
async def set_temporary_persona(umo: str, persona_name: str):
await sp.set_async(
scope="umo",
scope_id=umo,
key="session_service_config",
value={"persona_id": persona_name},
)
The next message processed for that UMO will resolve to the specified persona. To revert, delete the key or overwrite it with a new value.
Practical Trigger Mechanisms for Persona Switching
You can attach dynamic transitions to various triggers depending on your use case.
Command-Based Switching
The built-in /persona command (astrbot/builtin_stars/builtin_commands/commands/persona.py, lines 95-100) updates the conversation-level persona:
await self.context.conversation_manager.update_conversation_persona_id(
message.unified_msg_origin,
"detective",
)
This persists across the conversation but affects only the conversation level, not the session override layer.
Event-Driven Automatic Switching
Create a plugin that listens to AstrMessageEvent and sets session rules based on message content:
from astrbot.api import sp
async def on_message(event):
msg = event.message_str.lower()
if "tell me a joke" in msg:
await sp.set_async(
scope="umo",
scope_id=str(event.unified_msg_origin),
key="session_service_config",
value={"persona_id": "jokester"},
)
await event.reply("Switching to the jokester persona…")
This switches personas immediately when specific keywords are detected, overriding the conversation default for subsequent turns.
Time-Based and Scheduled Transitions
For time-sensitive personas (e.g., a "night" persona after 22:00), use a background task or cron job to write rules to sp for active sessions. Call sp.set_async with the appropriate persona_id for target UMOs based on your scheduling logic.
Code Examples for Dynamic Transitions
Creating a Temporary Session Rule
Use this pattern to switch personas for a single session without modifying the conversation record:
from astrbot.api import sp
async def activate_night_mode(umo: str):
await sp.set_async(
scope="umo",
scope_id=umo,
key="session_service_config",
value={"persona_id": "night_assistant"},
)
Clearing Rules to Revert Defaults
To remove the session override and fall back to the conversation-level or provider-default persona:
await sp.delete_async(
scope="umo",
scope_id=umo,
key="session_service_config",
)
Creating Personas for Dynamic Use
Before you can transition to a persona, it must exist in the database. Create it programmatically:
await context.persona_manager.create_persona(
persona_id="jokester",
system_prompt="You love telling jokes and witty remarks.",
tools=None,
skills=None,
)
Source: PersonaManager.create_persona in astrbot/core/persona_mgr.py, lines 304-340.
WebChat Automatic Fallback
No code is required for WebChat dynamic defaults. When the platform identifier is "webchat" and resolve_selected_persona finds no matching persona, it automatically injects _chatui_default_ (lines 106-109 in persona_mgr.py).
Key Source Files and Architecture
| File | Role |
|---|---|
astrbot/core/persona_mgr.py |
Contains PersonaManager.resolve_selected_persona (lines 64-112), the central resolution logic for all persona transitions, plus create_persona for personality definitions. |
astrbot/builtin_stars/builtin_commands/commands/persona.py |
Implements the /persona slash command for conversation-level switching and persona listing. |
astrbot/api/sp.py |
Async key-value store interface for session-level overrides (session_service_config). |
astrbot/core/conversation_manager.py |
Persists conversation-level persona_id settings that serve as the fallback when no session rule exists. |
astrbot/core/platform/message_session.py |
Defines the UMO (user-message-origin) identifier used as the scope for session rules. |
astrbot/dashboard/routes/persona.py |
Web dashboard API for persona CRUD operations. |
Summary
- Session rules override everything: Write to
sp.set_async(scope="umo", key="session_service_config")to dynamically switch personas without touching conversation records. - Four-tier fallback: Resolution proceeds from session overrides → conversation persona → provider default → WebChat special default.
- Instant transitions: Changes to the
spstore take effect on the next message processed byPersonaManager.resolve_selected_persona. - No restart required: Dynamic transitions operate entirely at runtime through the async store and resolution logic.
- Clean revert: Use
sp.delete_asyncto remove session rules and automatically fall back to conversation or provider defaults.
Frequently Asked Questions
How do I temporarily switch personas for just one reply?
Write a session rule using sp.set_async before processing the message, then delete it immediately after the response using sp.delete_async. This creates a single-message persona switch that reverts automatically for subsequent turns.
What's the difference between conversation-level and session-level personas?
Conversation-level personas are persisted in the conversation record via conversation_manager.update_conversation_persona_id and survive until explicitly changed. Session-level personas are stored in the ephemeral sp cache under session_service_config and override the conversation setting only for the current session, disappearing when deleted or when the session expires.
Can I use dynamic personas with the WebChat interface?
Yes. The WebChat platform triggers a special fallback to _chatui_default_ when no explicit persona matches. You can also force specific personas for WebChat sessions by writing UMO-scoped rules to sp using the WebChat session identifier, just as you would for any other platform.
How do I check which persona is currently active in code?
Call await context.persona_manager.resolve_selected_persona(...) with the current conversation and provider settings. Inspect the returned tuple: the first element is the final persona_id, the third element indicates if a session rule forced the selection (force_applied_persona_id), and the fourth boolean shows if the WebChat default triggered.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →