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:

  1. Session-level overrides – Rules stored in the async key-value store sp under the umo (user-message-origin) scope with the key session_service_config.
  2. Conversation-level persona – The persona_id persisted in the current conversation record via conversation_manager.
  3. Provider defaults – The default_personality setting defined in the provider configuration.
  4. 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_config contains persona_id, that value becomes force_applied_persona_id and overrides the conversation setting.
  • Fallback chain: If no session rule exists, the system checks conversation_persona_id, then provider default_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 sp store take effect on the next message processed by PersonaManager.resolve_selected_persona.
  • No restart required: Dynamic transitions operate entirely at runtime through the async store and resolution logic.
  • Clean revert: Use sp.delete_async to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →