1 Architecture Prompt System
blightbow edited this page 2025-12-08 04:11:20 +00:00

Architecture: Prompt System

Layer 2 - Templates, Components, and Runtime Assembly


Overview

The prompt system manages the LLM context window through three interrelated concepts:

  • PromptComponent - Ordered building blocks of the context window
  • PromptTemplate - Reusable content with placeholders
  • PromptRegistry - Persistent storage for user customizations

1. Component ID Scheme

Components use a 1000-point increment scheme for ordering:

Static Component IDs:
  0    → System Prompt
  1000 → Character Context
  1500 → Entity Context (O-Mem Working Memory)
  2000 → Semantic Memories
  3000 → Context Buffer
  4000 → Goals
  5000 → Conversation History
  6000 → Pending Event
  7000 → Tool Result

Pluggable Component IDs:
  1-999       → After System Prompt (0)
  1001-1499   → After Character Context (1000)
  1501-1999   → After Entity Context (1500)
  2001-2999   → After Semantic Memories (2000)
  ...

The spacing provides 999 slots between each static component pair for user-defined content.


2. PromptComponent

Dataclass representing a context window segment (prompt_components.py).

@dataclass
class PromptComponent:
    id: int              # Ordered position (0, 1000, 2000... or 1, 2, 1001...)
    key: str             # Unique identifier ("system_prompt", "my_custom")
    name: str            # Human-readable display name
    description: str     # What this component provides
    content: str         # Template content with {placeholders}
    is_static: bool      # True for built-in, False for user-created
    enabled: bool        # Whether to include in assembly
    role: str            # Message role ("system", "user", "assistant", "tool")

Static Components

Built-in components with fixed IDs:

ID Key Role Purpose
0 system_prompt system Core LLM instructions
1000 character_context system Persona, active project, session memory
1500 entity_context system O-Mem working memory for entities
2000 semantic_memories system Retrieved facts from Mem0
3000 context_buffer system Ambient messages, environment
4000 goals system Active goals with hierarchy
5000 conversation_history mixed Recent user/assistant exchanges
6000 pending_event user Current event being processed
7000 tool_result tool Result from previous tool call

Component Functions

from evennia.contrib.base_systems.ai.prompt_components import (
    get_static_components,       # All static components
    get_static_component,        # Single by key
    get_ordered_component_ids,   # [0, 1000, 1500, 2000, ...]
    is_static_id,                # Check if ID is static
    allocate_pluggable_id,       # Get next available ID
)

3. PromptTemplate

Reusable content with runtime placeholders (prompt_templates.py).

@dataclass
class PromptTemplate:
    key: str             # Unique identifier
    name: str            # Display name
    description: str     # What this template is for
    content: str         # Template text with {placeholders}
    placeholders: list   # List of placeholder names
    is_builtin: bool     # True for system templates
    category: str        # "system_prompt", "reflection", etc.

Built-in Templates

Key Category Placeholders Purpose
default system_prompt tick_rate Native tool calling mode
default_fallback system_prompt tick_rate, tool_schemas JSON format for non-native providers
reflection reflection 6 metrics Self-reflection prompt

Template Rendering

from evennia.contrib.base_systems.ai.prompt_templates import (
    get_template,             # Get by key
    get_default_template,     # Native mode template
    get_fallback_template,    # JSON fallback template
    render_template,          # Fill placeholders (raises on missing)
    safe_render_template,     # Fill placeholders (leaves missing unchanged)
)

# Example
template = get_default_template()
rendered = render_template(template, tick_rate=5)

4. PromptRegistry

Singleton script for persistent template and component storage (prompt_registry.py).

Getting the Registry

from evennia.contrib.base_systems.ai.prompt_registry import get_prompt_registry

registry = get_prompt_registry()

Template CRUD

# Create user template
registry.create_template(
    key="my_template",
    name="Custom Assistant",
    content="You are a {personality} assistant...",
    description="Custom personality template",
    placeholders=["personality"],
    category="system_prompt"
)

# Get template (checks user templates, then falls back to built-in)
template = registry.get_template("my_template")

