Human in the Loop

Add human intervention points to your background processes

Overview

Human in the Loop allows background processes to pause and request human intervention at critical decision points. This is particularly useful for processes that require human approval, input, or decision-making during execution.

A user will see a process requires action in their assistant dashboard, and can approve or deny an action, as well as provide a response to the process.

Requesting Human Action

To request human intervention:

const stepId = await app.processes!.requestHumanAction(processId, {
  message: "Need approval for next step",
  ui: new CardUIBuilder()
    .title("Action Required")
    .content("Please approve this action")
    .build(),
  actions: [
    {
      id: "approve",
      title: "Approve",
      requiresResponse: false
    },
    {
      id: "reject",
      title: "Reject",
      requiresResponse: true  // Requires explanation
    }
  ],
  timeoutMs: 30 * 1000  // 30 second timeout
});

Waiting for Response

After requesting action, wait for human response:

const response = await app.processes!.waitForHumanAction(
  processId,
  stepId,
  30 * 1000  // Timeout
);

// Handle response
if (response.actionId === "approve") {
  // Continue process
} else if (response.actionId === "reject") {
  // Handle rejection with response.responseText
}

Example: Phone Call Agent

Here's a real example from a phone call service that uses human intervention when the AI agent needs help:

const askForHelp = {
  description: "Ask the person you are calling on behalf of for help",
  parameters: z.object({
    question: z.string().describe("The question you need help with")
  }),
  execute: async ({ question }) => {
    // Log the help request
    callSummary.steps.push({
      speaker: "ask_for_help",
      text: question,
      timestamp: new Date().toISOString()
    });

    try {
      // Request human intervention
      const stepId = await processHandler.requestHumanAction(processId, {
        message: question,
        ui: new CardUIBuilder()
          .title("Help requested")
          .content(`I need help with: ${question}`)
          .build(),
        actions: [
          {
            id: "respond-to-help",
            title: "Respond to question",
            requiresResponse: true
          },
          {
            id: "end-call",
            title: "Just hang up the call",
            requiresResponse: false
          }
        ],
        timeoutMs: 30 * 1000
      });

      // Wait for response
      const response = await processHandler.waitForHumanAction(
        processId,
        stepId,
        30 * 1000
      );

      // Handle responses
      if (response.actionId === "end-call") {
        await hangUp("Call ended by recipient due to human request");
        return "Call ended by recipient due to human request";
      }

      if (response.actionId === "respond-to-help") {
        // Log human response
        callSummary.steps.push({
          speaker: "client",
          text: response.responseText || "No response from client",
          timestamp: new Date().toISOString()
        });

        return "The user has responded: " + response.responseText;
      }

    } catch (error) {
      return "The human could not be reached. Please continue or hang up.";
    }
  }
};

Action Configuration

When requesting human action, you can configure:

interface HumanAction {
  id: string;           // Unique identifier
  title: string;        // Button text
  requiresResponse: boolean;  // Whether text input is required
}

interface HumanActionRequest {
  message: string;      // Main message
  ui?: UIComponent;     // Optional UI
  actions: HumanAction[];  // Available actions
  timeoutMs: number;    // Timeout in milliseconds
}

Use Cases

Human in the Loop is ideal for:

  • Approval workflows
  • Decision points requiring judgment
  • Complex situations needing human insight
  • Quality control checkpoints
  • Exception handling
  • Sensitive operations
  • Customer service escalations

Error Handling

Always implement timeout handling and fallbacks:

try {
  const response = await app.processes!.waitForHumanAction(
    processId,
    stepId,
    timeout
  );
  // Handle response
} catch (error) {
  if (error.code === 'TIMEOUT') {
    // Handle timeout
  }
  // Handle other errors
}