Skip to content

UI UI patterns

This document describes standard patterns for tool-UI interactions in the mozaiksai runtime.


Pattern 1: Single-Step UI Interaction

Use Case: Tool performs action and immediately presents result to user.

Flow: 1. Tool executes business logic 2. Tool emits ONE UI event with complete payload 3. User interacts with result

Example: API key input

async def request_api_key(service: str):
    response = await use_ui_tool("AgentAPIKeyInput", {
        "service": service,
        "label": f"{service} API Key"
    })
    return response.get("api_key")

When to Use: - No approval needed before action - Action is cheap/fast - User just needs to provide input or view result


Pattern 2: Two-Step Confirmation Workflow

Use Case: Tool needs user approval before performing expensive/irreversible action.

Flow: 1. Tool emits FIRST UI event (confirmation) with minimal payload 2. User confirms or cancels 3. If confirmed: Tool performs action 4. Tool emits SECOND UI event (result) with complete payload 5. User interacts with result

Example: File generation with confirmation

async def generate_files(confirmation_only: bool = True):
    if confirmation_only:
        # Step 1: Ask for confirmation (empty payload)
        confirm_response = await use_ui_tool("FileDownloadCenter", {
            "files": [],  # Empty - just asking permission
            "agent_message": "Ready to generate files?",
            "downloadType": "single"
        })

        if confirm_response.get("status") == "cancelled":
            return {"status": "cancelled"}

        # Step 2: User confirmed - create files
        files = await create_files()

        # Step 3: Show result UI with populated payload
        download_response = await use_ui_tool("FileDownloadCenter", {
            "files": files,  # Populated - actual data
            "agent_message": "Files ready! Click to download.",
            "downloadType": "bulk" if len(files) > 1 else "single"
        })

        return download_response
    else:
        # Single-step mode: create immediately
        files = await create_files()
        response = await use_ui_tool("FileDownloadCenter", {
            "files": files,
            "agent_message": "Generated files ready!",
            "downloadType": "bulk" if len(files) > 1 else "single"
        })
        return response

When to Use: - Action is expensive (API calls, file generation, database writes) - Action is irreversible - User should review/approve before proceeding - Result requires user interaction beyond just viewing

Key Characteristics: - Tool has confirmation_only boolean parameter - Same UI component handles both confirmation and result states - Component uses payload emptiness to determine which UI to render - TWO use_ui_tool() calls from single tool invocation


Pattern 3: Multi-Step Wizard

Use Case: Complex workflow requiring multiple sequential user inputs.

Flow: 1. Tool emits UI for Step 1 2. User provides input 3. Tool processes + emits UI for Step 2 4. User provides input 5. ... (repeat for N steps) 6. Tool completes and returns final result

Example: Multi-service API key collection

async def collect_api_keys_bundle(services: List[str]):
    # Single UI handles all services via stepper/tabs
    response = await use_ui_tool("AgentAPIKeysBundleInput", {
        "services": [
            {"name": s, "label": f"{s} API Key"} 
            for s in services
        ],
        "description": "Please provide API keys for the following services:"
    })

    # Component internally manages steps, returns all data at once
    return response.get("api_keys", {})

When to Use: - Multiple related inputs needed - Inputs depend on each other (step 2 depends on step 1 answer) - Better UX to guide user through steps vs. showing all at once

Key Characteristics: - Component has internal state management (stepper, tabs, accordion) - Single use_ui_tool() call, component handles multi-step internally - Returns all collected data when user completes final step


Pattern 4: Artifact vs. Inline Display

Use Case: Different UI presentations based on content complexity.

Artifact Display (display="artifact"): - Opens in dedicated side panel - For complex/large content (diagrams, workflows, code) - User can expand/collapse panel - Example: ActionPlan, CodeEditor

Inline Display (display="inline"): - Appears in message stream - For simple/quick interactions (forms, confirmations, downloads) - Doesn't interrupt conversation flow - Example: API key inputs, file downloads, confirmations

Configuration:

