1 Architecture Commands and API
blightbow edited this page 2025-12-07 00:46:47 +00:00

Architecture: Commands and API

Layers 3 & 4 - Mixin Patterns for Commands and REST API


Overview

Both commands (Layer 3) and API (Layer 4) use domain-specific mixins:

  • commands/setup/ - 7 mixins for aisetup command
  • api/views/mixins/ - 11 mixins for AssistantViewSet

1. Command Layer (Layer 3)

Command Package Structure

commands/
├── __init__.py          # Re-exports all commands
├── base.py              # AICommand base class
├── cmdsets.py           # AICmdSet bundle
├── audit.py             # aiaudit command
├── execution.py         # aiexec command
├── goals.py             # aigoal command
├── history.py           # aihistory command
├── interaction.py       # aiinteract command
├── memory.py            # aimemory command
├── utils.py             # Shared utilities
└── setup/               # aisetup package (mixin-based)
    ├── __init__.py      # Re-exports CmdAssistant
    ├── base.py          # CmdAssistant main class
    ├── helpers.py       # Parsing utilities
    ├── lifecycle.py     # /init, /clone, /start, /stop, /delete
    ├── config.py        # /config management
    ├── rag.py           # /rag configuration
    ├── messaging.py     # /messaging settings
    ├── template.py      # /template management
    ├── context.py       # /context configuration
    └── component.py     # /component management

aisetup Command Mixins

class CmdAssistant(
    LifecycleMixin,    # /init, /clone, /start, /stop, /delete
    ConfigMixin,       # /config management
    RAGMixin,          # /rag configuration
    MessagingMixin,    # /messaging settings
    TemplateMixin,     # /template management
    ContextMixin,      # /context configuration
    ComponentMixin,    # /component management
    AICommand,
):
    key = "aisetup"
    aliases = ["ai-admin", "aiassistant"]

    def func(self):
        """Central dispatch to mixin handlers."""
        if "init" in self.switches:
            self.setup_assistant(key)  # From LifecycleMixin
        elif "config" in self.switches:
            self.handle_config_command(key, args)  # From ConfigMixin
        # ...

Mixin Responsibilities

Mixin Switches Purpose
LifecycleMixin /init, /clone, /start, /stop, /reset, /enable, /disable, /delete Assistant creation and lifecycle
ConfigMixin /config LLM settings, tick rate, timeouts
RAGMixin /rag Qdrant/semantic search configuration
MessagingMixin /messaging Message classification, channels, permissions
TemplateMixin /template Prompt template CRUD
ContextMixin /context Context type configuration
ComponentMixin /component Pluggable component management

Command Dispatch Pattern

# In base.py
def func(self):
    assistant_key = self._parse_assistant_key()

    if not self.switches:
        self._handle_no_switch(assistant_key)
    elif "list" in self.switches:
        self.list_assistants()
    elif "init" in self.switches:
        self._handle_init(assistant_key)
    elif "config" in self.switches:
        self._handle_config(assistant_key)
    # ... dispatch to mixin methods

Other Commands

Command Purpose Key Switches
aiexec Execution control /pattern, /state, /delegation
aihistory Conversation management /show, /clear, /export
aigoal Goal management /add, /update, /complete
aiinteract Direct interaction /event, /trigger
aimemory Memory operations /search, /list, /consolidate
aiaudit Audit and diagnostics /events, /tools, /memory

2. API Layer (Layer 4)

API Package Structure

api/
├── __init__.py
├── urls.py              # URL routing
├── permissions.py       # StaffOnlyPermission
├── serializers.py       # DRF serializers
└── views/
    ├── __init__.py
    ├── assistants.py    # AssistantViewSet (main)
    ├── base.py          # AssistantActionMixin
    └── mixins/
        ├── __init__.py
        ├── monitoring.py
        ├── workbench.py
        ├── events.py
        ├── memory.py
        ├── providers.py
        ├── configuration.py
        ├── goals.py
        ├── sleep_modes.py
        ├── execution_patterns.py
        ├── delegation.py
        └── tools.py

AssistantViewSet Mixins

class AssistantViewSet(
    AssistantActionMixin,       # Base helper methods
    MonitoringMixin,            # metrics, execution_log, journal
    WorkbenchMixin,             # prompt_preview, count_tokens
    EventsMixin,                # events, events_stats
    MemoryMixin,                # memory_list, memory_search
    ProvidersMixin,             # provider_capabilities, provider_models
    ConfigurationMixin,         # configuration_update
    GoalsMixin,                 # goals_create, goals_update
    SleepModesMixin,            # sleep_mode_sleep, sleep_mode_wake
    ExecutionPatternsMixin,     # execution_patterns_config
    DelegationMixin,            # delegation_status
    ToolsMixin,                 # tools_list, tools_categories
    ViewSet,
):
    permission_classes = [StaffOnlyPermission]
    lookup_field = "assistant_key"

