Confirmations
Confirmations are the chat flow that gates every write and destructive action: the user sees exactly what runs and the held call fires byte-identical on yes.
When a destructive (or chain) action would run, the web-kernel intercepts it, shows the user a confirmation card, and only executes after the user clicks "yes". This page explains the flow, the federal guarantees, and how your extension participates.
The user-facing flow
You: clear my done tasks
Webbee: "This will delete 3 completed tasks. Confirm?" — [✅ Yes] [✗ Cancel]
You: (clicks ✅)
Webbee: ✓ Cleared 3 completed task(s).
What happened end to end:
The platform picks tasks-mini.clear_done_tasks from the user's intent.
The platform sees action_type="destructive" on your tool, holds the call, and shows a ConfirmationCard in chat.
The Imperal Panel renders the card with [Yes] and [Cancel] buttons.
The user clicks Yes. The platform verifies the request came from the same user who triggered it.
The held call is then run with byte-identical arguments — exactly what the card showed. There is no re-planning and no second model run.
The action is recorded in the audit trail as user-accepted, retention federal_7y.
What you do
Just declare action_type="destructive":
@chat.function(
"delete_folder",
description="Permanently delete a folder and all its contents.",
action_type="destructive",
id_projection="folder_id",
)
async def delete_folder(ctx, params: DeleteFolderParams):
await ctx.http.delete(f"/folders/{params.folder_id}")
return {"text": "Folder deleted."}That's it. The web-kernel handles the rest.
What the user sees
The confirmation card includes:
| Element | Source |
|---|---|
| Title ("Delete folder?") | i18n template by action_type |
| Description | Your tool's description (so write it well) |
| Function call entries | Each intercepted call rendered: tool name + key params |
| Action buttons | [Yes, do it] / [Cancel] |
Example for a chain (multiple destructive calls in one user turn):
Confirm 3 actions:
1. delete_note(note_id="note_abc")
2. delete_note(note_id="note_def")
3. archive_thread(thread_id="thr_123")
[✅ Confirm all] [✗ Cancel]This means: all three run with those exact args, in that order, on accept. No re-planning, no model rerun.
The guarantees you can rely on
What you saw is what runs
The actions on the card run with byte-identical arguments — nothing is re-decided after you click Yes.
Acceptance carries no new data
Clicking Yes cannot inject new arguments. The actions are exactly the ones the card showed.
Same user, verified
An acceptance is only honored when it comes from the same user who triggered the action.
No zombie acceptances
Confirmations expire alongside the chat session. An old, stale card cannot be accepted later.
Consistent across plan shapes
Your confirmation preferences apply uniformly whether the action is a single step or part of a multi-step chain.
Multi-step chains and confirmations
When a user says "check my unread mail and delete the spam":
- The platform plans a 2-step sequence:
[mail.list_unread → mail.delete_spam] - Step 1 is read-only and runs immediately
- Step 2 is destructive — the platform pauses and shows a card with the actual count to be deleted
- The user accepts → step 2 runs with those exact, byte-identical arguments
The card shows real numbers ("Delete 7 spam threads?"), not placeholders ("Delete some threads?"). This is part of the Pre-Authorized Action Execution flow.
What you should NOT do
Panel button confirmation (a UI concern, not an SDK Call flag)
Confirmation gating in chat is driven by your tool's action_type ("write"/"destructive"), not by any flag on ui.Call. ui.Call(function, **params) only takes a function name plus call params — there is no confirm= argument; if you pass one, it is folded into the params, not treated as a confirmation directive.
For a panel button that should pop a quick "are you sure?" before firing, the confirmation lives on the panel action-wrapper dict as a "confirm" key (a Panel renderer prop), while the call itself stays a plain ui.Call:
from imperal_sdk import ui
@ext.panel("items", slot="left", title="Items")
async def items_sidebar(ctx, **kwargs):
items = await ctx.store.query("items", limit=50)
return ui.List(items=[
ui.ListItem(
id=str(item["id"]),
title=item["name"],
actions=[{
"label": "Move to trash",
"icon": "Trash2",
"on_click": ui.Call("archive_item", item_id=item["id"]),
"confirm": "Move this item to trash?", # Panel renderer prop
}],
)
for item in items.data
])The "confirm" string is rendered by the Panel host as a confirm prompt on the button. It is independent of the chat confirmation flow above, which is decided entirely by action_type on the @chat.function.
Where to next
BYOLLM (Bring Your Own LLM)
BYOLLM lets users bring their own LLM provider and call it from your extension via ctx.ai, with credentials handled by the platform and never seen by your code.
Chains
Chains sequence multiple tools from one user message, dependency-ordered, threading each step's output into the next and rendering one combined Webbee response.