# tools.yaml
tools:
  - agent: ActionPlanAgent
    file: render_action_plan.py
    function: render_action_plan
    tool_type: UI_Tool
    auto_tool_call: true
    ui:
      component: ActionPlan
      mode: artifact
  - agent: DeliveryAgent
    file: open_download_center.py
    function: open_download_center
    tool_type: UI_Tool
    auto_tool_call: true
    ui:
      component: FileDownloadCenter
      mode: inline
lifecycle_tools: []

Declarative type rules: - UI_Tool = interactive surface that waits for a user response via use_ui_tool(...) - UI_Surface = one-way artifact/status surface emitted via emit_ui_surface(...) - Agent_Tool = backend-only logic; omit the ui block entirely


Implementation Guidelines for Generator Agents

When creating tools that emit UI events:

1. Single-Step Tools

# No confirmation needed - just do it
async def simple_action():
    result = await do_work()
    response = await use_ui_tool("ResultComponent", {"data": result})
    return response

System Message Guidance:

This tool performs [action] and shows result immediately.
No confirmation required.
Emits ONE UI event with complete payload.

2. Two-Step Confirmation Tools

async def action_with_confirmation(confirmation_only: bool = True):
    if confirmation_only:
        # Confirmation step
        confirm = await use_ui_tool("Component", {"preview": "...", "data": []})
        if confirm.get("status") == "cancelled":
            return {"status": "cancelled"}

        # Action step
        result = await do_work()

        # Result step
        response = await use_ui_tool("Component", {"data": result})
        return response
    else:
        # Skip confirmation
        result = await do_work()
        response = await use_ui_tool("Component", {"data": result})
        return response

System Message Guidance:

This tool supports two modes:

confirmation_only=True (default):
  - Emits TWO UI events
  - First: Confirmation dialog (empty/minimal payload)
  - Second: Result UI (full payload)

confirmation_only=False:
  - Emits ONE UI event
  - Immediate action + result

Component must handle both empty (confirmation) and full (result) payloads.

3. UI Component Requirements

For components used in two-step workflows:

function MyComponent({ payload }) {
  const isEmpty = !payload.data || payload.data.length === 0;

  if (isEmpty) {
    // Confirmation mode
    return (
      <div>
        <p>{payload.preview || "Ready to proceed?"}</p>
        <button onClick={() => onResponse({status: "confirmed"})}>Yes</button>
        <button onClick={() => onResponse({status: "cancelled"})}>No</button>
      </div>
    );
  } else {
    // Result mode
    return (
      <div>
        <p>Results:</p>
        {payload.data.map(item => <div>{item}</div>)}
      </div>
    );
  }
}

Summary Table

Pattern UI Events Use Case Example
Single-Step 1 Simple input/output API key input
Two-Step Confirmation 2 Expensive action requiring approval File generation
Multi-Step Wizard 1 (with internal steps) Sequential related inputs Multi-service setup
Artifact vs Inline N/A Display complexity ActionPlan (artifact) vs API key (inline)

Best Practices

  1. Default to confirmation for expensive operations
  2. File generation → confirmation_only=True by default
  3. API calls → confirmation_only=True by default
  4. Simple inputs → Single-step

  5. Use consistent payload patterns

  6. Empty array/object → Confirmation state
  7. Populated array/object → Result state
  8. Component logic should check payload emptiness

  9. Provide clear agent messages

  10. Confirmation: "Ready to [action]? Yes/No"
  11. Result: "[Action] complete! Here are the results."

  12. Handle cancellation gracefully

  13. Always check status == "cancelled" after confirmation
  14. Return early with clear status message
  15. Don't perform action if user cancelled

  16. Document in tools.yaml

    tools:
      - agent: PlannerAgent
        file: my_tool.py
        function: my_tool
        tool_type: UI_Tool
        auto_tool_call: true
        description: "If confirmation_only=true, do a two-step UI flow."
        ui:
          component: MyComponent
          mode: inline
    lifecycle_tools: []