Skip to content

03 ยท Tool with Approval ๐ŸŸข โ€‹

Some actions are risky โ€” sending emails, deleting files, making purchases. The approval pattern pauses execution and asks a human "are you sure?" before the tool runs.

What you'll learn โ€‹

  • How to require human approval before a tool executes
  • How to implement a custom approval handler
  • When to use this pattern (irreversible or expensive actions)

Code โ€‹

ts
// approval-agent.ts
import { z } from 'zod';
import { createAgent, tool } from 'confused-ai';
import * as readline from 'node:readline/promises';

// โ”€โ”€ Approval helper โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
async function askHuman(question: string): Promise<boolean> {
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
  const answer = await rl.question(`\nโš ๏ธ  ${question}\nApprove? (yes/no): `);
  rl.close();
  return answer.toLowerCase().startsWith('y');
}

// โ”€โ”€ Tool that requires approval โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const sendEmail = tool({
  name: 'sendEmail',
  description: 'Send an email to a recipient. Always requires human approval.',
  parameters: z.object({
    to:      z.string().email().describe('Recipient email address'),
    subject: z.string().describe('Email subject line'),
    body:    z.string().describe('Email body text'),
  }),

  // needsApproval = true โ†’ pause before execute()
  needsApproval: true,

  execute: async ({ to, subject, body }) => {
    // In a real app: call your email service (SendGrid, SES, etc.)
    console.log(`\n๐Ÿ“ง Email sent to ${to}`);
    return { success: true, messageId: `msg_${Date.now()}` };
  },
});

// โ”€โ”€ Dynamic approval โ€” only ask for external domains โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const sendEmailSmart = tool({
  name: 'sendEmailSmart',
  description: 'Send email โ€” asks approval only for external addresses.',
  parameters: z.object({
    to:      z.string().email(),
    subject: z.string(),
    body:    z.string(),
  }),

  // Approval only when sending outside your company
  needsApproval: ({ to }) => !to.endsWith('@mycompany.com'),

  execute: async ({ to, subject, body }) => {
    console.log(`๐Ÿ“ง Sent to ${to}: ${subject}`);
    return { success: true };
  },
});

// โ”€โ”€ Agent โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const agent = createAgent({
  name: 'email-agent',
  model: 'gpt-4o-mini',
  instructions: 'You help draft and send emails. Always confirm before sending.',
  tools: [sendEmail],

  // Wire up the approval handler
  onToolApprovalRequired: async ({ toolName, params }) => {
    const paramsStr = JSON.stringify(params, null, 2);
    const approved = await askHuman(
      `Agent wants to call "${toolName}" with:\n${paramsStr}`
    );
    return { approved };
  },
});

// โ”€โ”€ Run โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const result = await agent.run(
  'Send a quick email to bob@example.com saying the meeting is moved to 3pm.'
);
console.log(result.text);

Terminal interaction โ€‹

โš ๏ธ  Agent wants to call "sendEmail" with:
{
  "to": "bob@example.com",
  "subject": "Meeting Rescheduled",
  "body": "Hi Bob, just a heads up that the meeting has been moved to 3pm."
}
Approve? (yes/no): yes

๐Ÿ“ง Email sent to bob@example.com
"I've sent Bob the email about the rescheduled meeting."

Skip approval in tests โ€‹

ts
// In your test suite โ€” auto-approve everything
const testAgent = createAgent({
  ...agentConfig,
  onToolApprovalRequired: async () => ({ approved: true }),
});

Auto-deny dangerous patterns โ€‹

ts
onToolApprovalRequired: async ({ toolName, params }) => {
  // Automatically block bulk operations
  if (params.recipients?.length > 10) {
    console.warn('Blocked: bulk email not allowed');
    return { approved: false, reason: 'Bulk email requires manual review' };
  }
  return askHuman(`Allow ${toolName}?`);
},

What's next? โ€‹

Released under the MIT License.