Human Approval for Tools

medium10 minapprovals.ts

Small refunds can go through automatically, but anything over $100 needs a person to approve it — without blocking a server thread while they decide.

What it shows

  • Mark a tool with requiresApproval (a boolean or a function of the arguments)
  • Get status: 'paused' and pendingApprovals from the run
  • Continue later with cog.resume() — approve per call or decline all

The code

approvals.ts
import { Agent, Cogitator, tool } from '@cogitator-ai/core';
import { z } from 'zod';

const apiKey = process.env.GOOGLE_API_KEY;
if (!apiKey) throw new Error('Set GOOGLE_API_KEY');

const orders: Record<string, { total: number; refunded: number }> = {
  'A-1001': { total: 340, refunded: 0 },
  'A-1002': { total: 25, refunded: 0 },
};

const refund = tool({
  name: 'refund_order',
  description: 'Refund part or all of an order to the customer.',
  parameters: z.object({
    order: z.string().describe('Order id, e.g. "A-1001"'),
    amount: z.number().positive().describe('Amount in dollars'),
  }),
  requiresApproval: ({ amount }) => amount > 100,
  sideEffects: ['external'],
  execute: async ({ order, amount }) => {
    const record = orders[order];
    if (!record) throw new Error(`No order ${order}`);
    record.refunded += amount;
    return { order, refunded: amount, totalRefunded: record.refunded };
  },
});

const cog = new Cogitator({
  llm: { providers: { google: { apiKey } } },
  memory: { adapter: 'memory' },
});

const support = new Agent({
  name: 'support',
  model: 'google/gemini-3.5-flash-lite',
  instructions: 'You are a support agent. Refund orders with refund_order when asked. Answer briefly.',
  tools: [refund],
  temperature: 0.2,
});

const threadId = 'ticket-7731';

const paused = await cog.run(support, {
  input: 'Order A-1001 arrived broken, please refund all $340.',
  threadId,
});
console.log(paused.status);
for (const call of paused.pendingApprovals ?? []) {
  console.log(`waiting: ${call.toolName}(${JSON.stringify(call.arguments)})`);
}

if (paused.status === 'paused') {
  const approved = await cog.resume(support, threadId, {
    decisions: Object.fromEntries(
      (paused.pendingApprovals ?? []).map((call) => [call.toolCallId, { approved: true as const }])
    ),
  });
  console.log(approved.status, approved.output);
}

const declinedRun = await cog.run(support, {
  input: 'Also refund $200 of order A-1001 for shipping.',
  threadId,
});
if (declinedRun.status === 'paused') {
  const declined = await cog.resume(support, threadId, {
    defaultDecision: { approved: false, reason: 'refunds over $100 need photos of the damage' },
  });
  console.log(declined.output);
}

await cog.close();

Run it

install · bash
pnpm add @cogitator-ai/core zod
EnvironmentGOOGLE_API_KEY
run · bash
GOOGLE_API_KEY=your-key npx tsx approvals.ts

Or run the full example from a clone of the repository (keys in .env):

repository · bash
npx tsx examples/core/14-approvals.ts

Learn more