Tools

Confirmations

How tools ask the user to confirm a destructive action before running it.

Introduction

Some tool operations should not run silently. Deleting a file, deleting an email, dropping a calendar event — the agent calling these unprompted is the kind of mistake that ends in tears. Laraclaw ships a confirmation flow that any tool can opt into: the tool asks, the connector relays the prompt to the user, the user replies "yes", and the tool runs.

This is not a separate tool you call. It's a contract a tool declares against an operation, and a Redis-backed wait-for-reply loop the connector implements.

Which Connectors Support It

A connector opts in by implementing Laraclaw\Connectors\Contracts\SupportsConfirmation. The package ships three connectors that do:

ConnectorSupports confirmations
TelegramYes
SlackYes
EmailYes
APINo
TerminalNo

If a tool tries to ask for confirmation on a connector that does not implement the contract, the tool simply runs the operation without asking. The contract is the gate.

How a Tool Opts In

Tools that extend BaseTool declare their confirmation prompts in a $requiresApproval map. Keys are operation names, values are the prompt template:

class FileManager extends BaseTool
{
    protected array $requiresApproval = [
        'delete' => 'Delete the file at {path} on disk {disk}?',
    ];
}

The placeholders in curly braces are replaced with the matching parameter from the tool's request before the prompt is sent. Before each operation, BaseTool::confirmOperation() checks the map; if the operation is in there, it asks the connector to run the confirmation flow before dispatching the actual handler.

What the User Sees

The connector posts the prompt back to the same conversation, prefixed with a warning emoji:

⚠️ Delete the file at reports/old.csv on disk local? Reply 'Yes' to confirm.

The user has 120 seconds (the default timeout) to reply. The reply must equal yes (case-insensitive, first non-empty line of the message — so an email reply with quoted history below still matches). Any other reply, or a timeout, counts as a no.

How It Works Under the Hood

The flow is implemented by the ChecksRedisForConfirmations trait, which every supporting connector uses:

  1. The tool calls $connector->askForConfirmation($message, $prompt).
  2. The trait sets a Redis key awaiting_confirm:{connector}:{thread_key} with the timeout as TTL.
  3. It posts the prompt back to the user through the connector.
  4. It opens a BLPOP against confirm:{connector}:{thread_key} on a dedicated Redis connection (registered with read_write_timeout = -1 so the socket never closes mid-wait).
  5. When the user's reply arrives at the webhook, the connector's intercept() method sees the awaiting key in Redis, pushes the reply text into the confirm key, and short-circuits normal processing.
  6. The blocked tool wakes up, reads the reply, returns true if it was yes, false otherwise.
  7. The finally block deletes both Redis keys regardless of how the wait ended.

The dedicated laraclaw-blocking Redis connection is registered automatically by the service provider. There is nothing to configure on your end — but Redis must be running, even if your queue driver is something else.

Edge Cases

  • Timeout. If 120 seconds pass with no reply, both Redis keys are deleted and the tool gets false. The user can still reply later, but the message will be processed as a normal message, not a confirmation.
  • Wrong connector replies. Confirmation keys are scoped by (connector, thread_key). A "yes" sent in Slack cannot satisfy a pending Telegram confirmation.
  • The queue worker dies mid-wait. Both Redis keys have a TTL equal to the timeout, so they expire on their own. No leaks. The tool's job will be retried by the queue if it was retriable.
  • The user replies with a file and no caption. The message is consumed (so it does not also reach the agent), but the wait stays open. Effectively a silent no.
  • Multiple confirmations stacked up. Only one confirmation can be pending per (connector, thread_key) at a time. A second askForConfirmation() call overwrites the first awaiting key.

Tool Authors: Choosing What to Confirm

Confirm operations that are destructive, irreversible, or expensive. Don't confirm read-only operations. Don't confirm small operations to be safe — the friction trains the user to type "yes" without reading. Save it for things where a wrong call would actually hurt: deletes, sends, anything that touches money or external systems.

protected array $requiresApproval = [
    'delete'        => 'Delete {path}?',
    'send'          => 'Send this email to {to}?',
    'delete_folder' => 'Delete the folder {folder} and everything in it?',
];
Copyright © 2026