# Update template
registry.update_template("my_template", content="New content...")

# Delete template
registry.delete_template("my_template")

# List all templates
all_templates = registry.list_templates(include_builtin=True)

# Clone built-in for customization
registry.clone_template("default", "my_custom", new_name="My Custom")

Component CRUD

# Create pluggable component (ID auto-allocated)
registry.create_component(
    key="my_context",
    name="Custom Context",
    content="Additional context here...",
    after_static=1000,        # Insert after character_context
    description="Custom context block",
    role="system"
)
# → Returns component with allocated ID (e.g., 1001)

# Get component by ID or key
comp = registry.get_component(1001)
comp = registry.get_component_by_key("my_context")

# Update component
registry.update_component(1001, content="Updated content...")

# Delete component
registry.delete_component(1001)

# List components
all_comps = registry.list_components(include_static=True)  # Sorted by ID
pluggable = registry.list_pluggable_components()
after_char = registry.list_components_after(1000)  # Range 1001-1999

# Move component to different position
registry.move_component(1001, new_after_static=2000)

Template ↔ Component Bridge

# Apply template content to existing component
registry.apply_template_to_component("my_template", component_id=1001)

# Save component content as reusable template
registry.save_component_as_template(
    component_id=1001,
    template_key="saved_from_component",
    name="Saved Component Template"
)

# Convert to PromptComponent dataclass for assembly
prompt_component = registry.component_to_prompt_component(1001)

Export/Import

# Export for sharing
json_data = registry.export_template_json("my_template")
json_data = registry.export_component_json(1001)

# Import from JSON
registry.import_template_json(data, overwrite=False)
registry.import_component_json(data, overwrite=False)

5. Runtime Assembly Flow

Tick Event
    │
    ▼
┌──────────────────────────────────────────────────────────────────┐
│  Collect Components (sorted by ID)                               │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│   0: System Prompt        ← Rendered from template               │
│   1: (pluggable)          ← User component if exists             │
│   2: (pluggable)          ← User component if exists             │
│   ...                                                            │
│   1000: Character Context ← Persona, project, session memory     │
│   1001: (pluggable)       ← User component if exists             │
│   ...                                                            │
│   1500: Entity Context    ← O-Mem working memory                 │
│   ...                                                            │
│   2000: Semantic Memories ← Mem0 retrieval results               │
│   ...                                                            │
│   7000: Tool Result       ← Previous tool execution result       │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
    │
    ▼
Filter by enabled=True
    │
    ▼
Render placeholders
    │
    ▼
Convert to LLM message format by role
    │
    ▼
Send to UnifiedLLMClient

6. ID Allocation Details

When creating a pluggable component:

def allocate_pluggable_id(existing_ids: list[int], after_static: int) -> int:
    """
    Find next available ID in range (after_static + 1, after_static + 1000).

    Example:
        after_static=1000, existing=[1001, 1002]
        → Returns 1003

        after_static=1000, existing=[]
        → Returns 1001
    """

Range limits:

  • Each static component has 999 pluggable slots
  • If a range fills up, ValueError is raised
  • Use move_component() to relocate if needed

7. Data Storage

Registry Script Attributes

registry.db.templates = {
    "template_key": {
        "key": str,
        "name": str,
        "description": str,
        "content": str,
        "placeholders": list[str],
        "category": str,
        "is_builtin": False,
        "created": datetime,
        "updated": datetime,
    },
    ...
}

registry.db.components = {
    component_id: {
        "id": int,
        "key": str,
        "name": str,
        "description": str,
        "content": str,
        "role": str,
        "after_static": int,
        "is_static": False,
        "created": datetime,
        "updated": datetime,
    },
    ...
}

Key Files

File Lines Purpose
prompt_components.py 1-304 PromptComponent dataclass, static definitions, ID utilities
prompt_templates.py 1-300 PromptTemplate dataclass, built-in templates, rendering
prompt_registry.py 1-983 PromptRegistry script, CRUD operations

See also: Architecture-Context-System | Architecture-Core-Engine | Data-Flow-02-ReAct-Loop