Skip to content

ADR 006: Superpowers Platform (User-Created Apps)

Date: 2025-12-12 Updated: 2026-01-07 Status: Implementing Deciders: Engineering Team Related: ADR 004: MiniApp Framework, ADR 001: Event Sourcing


Terminology

Term Definition
Superpower A user-created custom app built via conversation with Sage
Workflow An automated background task (Travel Brain, Gmail, Calendar, etc.) in app/workflows/
MiniApp A built-in app like Trip Planner or Bill Split

Context

We need to evolve from developer-built MiniApps to user-created apps ("superpowers") that can be:

  1. Created via conversation - Users "vibe-code" by chatting with Sage
  2. Shared privately - Via unique links
  3. Published to marketplace - For community discovery

Use Case Example

A user describes: "Create a superpower that analyzes receipt photos, tracks who owes what, and checks my Gmail for Zelle/Venmo payment confirmations to mark splits as paid."

This should generate a functional superpower without the user writing any code.

Existing Infrastructure

ADR 004 established a mature MiniApp framework: - Event sourcing with EventStore and StateCoordinator - Handler Registry pattern for loading handlers - Default reducer that merges event_data into state - UI Block system for rendering - SmartMessageRouter for intent-based routing - Database tables: mini_apps, room_capabilities, user_miniapp_settings

Requirements

  1. No Code Required - Users define config, not Python
  2. Full Integration Support - Gmail, Calendar, Vision API via OAuth
  3. Secure Isolation - Superpowers can't access other users' data
  4. Seamless Routing - SmartMessageRouter routes to superpowers via create_superpower workflow
  5. Marketplace Ready - Foundation for future public sharing

Decision

We will implement a Config-Driven Superpowers Platform with:

  1. JSON Definition Schema - Pydantic-validated structure in mini_apps.definition
  2. GenericConfigHandler - Interprets JSON definitions at runtime
  3. Database-Driven Triggers - Move patterns from Python to miniapp_trigger_patterns table
  4. Scoped OAuth Grants - Per-app OAuth permissions in custom_miniapp_oauth_grants
  5. Template Interpolation - Dynamic content via {{$state.field}} syntax

Conceptual Model

Users define three things (no code):

What Users Define What It Becomes
Objects/State initial_state JSON with fields
Prompts for Processing actions with operations (LLM, Vision, Gmail)
Output Format response templates and ui_blocks

Architecture

User (via Sage)
  → Describes superpower requirements
  → SmartRouter routes to `create_superpower` workflow
  → AppCreationHandler manages multi-turn conversation
  → GPT-5 generates CustomAppDefinition JSON
  → Validated against Pydantic schema
  → Stored in mini_apps.definition

User Message (using superpower)
  → SmartMessageRouter (dynamic superpower list)
  → MiniAppDetector (database-driven patterns)
  → MiniAppRegistry.get_handler()
    → Returns GenericConfigHandler for superpowers
  → GenericConfigHandler.handle()
    → Match action via triggers
    → Check OAuth grants
    → Execute operations sequentially
    → Emit events to EventStore
    → Interpolate response template
  → User sees response + UI

Alternatives Considered

Option 1: Sandboxed Python Execution

Approach: Users write Python code executed in sandbox

Pros: - Maximum flexibility - Full programming power

Cons: - ❌ Security nightmare (code injection, resource abuse) - ❌ Complex sandboxing infrastructure - ❌ Users must know Python - ❌ Versioning and dependency hell

Verdict: ❌ Too risky, too complex for MVP

Option 2: Visual Builder UI

Approach: Drag-and-drop workflow builder (like Zapier)

Pros: - Intuitive for non-technical users - Visual feedback

Cons: - ❌ Requires significant frontend investment - ❌ Limited flexibility - ❌ Doesn't leverage our conversational strength

Verdict: ⏳ Phase 3 enhancement, not foundation

Option 3: Config-Driven with GenericHandler (CHOSEN)

Approach: JSON definitions interpreted by generic handler

Pros: - ✅ No code security risks - ✅ Validates via Pydantic - ✅ Leverages existing EventStore, StateCoordinator, UIBuilder - ✅ Natural fit with conversational creation - ✅ Portable and versionable - ✅ Clear upgrade path to marketplace

Cons: - ⚠️ Limited to predefined operation types - ⚠️ Template syntax learning curve

Mitigation: - Rich operation library (vision, gmail, calendar, web) - AI-assisted template generation - Expandable operation types over time

Verdict:Best balance of safety, flexibility, and fit


Implementation Details

1. Database Schema Extensions

-- Extend mini_apps table
ALTER TABLE mini_apps ADD COLUMN creator_user_id UUID REFERENCES users(id);
ALTER TABLE mini_apps ADD COLUMN status VARCHAR(20) DEFAULT 'active';
ALTER TABLE mini_apps ADD COLUMN share_token VARCHAR(64) UNIQUE;
ALTER TABLE mini_apps ADD COLUMN required_scopes JSONB DEFAULT '[]';
ALTER TABLE mini_apps ADD COLUMN definition JSONB;

-- New: Trigger patterns (replaces hard-coded Python)
CREATE TABLE miniapp_trigger_patterns (
    id UUID PRIMARY KEY,
    miniapp_id VARCHAR(100) REFERENCES mini_apps(id),
    keywords JSONB DEFAULT '[]',
    phrases JSONB DEFAULT '[]',
    regex_patterns JSONB DEFAULT '[]',
    priority INTEGER DEFAULT 5,
    enabled BOOLEAN DEFAULT TRUE
);

