Human-in-the-Loop Review

Require a person to approve a task's output before the next task consumes it

Require a person to sign off on a task’s output before the next task can read it.

Diagram

Human Review

yes

no

🤖 Task runs

🧑 Approve output?

🤖 Next task

🔁 Re-run with reason

Quick Start

  1. Mark a task for review

    Set humanInput: true on the task, then call reviewTaskOutput after the task produces its output.

    typescript
    import { Task, reviewTaskOutput } from 'praisonai';
    
    const task = new Task({
      description: 'Draft a customer refund reply',
      expected_output: 'A one-paragraph reply',
      humanInput: true,
      humanReviewPrompt: 'Is this legally safe to send?',
    });
    
    const output = '...draft reply...';
    
    const outcome = await reviewTaskOutput(task, output);
    if (!outcome.approved) {
      // Re-run with outcome.reason folded back into the prompt
    }
  2. Plug in your own approver

    Pass an approvalManager to route the review through Slack, a webhook, or any UI instead of the built-in terminal prompt.

    typescript
    import { Task, reviewTaskOutput } from 'praisonai';
    
    const task = new Task({
      description: 'Draft a customer refund reply',
      expected_output: 'A one-paragraph reply',
      humanInput: true,
    });
    
    const output = '...draft reply...';
    
    const outcome = await reviewTaskOutput(task, output, {
      approvalManager: {
        async requestApproval({ toolName, input, reason }) {
          return await askHumanAndReturnTrueOrFalse(reason, input);
        },
      },
      timeout: 300, // seconds — forwarded to the approval manager
    });

How It Works

reviewTaskOutput asks the approval manager to approve the complete output, then returns a verdict.

Diagram
HumanApproval ManagerreviewTaskOutputOrchestratorHumanApproval ManagerreviewTaskOutputOrchestratorapproved → next taskrejected → re-run with reasontask + outputrequestApproval(complete output)“Approve the output?”yes / noboolean{ approved, reason? }
RuleBehaviour
Opt-inWhen humanInput is falsy, reviewTaskOutput returns { approved: true } and never calls the manager. Callers need no if of their own.
No silent skipWith humanInput: true and no manager available, the function throws — a task that was supposed to be signed off and simply was not is the exact failure this prevents.
Complete outputThe reviewer always sees the full output, never a prefix, so no unreviewed trailing content reaches the next task.
Strict approvalOnly a literal true counts as approval. A truthy non-boolean (e.g. { approved: false }) is treated as a rejection.
Rejection reasonA rejection returns reason: "a reviewer rejected this output" so you can feed it back into a re-run.
Custom prompthumanReviewPrompt is passed to the manager as reason; otherwise a default question is built from the task name.
Audit labelThe toolName sent to the manager is task_output:<task.name ?? 'task'>, so approval rules and audit trails tell task-output reviews apart from tool-call reviews.

Approval vs. Human Review vs. Guardrails

Three features gate agent work at different points — pick by what you are protecting.

The approval manager gates a TOOL CALL and guardrails validate automatically; neither let an orchestrator require a person to approve an output before the next task consumes it.

Diagram

A tool call

A task’s output, by a person

A task’s output, automatically

What do you gate?

Approval

Human Review

Guardrails

FeatureGatesWho decides
ApprovalA tool call before it runsA person
Human ReviewA task’s output before the next task reads itA person
GuardrailsA task’s outputAutomatic validation

Task Options

Two Task fields turn on review.

OptionTypeDefaultDescription
humanInputbooleanfalseRequire a person to approve this task’s output before the next task consumes it.
humanReviewPromptstringundefinedCustom question the reviewer is asked. Falls back to Approve the output of task <name>?.

reviewTaskOutput Reference

`reviewTaskOutput(task, output, options?) → Promise

`

ParameterTypeDescription
taskReviewableTask{ name?, humanInput?, humanReviewPrompt? } — usually your Task.
outputunknownThe complete output to review.
options.approvalManagerReviewApprovalManagerOverride the manager. Defaults to the global one from getApprovalManager().
options.timeoutnumberSeconds to wait, forwarded to the manager.

ReviewOutcome return shape:

FieldTypeDescription
approvedbooleantrue when the reviewer accepted the output.
reasonstringPresent on rejection, to fold back into a re-run.

A ReviewApprovalManager is any object with a requestApproval that resolves to a boolean:

typescript
import { Task, reviewTaskOutput, type ReviewApprovalManager } from 'praisonai';

const manager: ReviewApprovalManager = {
  async requestApproval({ toolName, input, reason, timeout }) {
    // toolName === `task_output:<task name>`
    return await askHuman(reason);
  },
};

const task = new Task({
  description: 'Draft a reply',
  expected_output: 'A one-paragraph reply',
  humanInput: true,
});

const outcome = await reviewTaskOutput(task, '...draft...', { approvalManager: manager });

Best Practices

Feed the reason back into a re-run

A rejection returns reason: "a reviewer rejected this output". Add it to the task’s prompt and re-run so the next draft addresses the objection.

Register a manager before running headless

In non-TTY environments the default prompt cannot read stdin. Pass options.approvalManager (Slack, webhook, dashboard) so the throw never fires.

Return a strict boolean

Only === true approves. Make sure your requestApproval resolves an actual boolean — a truthy object is read as a rejection on purpose.

Reach for the right gate

Use Approval for tool calls, Human Review for outputs a person must sign off, and Guardrails for automatic output validation.


Related