Overview

Human-in-the-loop (HITL) workflows allow you to require human approval before certain function calls are executed. This is useful for:

  • Reviewing and approving high-risk operations
  • Validating model outputs before taking action

Inferable provides a simple API for creating approval requests and approving them. It accomplishes this by returning a special result type as a result from a function call.

Implementation

To implement HITL in Inferable, use the approvalRequest() helper when you need approval within your functions:

import { Inferable, approvalRequest } from "inferable";

const client = new Inferable({
  apiSecret: process.env.INFERABLE_API_SECRET,
});

client.default.register({
  name: "deleteUser",
  description: "Permanently deletes a user account",
  schema: {
    input: z.object({
      userId: z.string(),
    }),
  },
  func: async (input, context) => {
    if (!context.approved) {
      return approvalRequest();
    }

    return db.customers.delete({
      userId: input.userId,
    });
  },
});

Approval Request result

By returning the approvalRequest() result, you are in fact returning a special result type that the execution engine can handle.

In this case, the result will pause the run and create an approval request in the run.

Approval Flow

When approvalRequest() is returned from a function:

  1. The run is paused.

  2. The approval request is created in the run.

  3. An authorized user must approve or reject the request using the API or the App UI.

  4. If the approval is successful,

    4.1 The agent will resume execution, and will call the function again with the same input.

    4.2 At this point, since the context.approved is true, the function will continue without entering the return approvalRequest() block.

    4.3 The agent will receive the result of the function call and continue execution.

  5. If the approval is rejected

    5.1 The function will be rejected, and the agent will be informed that the approval was rejected.

    5.2 The function can not continue since context.approved is false.

Pausing a Run

When a run is paused, the agent will not continue to the next function. Inferable’s durable execution engine will save the state of the run and resume it when the approval is resolved.

Since humans can take a long time to approve, the run can be paused for an indefinite amount of time. There’s no theoretical upper limit on how long a run can be paused for.

Modeling the Approval Block

It’s important to reiterate that the function may be called multiple times with the same input, if the request is approved.

Anything that sits before the if (context.approved) { ... } block will be executed at least twice.

Example:

async (input, context) => {
  console.log(`Request to delete user ${input.userId}`);

  if (!context.approved) {
    return approvalRequest();
  }

  // unless context.approved is true, the function can not continue to this point

  console.log(`Deleting user ${input.userId}`);

  return db.customers.delete({
    userId: input.userId,
  });
},

// This will result in:
// Request to delete user 123
// Request to delete user 123
// Deleting user 123