Skip to content

Confirmations and forms ​

An agent's call can stop and wait for the person: for their confirmation before something risky, or for the fields it left out.

Copilot
Person:
Delete my old meetup draft.
Waiting for your confirmation

Delete this post? This cannot be undone.

Post
Spring meetup
Status
draft
DeclineConfirm
After Confirm
Removeddone
After Decline
Declineddeclined
DeletePost's card in the chat, and the row each answer leaves.

Confirm a risky call ​

A Destructive or External action, such as deleting a post or sending an email, reaches an agent only when its #[Expose] names a toolset. When the agent calls it, the call pauses, and the server builds a card from the action's own approvalReason() and approvalSummary(). Nothing has run yet. See Offer the action.

  1. The agent calls DeletePostDestructive, offered through a toolset
  2. authorize()allows the call before any card
    denied: refused
  3. The card waitsbuilt on the server from the actionhandle() has not run
  4. The person confirmsin their own session, once
    declined: nothing runs
  5. The card is built againit must read the same
    changed: nothing runs
  6. handle()runs as the person, in their tenant
The call runs only after the person confirms it, and only if the card still reads the same.

The card ​

The browser receives the sentence and the rows as plain text, never the model's arguments or words. The person answers from their own session, once, within approvals.ttl seconds (30 minutes by default). A decline runs nothing, and neither does a second press, a replay or another person.

The rows come from the record the input names, found the way authorize() finds it:

app/Actions/DeletePost.php
php
public function approvalSummary(
    ActionContext $context,
    ValidatedInput $input,
): array {
    $post = $context->find(Post::class, $input->integer('post'));

    return [__('Post') => $post->title, __('Status') => $post->status];
}

See What the person can rely on and Security.

Ask for what is missing ​

A Read or Write action can ask instead of refusing a call that left something out. The form is built on the server from the action's schema and its optional ask(), in the shape of MCP's form elicitation, so an MCP client that shows forms gets the same one. Only a submit runs the action, with every check again.

  1. draft-post, title onlythe model leaves fields out
  2. The form waitsMCP's form-elicitation shape
  3. The person submitschecked against your rules
    declined: nothing runs
  4. handle()with the person's values
Copilot
Asked by Laravel

A few details for your post.

Title (required)Launch notes
Body (required)What shipped this week, and what comes next.
Status (required)Draft
SubmitDeclineNot now
handle() receives
title
Launch notes
body
What shipped this week…
status
draft
The model reads

The person filled in: title, body, status. Done.

The model gives a title; the person fills in the rest, and the model learns only which fields they filled.
app/Actions/DraftPost.php
php
protected bool $askForMissing = true;

See What the form holds for which schema types become which fields, and Asking over MCP.

The model never reads the values ​

The action runs with the person's values over the model's arguments. The values are not stored in the conversation either, so they reach a model only if your action returns them. A form never asks for a password, a token or a card number: such a call is refused. See Never ask for secrets.

Destructive and External actions never ask ​

$askForMissing changes nothing on them. An incomplete call is refused, naming the fields, and a complete one gets the card. The person confirms a deletion or a send only there. See Asking the person and Testing confirmations.

Released under the MIT License.