Mixin Endpoints

Mixin Endpoints
MonitoringMixin metrics, execution_log, goals, session_memory, projects, journal, context_buffer
WorkbenchMixin workbench, prompt_preview, assemble_preview, count_tokens, component_health
EventsMixin events, events_stats, events_replay, events_export
MemoryMixin memory_list, memory_search, memory_status
ProvidersMixin provider, provider_capabilities, provider_models, provider_status
ConfigurationMixin configuration, configuration_update, configuration_bulk_update, update_text
GoalsMixin goals_create, goals_update, goals_delete, goals_complete
SleepModesMixin sleep_mode, sleep_mode_sleep, sleep_mode_wake, sleep_mode_history, sleep_mode_schedule
ExecutionPatternsMixin execution_patterns, execution_patterns_config, execution_patterns_state
DelegationMixin delegation_status, delegation_delegates
ToolsMixin tools_list, tools_categories

Registry-Based Lookup

The API uses assistant keys instead of database PKs:

# ViewSet configuration
lookup_field = "assistant_key"
lookup_value_regex = "[^/]+"  # Any non-slash characters

# Base mixin helper
class AssistantActionMixin:
    def get_assistant(self, assistant_key):
        """Resolve assistant key to script via registry."""
        registry = get_assistant_registry()
        assistants = registry.list_assistants()

        if assistant_key not in assistants:
            return None, Response(
                {"error": f"Assistant '{assistant_key}' not found"},
                status=status.HTTP_404_NOT_FOUND,
            )

        script = get_assistant_script(assistant_key)
        return script, None

Example API Endpoint

# In mixins/monitoring.py
class MonitoringMixin:

    @action(detail=True, methods=["get"])
    def metrics(self, request, assistant_key=None):
        """
        GET /api/ai-assistants/:key/metrics/

        Returns current metrics for the assistant.
        """
        script, error = self.get_assistant(assistant_key)
        if error:
            return error

        return Response({
            "tick_count": script.db.tick_count,
            "error_count": script.db.error_count,
            "operating_mode": script.db.operating_mode,
            "estimated_tokens": script.db.estimated_tokens,
            # ...
        })

3. Shared Patterns

Pattern: Mixin-Based Dispatch

Both layers use mixins to group related functionality:

# Command pattern
class CmdAssistant(LifecycleMixin, ConfigMixin, ...):
    def func(self):
        if "init" in self.switches:
            self.setup_assistant(key)  # From mixin

# API pattern
class AssistantViewSet(MonitoringMixin, WorkbenchMixin, ...):
    @action(detail=True, methods=["get"])
    def metrics(self, request, key=None):
        # Each mixin contributes @action methods

Pattern: Registry-Based Lookup

Both layers resolve assistants via the registry, not DB queries:

# Commands use helpers
from evennia.contrib.base_systems.ai.helpers import get_assistant_script
script = get_assistant_script(assistant_key)

# API uses registry
registry = get_assistant_registry()
if assistant_key not in registry.list_assistants():
    return error_response

Pattern: Consistent Response Formatting

API responses follow a consistent structure:

# Success
return Response({
    "success": True,
    "data": {...}
})

# Error
return Response(
    {"error": "Message", "details": {...}},
    status=status.HTTP_400_BAD_REQUEST
)

4. Command Helpers

Shared parsing utilities in commands/setup/helpers.py:

def parse_key_value(args: list) -> tuple[str, str]:
    """Parse 'key value' or 'key=value' formats."""

def format_config_display(config: dict) -> str:
    """Format configuration for terminal display."""

def validate_config_key(key: str) -> tuple[bool, str]:
    """Validate configuration key is allowed."""

Key Files

File Lines Purpose
commands/setup/base.py 1-307 CmdAssistant main class
commands/setup/lifecycle.py ~600 /init, /clone, /start, /stop, /delete
commands/setup/config.py ~270 /config management
commands/base.py ~100 AICommand base class
api/views/assistants.py ~150 AssistantViewSet
api/views/base.py ~80 AssistantActionMixin
api/views/mixins/monitoring.py ~300 Metrics, logs, journal
api/views/mixins/workbench.py ~250 Prompt preview, tokens

See also: Architecture-Overview | Architecture-Tool-System | Architecture-Helpers