Write Actions & Confirmation Gate
The assistant uses a two-step confirmation gate for every tool that mutates data. The AI previews what it will do, the user confirms or cancels, and only then does the mutation execute.
How It Works
Section titled “How It Works”Every write tool accepts a confirmed boolean parameter (defaults to false):
- Preview — the AI calls the tool with
confirmed: false. The tool validates inputs, resolves references (tags, phone numbers), checks for duplicates, and returns a JSON preview with"status": "preview". - User decision — the UI renders Confirm, Edit, and Cancel buttons below the preview card.
- Execute — if the user clicks Confirm, the AI calls the tool again with
confirmed: true. The tool performs the mutation and returns"status": "created"(or"updated","sent", etc.) with adeep_linkto the new or modified entity.
AI calls tool(confirmed: false) → Tool validates + returns preview JSON → UI shows ToolCallCard (yellow/pending) + action buttons → User clicks Confirm → AI calls tool(confirmed: true) → Tool executes mutation + returns success JSON with deep_link → UI shows ToolCallCard (green/success) + "Open" buttonWrite Tools
Section titled “Write Tools”All write tools follow the same confirmation pattern:
| Tool | Action | Domain |
|---|---|---|
create_contact | Create a new contact with phone, tags | Contacts |
update_contact | Update contact fields | Contacts |
add_contact_note | Add a note to a contact | Contacts |
manage_contact_tags | Add/remove tags (auto-creates missing tags) | Contacts |
manage_contact_emails | Add/remove email addresses | Contacts |
manage_contact_phones | Add/remove phone numbers | Contacts |
send_sms | Send an SMS (shows recent conversation in preview) | Messages |
create_task | Create a task for a contact | Tasks |
update_task | Update task fields | Tasks |
complete_task | Mark a task complete | Tasks |
dismiss_task | Dismiss a task | Tasks |
reopen_task | Reopen a completed/dismissed task | Tasks |
create_plan | Apply a plan template to a contact | Workflow |
create_plan_template | Create a new plan template | Workflow |
update_plan_template | Update template metadata | Workflow |
create_agent | Create a voice agent | Infrastructure |
update_agent | Update voice agent settings | Infrastructure |
create_analyzer | Create a call analyzer | Infrastructure |
update_analyzer | Update analyzer settings | Infrastructure |
create_text_agent | Create a text agent | Infrastructure |
update_text_agent | Update text agent settings | Infrastructure |
buy_phone_number | Purchase a phone number | Infrastructure |
assign_phone_agent | Assign a voice agent to a phone | Infrastructure |
assign_phone_analyzers | Assign analyzers to a phone | Infrastructure |
assign_phone_text_agent | Assign a text agent to a phone | Infrastructure |
Tool Response Shape
Section titled “Tool Response Shape”Preview response (confirmed: false)
Section titled “Preview response (confirmed: false)”{ "action": "create_contact", "status": "preview", "preview": { "first_name": "Jane", "last_name": "Doe", "phone_number": "+15551234567", "tags": ["VIP"] }, "message": "Ready to create contact Jane Doe. Call again with confirmed=true to proceed."}Success response (confirmed: true)
Section titled “Success response (confirmed: true)”{ "action": "create_contact", "status": "created", "contact_id": "a1b2c3d4-...", "deep_link": "/contacts/a1b2c3d4-...", "message": "Contact Jane Doe created successfully."}The deep_link is generated by entity_deep_link() in page_context_mapper.rs, which maps entity types to their route prefixes.
Confirmation UI
Section titled “Confirmation UI”The ChatMessage component tracks an ActionState for each message that contains a preview:
enum ActionState { Pending, // Buttons visible — waiting for user decision Confirming, // User clicked Confirm — "Confirmed" badge shown Cancelled, // User clicked Cancel — "Cancelled" badge shown}Three buttons appear below messages with a preview tool result:
- Confirm — sends
"Yes, go ahead"as a user message, which triggers the AI to re-call the tool withconfirmed: true - Edit — pre-fills the input with
"Yes, but "so the user can add custom instructions - Cancel — sends
"No, cancel that"as a user message
Buttons only appear when:
- The tool result has
"status": "preview"(detected byToolResultMeta::parse()) - The message is not from history (
from_history: false) - The assistant has finished streaming (
is_loading: false)
Historical previews that were never confirmed show an “Expired” badge instead of action buttons.
Tool Call Cards
Section titled “Tool Call Cards”The ToolCallCard component renders a compact card with a color-coded left accent bar:
| State | Accent Color | Indicator |
|---|---|---|
| Loading | Primary (blue) | Spinning border |
| Preview | Pending (yellow) | Pulsing dot |
| Success | Success (green) | Solid dot |
| Error | Destructive (red) | Solid dot |
| Other | Muted | Solid dot |
Successful actions with a deep_link show an Open button that navigates to the created/modified entity.
Building a Write Tool
Section titled “Building a Write Tool”Use the make_tool_execute! macro to reduce boilerplate:
pub fn build_create_thing_tool(session: Session) -> Tool { Tool { name: AssistantToolName::CreateThing.api_name().to_string(), description: "Create a thing. Call with confirmed=false to preview, \ then confirmed=true to execute.".to_string(), input_schema: schemars::schema_for!(CreateThingInput), execute: make_tool_execute!("create_thing", CreateThingInput, handler => session), }}
async fn handler(session: Session, input: CreateThingInput) -> Result<String, AppError> { // 1. Permission check // 2. Validate inputs // 3. If !input.confirmed → return preview JSON // 4. If confirmed → execute mutation, return success JSON with deep_link}The macro handles JSON deserialization, session cloning, and async dispatch through the dedicated TOOL_RUNTIME (a separate multi-threaded Tokio runtime that avoids blocking the single-threaded Dioxus runtime).
Key Files
Section titled “Key Files”| File | Purpose |
|---|---|
tools/contacts/ai_create_contact_tool.rs | Reference implementation of confirmation pattern |
tools/messages/ai_send_sms_tool.rs | Confirmation with conversation context in preview |
tools/mod.rs | make_tool_execute! macro definition |
tools/runtime.rs | TOOL_RUNTIME and run_tool_async() |
components/chat_message_component.rs | ActionState and confirm/edit/cancel buttons |
components/tool_call_card_component.rs | ToolResultMeta parsing and status-colored cards |
services/tool_registry_service.rs | Permission-gated tool collection |
page_context_mapper.rs | entity_deep_link() for generating navigation links |