Tool Event Lifecycle¶
This document maps the live Mozaiks workflow UI transport to AG-UI and CopilotKit.
It focuses on the actual implementation path in this repo:
- how
use_ui_tool(...)becomes frontend UI - where
chat.tool_callis canonical - how the frontend preserves
tool_callsemantics through rendering - what Mozaiks should keep
- what should be normalized next
Current live lifecycle¶
1. Interactive workflow UI¶
Current interactive UI tools follow this runtime path:
workflow tool
-> use_ui_tool(...)
-> _emit_tool_call_core(...)
-> transport.send_tool_call_event(...)
-> kind=tool_call
-> UnifiedEventDispatcher maps to chat.tool_call
-> ChatPage detects component_type
-> dynamicUIHandler annotates the event with onResponse(...)
-> ChatPage stores a `toolCall` render object in message/artifact state
-> WorkflowUIRouter resolves the workflow component
-> generated React component renders and calls onResponse(...)
-> websocket tool_call_response
-> submit_tool_call_response(...)
-> pending future resolves inside use_ui_tool(...)
This is the live path today.
The important point is that the wire contract is chat.tool_call, and the frontend now preserves that shape through rendering instead of inventing a second event type.
2. Auto-tool and fire-and-forget surfaces¶
Auto-tool execution uses the same render lane, but without waiting for user response:
- producer:
AutoToolEventHandler - transport:
chat.tool_call - required hint:
awaiting_response=false - completion lane:
chat.tool_response
This is closer to generated status/artifact rendering than to human-in-the-loop.
3. Input requests after normalization¶
Runtime-generated AG2 input requests now normalize onto the same workflow UI lane:
- transport event:
chat.tool_call - discriminator:
interaction_type=input_request - generic chat-ui presentation:
display=composer - fallback component identity:
UserInputRequest
That means response-required UI from InputRequestEvent now follows the same render lane as use_ui_tool(...), but generic text reply in chat-ui uses the main composer instead of an inline card.
chat.input_request is not emitted to browser clients for runtime-managed interactive input.
AG2's generic "Please give feedback to chat_manager..." compatibility prompt is still the actual response handoff signal. The runtime suppresses that raw prompt text, but keeps the pending input request live so the user can answer through the normal workflow composer or tool_call_response.
When the AG2 run slice ends after emitting one of these input requests, the runtime emits chat.run_complete with status=0 and reason=awaiting_user_input. Terminal workflow completion uses status=1.
The key difference from AG-UI and CopilotKit¶
AG-UI and CopilotKit make the tool call itself the UI interaction contract.
Mozaiks often emits UI from inside a workflow tool implementation. That means there are two related but distinct things:
- the underlying workflow tool invocation
- the UI interaction request emitted by that tool
That is why Mozaiks has historically leaned on a separate UI event id (corr / toolCallId) instead of relying only on the original AG2 tool call_id.
This is also why AG-UI examples feel cleaner: they collapse those two ideas into one protocol lifecycle.
Side-by-side lifecycle mapping¶
| Concern | AG-UI / CopilotKit | Current Mozaiks | Judgment |
|---|---|---|---|
| Render request | Tool lifecycle starts on the wire and the frontend renders directly from that tool call | use_ui_tool(...) emits chat.tool_call; frontend stores a toolCall render object in chat/artifact state | Mozaiks should keep the runtime-owned authoring model |
| Tool args | Explicit tool-call argument lifecycle in AG-UI | Fully materialized payload is sent in one chat.tool_call envelope | Fine for generated app UI, less reusable as a general protocol |
| Response wait | CopilotKit renderAndWaitForResponse, AG-UI interrupts/resume | awaiting_response=true plus tool_call_response; transport resolves either a pending tool-call future or a pending AG2 input callback | Capability is present, but the pause/resume model is still implicit rather than first-class |
| Result delivery | Explicit tool result event | chat.tool_response for tool results, plus chat.tool_call_dismiss for artifact close | Good enough, but less explicit than AG-UI lifecycle phases |
| Shared state | AG-UI snapshot/delta stream; CopilotKit useCoAgent / useAgent | No active protocol-level shared-state stream; shell state lives in ChatUIContext | Mozaiks should not pretend shell cache equals runtime shared state |
| Frontend registration | Frontend-defined tools and renderer hooks | Workflow-local React component registry through WorkflowUIRouter | This is a Mozaiks strength and should stay |
| Primitive UI bus | Usually not the main generative UI lane | Separate ui.* primitive lane through useAppEventBus | Good separation; keep it separate from workflow UI |
Current canonical contract¶
For workflow-owned UI, the current canonical contract should be treated as:
Authoring contract¶
- Python side uses
use_ui_tool(...)for response-bearing UI - Python side uses
emit_ui_surface(...)for fire-and-forget UI - React side is mounted by
WorkflowUIRouter - Generated React responds through
onResponse(...)
Wire contract¶
Workflow-owned UI renders through chat.tool_call.
Current required fields for workflow UI transport:
type: chat.tool_call
data:
tool_call_id: ui_123
corr: ui_123
tool_name: request_api_key
component_type: AgentAPIKeysBundleInput
workflow_name: AgentGenerator
interaction_type: ui_tool
awaiting_response: true
display: artifact
display_type: artifact
payload:
workflow_name: AgentGenerator
interaction_type: ui_tool
display: artifact
Current rules:
component_typeis required for workflow UI renderingtool_call_idis the primary UI interaction idcorrremains the compatibility alias for existing consumersworkflow_nameshould be available at the top level and inpayloadinteraction_typeshould be one ofui_tool,ui_surface,auto_tool, orinput_requestawaiting_response=truemeans the frontend is expected to answerdisplaycontrolscomposer,inline,artifact, orviewbehavior
Generic tool invocations that do not carry component_type are not workflow UI surfaces. They are transport/telemetry events, not component-mount requests.
Response contract¶
Generated React components answer through:
- websocket
tool_call_response - or REST
POST /api/tool-call/respond
The response is correlated by the UI interaction id, not by raw message text.
This response contract is only for workflow-owned UI. Persistent app pages that launch workflows do so through declarative page actions (action_type: workflow), not through tool_call_response.
Recommended next normalization¶
Mozaiks should not switch to CopilotKit wholesale.
It should keep its stronger generated-app model:
- runtime-owned
use_ui_tool(...) - workflow-local component registry
- artifact-aware shell layouts
- app/workflow/module composition
But it should normalize the contract around those strengths:
chat.tool_callstays the only workflow UI render lane.- The frontend should keep
tool_call_id/tool_nameas the canonical identifiers all the way through render state. ui.*stays limited to primitive refresh/update/open/close behavior.- response-required interactions should not introduce any second browser-facing lane instead of remaining a parallel primary HITL lane.
- If Mozaiks later needs protocol reuse beyond its own shell, it should evolve
chat.tool_calltoward AG-UI-style explicit phases rather than adding more side channels.
Recommended future evolution¶
The next protocol step should be:
- keep the current authoring model
- keep the
chat.*namespace if desired - make the wire lifecycle more AG-UI-shaped
That means:
- explicit call lifecycle fields or events
- one canonical interrupt/resume model
- one canonical shared-state model if shared runtime state is needed
- no overlap between workflow UI rendering and primitive
ui.*events
The goal is not “become CopilotKit.”
The goal is:
- AG-UI-grade protocol discipline
- plus Mozaiks-grade generated app composition