[{"data":1,"prerenderedAt":507},["ShallowReactive",2],{"navigation_docs":3,"-tools-confirmations":139,"-tools-confirmations-surround":502},[4,8,12,16,20,24,28,32,59,63,117,121,135],{"title":5,"path":6,"stem":7},"Introduction","\u002Fintroduction","1.introduction",{"title":9,"path":10,"stem":11},"Memory","\u002Fmemory","10.memory",{"title":13,"path":14,"stem":15},"Customization","\u002Fcustomization","11.customization",{"title":17,"path":18,"stem":19},"Deployment","\u002Fdeployment","12.deployment",{"title":21,"path":22,"stem":23},"Reference","\u002Freference","13.reference",{"title":25,"path":26,"stem":27},"Quickstart","\u002Fquickstart","2.quickstart",{"title":29,"path":30,"stem":31},"Installation","\u002Finstallation","3.installation",{"title":33,"icon":34,"path":35,"stem":36,"children":37,"page":58},"Connectors","i-lucide-cable","\u002Fconnectors","4.connectors",[38,42,46,50,54],{"title":39,"path":40,"stem":41},"Telegram","\u002Fconnectors\u002Ftelegram","4.connectors\u002F1.telegram",{"title":43,"path":44,"stem":45},"Slack","\u002Fconnectors\u002Fslack","4.connectors\u002F2.slack",{"title":47,"path":48,"stem":49},"Email","\u002Fconnectors\u002Femail","4.connectors\u002F3.email",{"title":51,"path":52,"stem":53},"API","\u002Fconnectors\u002Fapi","4.connectors\u002F4.api",{"title":55,"path":56,"stem":57},"Terminal","\u002Fconnectors\u002Fterminal","4.connectors\u002F5.terminal",false,{"title":60,"path":61,"stem":62},"Attachments","\u002Fattachments","5.attachments",{"title":64,"icon":65,"path":66,"stem":67,"children":68,"page":58},"Tools","i-lucide-wrench","\u002Ftools","6.tools",[69,73,77,81,85,89,93,97,101,105,109,113],{"title":70,"path":71,"stem":72},"File Manager","\u002Ftools\u002Ffile-manager","6.tools\u002F1.file-manager",{"title":74,"path":75,"stem":76},"Text-to-Speech","\u002Ftools\u002Ftext-to-speech","6.tools\u002F10.text-to-speech",{"title":78,"path":79,"stem":80},"Confirmations","\u002Ftools\u002Fconfirmations","6.tools\u002F11.confirmations",{"title":82,"path":83,"stem":84},"Adding Custom Tools","\u002Ftools\u002Fadding-custom-tools","6.tools\u002F12.adding-custom-tools",{"title":86,"path":87,"stem":88},"Image Manager","\u002Ftools\u002Fimage-manager","6.tools\u002F2.image-manager",{"title":90,"path":91,"stem":92},"Web Request","\u002Ftools\u002Fweb-request","6.tools\u002F3.web-request",{"title":94,"path":95,"stem":96},"Reminders","\u002Ftools\u002Freminders","6.tools\u002F4.reminders",{"title":98,"path":99,"stem":100},"Heartbeats","\u002Ftools\u002Fheartbeats","6.tools\u002F5.heartbeats",{"title":102,"path":103,"stem":104},"Email Manager","\u002Ftools\u002Femail-manager","6.tools\u002F6.email-manager",{"title":106,"path":107,"stem":108},"Calendar","\u002Ftools\u002Fcalendar","6.tools\u002F7.calendar",{"title":110,"path":111,"stem":112},"Read Database","\u002Ftools\u002Fread-database","6.tools\u002F8.read-database",{"title":114,"path":115,"stem":116},"Headless Browser","\u002Ftools\u002Fbrowser","6.tools\u002F9.browser",{"title":118,"path":119,"stem":120},"Tinker","\u002Ftinker","7.tinker",{"title":122,"icon":123,"path":124,"stem":125,"children":126,"page":58},"Skills","i-lucide-sparkles","\u002Fskills","8.skills",[127,131],{"title":128,"path":129,"stem":130},"Overview","\u002Fskills\u002Foverview","8.skills\u002F1.overview",{"title":132,"path":133,"stem":134},"Adding Custom Skills","\u002Fskills\u002Fadding-custom-skills","8.skills\u002F2.adding-custom-skills",{"title":136,"path":137,"stem":138},"Personas","\u002Fpersonas","9.personas",{"id":140,"title":78,"body":141,"description":495,"extension":496,"links":497,"meta":498,"navigation":499,"path":79,"seo":500,"stem":80,"__hash__":501},"docs\u002F6.tools\u002F11.confirmations.md",{"type":142,"value":143,"toc":486},"minimark",[144,148,152,155,159,167,219,222,226,237,284,291,295,298,304,311,315,322,386,393,397,445,449,452,482],[145,146,5],"h2",{"id":147},"introduction",[149,150,151],"p",{},"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.",[149,153,154],{},"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.",[145,156,158],{"id":157},"which-connectors-support-it","Which Connectors Support It",[149,160,161,162,166],{},"A connector opts in by implementing ",[163,164,165],"code",{},"Laraclaw\\Connectors\\Contracts\\SupportsConfirmation",". The package ships three connectors that do:",[168,169,170,183],"table",{},[171,172,173],"thead",{},[174,175,176,180],"tr",{},[177,178,179],"th",{},"Connector",[177,181,182],{},"Supports confirmations",[184,185,186,194,200,206,213],"tbody",{},[174,187,188,191],{},[189,190,39],"td",{},[189,192,193],{},"Yes",[174,195,196,198],{},[189,197,43],{},[189,199,193],{},[174,201,202,204],{},[189,203,47],{},[189,205,193],{},[174,207,208,210],{},[189,209,51],{},[189,211,212],{},"No",[174,214,215,217],{},[189,216,55],{},[189,218,212],{},[149,220,221],{},"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.",[145,223,225],{"id":224},"how-a-tool-opts-in","How a Tool Opts In",[149,227,228,229,232,233,236],{},"Tools that extend ",[163,230,231],{},"BaseTool"," declare their confirmation prompts in a ",[163,234,235],{},"$requiresApproval"," map. Keys are operation names, values are the prompt template:",[238,239,244],"pre",{"className":240,"code":241,"language":242,"meta":243,"style":243},"language-php shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","class FileManager extends BaseTool\n{\n    protected array $requiresApproval = [\n        'delete' => 'Delete the file at {path} on disk {disk}?',\n    ];\n}\n","php","",[163,245,246,254,260,266,272,278],{"__ignoreMap":243},[247,248,251],"span",{"class":249,"line":250},"line",1,[247,252,253],{},"class FileManager extends BaseTool\n",[247,255,257],{"class":249,"line":256},2,[247,258,259],{},"{\n",[247,261,263],{"class":249,"line":262},3,[247,264,265],{},"    protected array $requiresApproval = [\n",[247,267,269],{"class":249,"line":268},4,[247,270,271],{},"        'delete' => 'Delete the file at {path} on disk {disk}?',\n",[247,273,275],{"class":249,"line":274},5,[247,276,277],{},"    ];\n",[247,279,281],{"class":249,"line":280},6,[247,282,283],{},"}\n",[149,285,286,287,290],{},"The placeholders in curly braces are replaced with the matching parameter from the tool's request before the prompt is sent. Before each operation, ",[163,288,289],{},"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.",[145,292,294],{"id":293},"what-the-user-sees","What the User Sees",[149,296,297],{},"The connector posts the prompt back to the same conversation, prefixed with a warning emoji:",[299,300,301],"blockquote",{},[149,302,303],{},"⚠️ Delete the file at reports\u002Fold.csv on disk local? Reply 'Yes' to confirm.",[149,305,306,307,310],{},"The user has 120 seconds (the default timeout) to reply. The reply must equal ",[163,308,309],{},"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.",[145,312,314],{"id":313},"how-it-works-under-the-hood","How It Works Under the Hood",[149,316,317,318,321],{},"The flow is implemented by the ",[163,319,320],{},"ChecksRedisForConfirmations"," trait, which every supporting connector uses:",[323,324,325,333,340,343,358,365,379],"ol",{},[326,327,328,329,332],"li",{},"The tool calls ",[163,330,331],{},"$connector->askForConfirmation($message, $prompt)",".",[326,334,335,336,339],{},"The trait sets a Redis key ",[163,337,338],{},"awaiting_confirm:{connector}:{thread_key}"," with the timeout as TTL.",[326,341,342],{},"It posts the prompt back to the user through the connector.",[326,344,345,346,349,350,353,354,357],{},"It opens a ",[163,347,348],{},"BLPOP"," against ",[163,351,352],{},"confirm:{connector}:{thread_key}"," on a dedicated Redis connection (registered with ",[163,355,356],{},"read_write_timeout = -1"," so the socket never closes mid-wait).",[326,359,360,361,364],{},"When the user's reply arrives at the webhook, the connector's ",[163,362,363],{},"intercept()"," method sees the awaiting key in Redis, pushes the reply text into the confirm key, and short-circuits normal processing.",[326,366,367,368,371,372,374,375,378],{},"The blocked tool wakes up, reads the reply, returns ",[163,369,370],{},"true"," if it was ",[163,373,309],{},", ",[163,376,377],{},"false"," otherwise.",[326,380,381,382,385],{},"The ",[163,383,384],{},"finally"," block deletes both Redis keys regardless of how the wait ended.",[149,387,388,389,392],{},"The dedicated ",[163,390,391],{},"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.",[145,394,396],{"id":395},"edge-cases","Edge Cases",[398,399,400,410,420,426,432],"ul",{},[326,401,402,406,407,409],{},[403,404,405],"strong",{},"Timeout."," If 120 seconds pass with no reply, both Redis keys are deleted and the tool gets ",[163,408,377],{},". The user can still reply later, but the message will be processed as a normal message, not a confirmation.",[326,411,412,415,416,419],{},[403,413,414],{},"Wrong connector replies."," Confirmation keys are scoped by ",[163,417,418],{},"(connector, thread_key)",". A \"yes\" sent in Slack cannot satisfy a pending Telegram confirmation.",[326,421,422,425],{},[403,423,424],{},"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.",[326,427,428,431],{},[403,429,430],{},"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.",[326,433,434,437,438,440,441,444],{},[403,435,436],{},"Multiple confirmations stacked up."," Only one confirmation can be pending per ",[163,439,418],{}," at a time. A second ",[163,442,443],{},"askForConfirmation()"," call overwrites the first awaiting key.",[145,446,448],{"id":447},"tool-authors-choosing-what-to-confirm","Tool Authors: Choosing What to Confirm",[149,450,451],{},"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.",[238,453,455],{"className":240,"code":454,"language":242,"meta":243,"style":243},"protected array $requiresApproval = [\n    'delete'        => 'Delete {path}?',\n    'send'          => 'Send this email to {to}?',\n    'delete_folder' => 'Delete the folder {folder} and everything in it?',\n];\n",[163,456,457,462,467,472,477],{"__ignoreMap":243},[247,458,459],{"class":249,"line":250},[247,460,461],{},"protected array $requiresApproval = [\n",[247,463,464],{"class":249,"line":256},[247,465,466],{},"    'delete'        => 'Delete {path}?',\n",[247,468,469],{"class":249,"line":262},[247,470,471],{},"    'send'          => 'Send this email to {to}?',\n",[247,473,474],{"class":249,"line":268},[247,475,476],{},"    'delete_folder' => 'Delete the folder {folder} and everything in it?',\n",[247,478,479],{"class":249,"line":274},[247,480,481],{},"];\n",[483,484,485],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":243,"searchDepth":256,"depth":256,"links":487},[488,489,490,491,492,493,494],{"id":147,"depth":256,"text":5},{"id":157,"depth":256,"text":158},{"id":224,"depth":256,"text":225},{"id":293,"depth":256,"text":294},{"id":313,"depth":256,"text":314},{"id":395,"depth":256,"text":396},{"id":447,"depth":256,"text":448},"How tools ask the user to confirm a destructive action before running it.","md",null,{},true,{"title":78,"description":495},"5s2bpAqBwd6OB-U1ELPrvUvrOYdWiA05_qs687YOhqQ",[503,505],{"title":74,"path":75,"stem":76,"description":504,"children":-1},"Reply with synthesized audio instead of (or in addition to) text.",{"title":82,"path":83,"stem":84,"description":506,"children":-1},"Build your own tools and register them with the agent.",1786661906119]