Skip to main content

AI agent pauses for World ID approval before booking a flight

Human-in-the-loop lets an AI agent pause mid-execution and wait for a real, verified human to approve an action before continuing. Every approval is cryptographically bound to the action via World ID — no bots, no spoofing, no replay. Built on the Workflow SDK and the Vercel AI SDK.

Install

# Server — human-in-the-loop + peer dependencies
npm install @worldcoin/human-in-the-loop ai workflow

# Client — React bindings + peer dependencies
npm install @worldcoin/human-in-the-loop-react @worldcoin/idkit ai react

Environment variables

# Server — used by @worldcoin/human-in-the-loop
WORLD_RP_ID=your_rp_id
WORLD_SIGNING_KEY=your_signing_key

# Client — used by the <HumanApproval> component (optional if passing appId prop)
NEXT_PUBLIC_WORLD_APP_ID=app_...
Get these from the World developer portal by creating an app.

Step 1: Define the workflow

// src/workflows/chat/index.ts
import { DurableAgent } from 'workflow/ai'
import { getWritable } from 'workflow'
import { openai } from '@workflow/ai/openai'
import { tools } from './steps/tools'

export async function chatWorkflow(messages: ModelMessage[]) {
  // Durable workflow — can pause for hours/days and resume where it left off
  'use workflow'

  const writable = getWritable<UIMessageChunk>()
  const agent = new DurableAgent({
    model: openai('gpt-5.4'),
    tools,
    system:
      'You are a helpful assistant. Before performing any sensitive action, use the approveAction tool.',
  })

  await agent.stream({ messages, writable })
}

Step 2: Register the approval tool

// src/workflows/chat/steps/tools.ts
import { requestHumanAuthorization } from '@worldcoin/human-in-the-loop/workflows'
import { z } from 'zod'

export const tools = {
  approveAction: {
    description: 'Request human approval via World ID before a sensitive action.',
    inputSchema: z.object({ summary: z.string() }),

    // Pauses the workflow, streams approval context to the client,
    // waits for World ID proof, verifies it, then resumes.
    // Action defaults to toolCallId; pass a function to bind to input fields:
    //   action: ({ input }) => `booking:${input.flightNumber}`
    execute: requestHumanAuthorization(),
  },
  // ...your other tools
}

Step 3: Render the approval on the client

This example uses the <HumanApproval> component, if you want to customize the UI you can use the useHumanApproval hook instead.
import { HumanApproval } from '@worldcoin/human-in-the-loop-react'

// Match on the tool name from Step 2. <HumanApproval> renders the World ID
// widget and POSTs the proof back to the server automatically.
{message.parts.map(part => {
  if (part.type === 'tool-approveAction' && 'toolCallId' in part) {
    return (
      <HumanApproval
        key={part.toolCallId}
        message={message}
        part={part}
      />
    )
  }
  // ...your other part renderers
})}

Flight booking example

Check out Flight booking example for a complete implementation of a human-in-the-loop workflow with World ID approval.