-- New: OAuth grants per custom app
CREATE TABLE custom_miniapp_oauth_grants (
    id UUID PRIMARY KEY,
    user_id UUID REFERENCES users(id),
    miniapp_id VARCHAR(100) REFERENCES mini_apps(id),
    scope VARCHAR(50) NOT NULL,
    granted_at TIMESTAMPTZ DEFAULT NOW()
);

2. CustomAppDefinition Schema

class CustomAppDefinition(BaseModel):
    version: str = "1.0"
    metadata: AppMetadata           # name, description, category, icon
    initial_state: Dict[str, Any]   # Starting state for sessions
    event_types: List[EventType]    # Events this app can emit
    trigger_patterns: TriggerDef    # Auto-detection patterns
    actions: List[ActionDefinition] # Behavior definitions
    ui_blocks: Dict[str, UIBlock]   # Named UI views

3. GenericConfigHandler

class GenericConfigHandler(BaseMiniAppHandler):
    """Interprets JSON app definitions at runtime."""

    def __init__(self, definition: CustomAppDefinition, miniapp_id: str):
        self.definition = definition
        self.app_id = miniapp_id
        self.template_engine = TemplateEngine()
        self.operation_executors = {
            "emit_event": EmitEventExecutor(),
            "vision_analysis": VisionAnalysisExecutor(),
            "gmail_search": GmailSearchExecutor(),
            "llm_extract": LLMExtractExecutor(),
        }

    async def handle(self, context: MiniAppContext) -> MiniAppResponse:
        # 1. Match action via triggers
        action = self._match_action(context)

        # 2. Check OAuth requirements
        if action.oauth_required:
            missing = await self._check_grants(context.user_id, action.oauth_required)
            if missing:
                return self._request_oauth(missing)

        # 3. Execute operations, emit events
        op_results = {}
        for op in action.operations:
            result = await self._execute_operation(op, context, op_results)
            if op.output:
                op_results[op.output] = result

        # 4. Interpolate response
        message = self.template_engine.interpolate(
            action.response.template,
            {"state": context.state, "op": op_results}
        )

        return MiniAppResponse(message=message)

4. Template Interpolation

{{$state.items | length}}          → len(state["items"])
{{$state.total | currency}}        → "$123.45"
{{$op.receipt_data.merchant}}      → Previous op result
{{$context.user_name}}             → Execution context
{{$state.items | map:"name"}}      → Extract field from list
{{value | default:"none"}}         → Default if falsy

5. SmartMessageRouter Integration

# Dynamic MiniApp list in system prompt
def _build_miniapp_list(self, user_id: str) -> str:
    # Built-in apps
    apps = ["trip_planner: Plan trips...", "bill_split: Split bills..."]

    # User's custom apps
    custom = self.db.query(MiniApp).filter(
        MiniApp.creator_user_id == user_id,
        MiniApp.status == "active"
    ).all()

    for app in custom:
        apps.append(f"{app.id}: {app.description}")

    return "\n".join(apps)

Security Model

OAuth Scope Isolation

  • Custom apps request scopes via required_scopes
  • Users explicitly grant scopes via custom_miniapp_oauth_grants
  • Grants are per-user-per-app (not global)
  • Users can revoke at any time

State Isolation

  • Custom apps use existing EventStore with session_id scoping
  • No cross-session or cross-user data access
  • Event namespace: custom:{miniapp_id}:{session_id}

Template Safety

  • Templates only access explicit context
  • No code execution in templates
  • Pydantic validation prevents injection

Rate Limits

  • Operation execution rate limited per user
  • LLM operations count against user's quota
  • Integration calls (Gmail, etc.) throttled

Phases

Phase 1: Foundation (Current)

  • Database migration ✅
  • SQLAlchemy models ✅
  • Pydantic schema ✅
  • Template engine
  • GenericConfigHandler
  • Operation executors
  • Database-driven triggers
  • Registry integration

Phase 2: Creation Flow

  • Sage conversation → Definition generation
  • Preview and validation UI
  • Edit via conversation

Phase 3: Private Sharing

  • Share tokens and install flow
  • Permission prompts
  • Usage analytics

Phase 4: Public Marketplace

  • Discovery and search
  • Ratings and reviews
  • Featured apps

Trade-offs

Pros

  1. Safe by Design - No arbitrary code execution
  2. Leverages Existing Infra - EventStore, StateCoordinator, UIBuilder
  3. Natural Fit - Conversational creation matches product DNA
  4. Extensible - New operation types can be added
  5. Portable - JSON definitions are versionable and shareable

Cons

  1. Limited Flexibility - Can't do arbitrary computation
  2. Learning Curve - Template syntax and operation model
  3. Complexity - GenericHandler more complex than dedicated handlers

Mitigations

  • Rich operation library covers most use cases
  • AI assistance for template generation
  • Comprehensive documentation and examples
  • Gradual rollout with power users first

Success Criteria

Technical

  • Custom app handles 95% of use cases without code
  • GenericHandler performance <200ms overhead
  • OAuth grants work correctly
  • Template interpolation handles edge cases
  • Database triggers load <50ms with caching

Product

  • User can create simple app in <5 minutes via chat
  • Share flow works without friction
  • Installed apps appear in SmartMessageRouter

References


Revision History

  • 2025-12-12: Initial implementation - database, models, schema complete