The UserInteractionRegistry is the mechanism that allows tools to “pause” and ask the user for information or confirmation. This is essential for tools that require human-in-the-loop decisions.
Architecture
The registry works by coordinating between the async tool execution task and the UI event loop. It uses oneshot channels to bridge the gap between an async request and its eventual response.
pub struct UserInteractionRegistry {
pending_interactions: Mutex<HashMap<String, InteractionSender>>,
event_sender: mpsc::UnboundedSender<ControllerEvent>,
}
How it Works
- Tool Requests Interaction: A tool calls a method on the
interaction_registry(via theToolContext). - Registry Creates a Channel: The registry generates a unique ID, creates a
oneshotchannel, and stores the sender. - Emit Event: The registry sends a
ControllerEvent(e.g.,AskUser) to the controller, which passes it to the UI. - Tool Awaits: The tool execution task
awaits the receiver of theoneshotchannel. - User Responds: The UI captures the user’s input and sends it back to the controller.
- Complete Interaction: The controller calls
complete_interaction(id, response)on the registry. - Resume Tool: The
oneshotsender is triggered, the tool receives the response and continues execution.
Example: AskUserQuestions
The AskUserQuestions tool is a built-in tool that utilizes this registry.
// Inside a tool's execute method
let response = context.interaction_registry
.ask_user(context.session_id, "What is your favorite color?")
.await?;
// response is now the string the user typed
Interaction Types
While the most common interaction is a simple text question, the registry can support various types:
- Confirmation: Yes/No prompts.
- Selection: Choosing from a list of options.
- Form: Multiple input fields.
Timeouts and Cleanup
To prevent “zombie” tasks, the registry can implement timeouts. If a user doesn’t respond within a certain period, the interaction can be automatically cancelled, and the tool will receive an error, allowing it to exit gracefully.
