Pro

Ask before the agent acts

Articles ·

Every agent with tools hits the same moment: it's about to do something the person might not want. Send twelve texts. Delete a menu. Refund $84. The usual answer is a modal with the tool's name, a block of JSON and two buttons, which people learn to click through by the third time.

The question can be much better than that, and it should be, because it's the one place the person stays in charge. Here's a working one. Try the arguments, deny with a reason, or allow it for the rest of the conversation.

Copper moving Saturday supper to 7:30

Move Saturday to 7:30, tell everyone, and sort out Ben’s refund.

I’ve updated the booking. Three things need your OK before I go on.

Copper needs your OK

Send a message to 12 guests

To everyone booked for Saturday supper: “We’re starting at 7:30 instead of 7. Same table, 14 Alder Lane.”

Once it’s sent, it can’t be taken back.

postbox · send_message
send_message arguments
{  "to": "booking:saturday-supper",  "recipients": 12,  "from": "mara@saturdaysupper.example.com",  "subject": "Saturday supper starts at 7:30",  "body": "Hi all, we’re starting at 7:30 instead of 7. Same table, 14 Alder Lane. Reply here if that doesn’t work. Mara"}

Enter denies and sends this to Copper.

  • Refund Ben Ito $48.00Spends money
  • Add 4 items to the shopping list

Waiting for you.

Ledger takes a moment to answer and the first refund fails on purpose, so you can see the busy button and the error.

Reads and deletes

Copper needs your OK

Read Saturday’s bookings

Names, seats and dietary notes for 12 guests.
acme · get_bookings
get_bookings arguments
{  "event": "saturday-supper",  "fields": [    "name",    "seats",    "dietary"  ]}

Enter denies and sends this to Copper.

Copper needs your OK

Delete the draft menu “Autumn v2”

An older draft nobody has seen. The menu you shared with guests stays.

Deleted for good. It can’t be undone.

acme · delete_menu
delete_menu arguments
{  "menu": "autumn-v2",  "status": "draft"}

Enter denies and sends this to Copper.

Settled, from earlier in the conversation

Add 4 items to the shopping list

acme · update_list
Lamb shoulder 2.8 kg, white beans, sourdough and lemons. The total goes from $212.00 to $236.00.
acme · update_list
update_list arguments
{  "list": 212,  "add": [    {      "item": "Lamb shoulder",      "quantity": 2.8,      "unit": "kg"    },    {      "item": "White beans",      "quantity": 5,      "unit": "jar"    },    {      "item": "Sourdough",      "quantity": 3,      "unit": "loaf"    },    {      "item": "Lemons",      "quantity": 6    }  ]}

Send a message to 12 guests

postbox · send_message
To everyone booked for Saturday supper: “We’re starting at 7:30 instead of 7. Same table, 14 Alder Lane.”

Once it’s sent, it can’t be taken back.

postbox · send_message
send_message arguments
{  "to": "booking:saturday-supper",  "recipients": 12,  "from": "mara@saturdaysupper.example.com",  "subject": "Saturday supper starts at 7:30",  "body": "Hi all, we’re starting at 7:30 instead of 7. Same table, 14 Alder Lane. Reply here if that doesn’t work. Mara"}

You told Copper: “not yet, I’m still checking with Ben”

Refund Ben Ito $48.00

ledger · create_refund
Ben cancelled 3 days ahead. The policy gives a full refund for his 2 seats.

Goes back to Ben’s card. A refund can’t be reversed.

ledger · create_refund
create_refund arguments
{  "booking": "BK-2207",  "guest": "Ben Ito",  "amount": 48,  "currency": "USD",  "reason": "cancelled_72h"}

Ask about effects, not tools

Asking before every call trains people to say yes without reading. Asking never is how an agent texts your guests the wrong time. Decide by what the call does, not which tool it is:

EffectExampleAsk?
ReadsLook up Saturday's bookingsNot by default
ChangesUpdate the shopping listAsk, and offer "don't ask again"
SendsText 12 guestsAsk, every time or for this conversation
DeletesDelete a draft menuAsk, every time or for this conversation
SpendsRefund $84Ask, every time or for this conversation

The last three have one thing in common: they can't be taken back. That's the line. Whatever can be undone can earn standing permission; whatever can't shouldn't get "always" at all, only "for this conversation".

Say it in words first

Lead with the action as a sentence a person would say, then the specifics, then the consequence:

Text 12 guests about 7:30

“We're starting at 7:30 instead of 7. Same table, 14 Alder Lane.”

Once it's sent, it can't be taken back.

The server, the tool's name (postbox · send_message) and the raw arguments sit one click away under "Details", never up front: a person can't review JSON, but they can review the message their guests are about to get.

Only the calls that can't be taken back get that last line. A read or an edit says nothing extra; the title is enough. When the effect is a delete, the Allow button turns red. That's the only place color should shout.

Two answers, and a box

  • Deny. With an optional "tell Copper why", because the reason is useful to the agent: "not yet, I'm still checking with Ben" changes what it does next. Send it back as the tool's result.
  • Allow. The primary button, and it means once.
  • "Don't ask again in this chat", a checkbox above them. Standing permission is a choice about Allow, not a third answer, so it shouldn't be a third button competing with the other two. Keep it to the chat for sends, deletes and spends: one click should never turn into standing permission to move money.

Remember what was allowed somewhere the person can see and take back: a permissions list in settings with a Revoke button next to each tool.

Get out of the way afterwards

Once answered, the card has done its job. Fold it into one line in the conversation, "Allowed once · send_message", that can be reopened to see exactly what was approved. Three answered cards stacked in a thread are noise; three lines are a record.

Two more details people notice:

  • Don't steal focus. If the person is typing their next message, an approval appearing shouldn't yank the cursor away. Take focus only when nothing else has it, and announce the card politely instead.
  • Let it expire. An agent can't wait forever. When it stops waiting, the card turns into a line that says so ("Copper stopped waiting after 5 minutes"), so nobody approves something that's no longer going to happen.

When several arrive at once

Agents batch. Show the first request as the card and the rest as a short queue under it ("2 more waiting"), each with its own Deny and Allow. Never one button that approves everything you haven't read.

The Tool approval component does all of this. It sits in the same thread as Tool steps, which shows what the agent did after you said yes.