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¶
- Default to confirmation for expensive operations
- File generation →
confirmation_only=Trueby default - API calls →
confirmation_only=Trueby default -
Simple inputs → Single-step
-
Use consistent payload patterns
- Empty array/object → Confirmation state
- Populated array/object → Result state
-
Component logic should check payload emptiness
-
Provide clear agent messages
- Confirmation: "Ready to [action]? Yes/No"
-
Result: "[Action] complete! Here are the results."
-
Handle cancellation gracefully
- Always check
status == "cancelled"after confirmation - Return early with clear status message
-
Don't perform action if user cancelled
-
Document in
tools.yaml