Skip to main content
Agents can request human approval before running a tool.

Quick Start

1

Simple Usage

Set approval: true to gate every tool behind an interactive y/n prompt on the terminal.
2

With an ApprovalManager

Pass an ApprovalManager to add auto-approve / auto-deny rules and a custom handler.
approval accepts only boolean | ApprovalManager. Per-tool rules live on the ApprovalManager (via addAutoApprove / addAutoDeny), not on the agent.

User Interaction Flow


How approval: true behaves

approval: true builds an ApprovalManager for you and wires createCLIApprovalPrompt() as its handler, so the default is a blocking y/n prompt on stdin. Without a handler, requestApproval() waits on respond() — which nobody calls in a plain CLI session — so it would otherwise stall until the 5-minute default-deny timeout. Wiring the prompt is what keeps the default usable.
Denials are fed back to the model, not thrown. When a call is denied, the agent returns this string as the tool result so the model can course-correct and continue the run:

Correlation with toolInvocationId

The gate passes the LLM’s tool_call.id as toolInvocationId on every approval request. Handlers and out-of-band UIs use it to correlate a decision back to the exact pending call — never positionally. This keeps multiple in-flight tool calls disambiguated.

Custom Handler

Register handlers with onApprovalRequest. The first handler to resolve true approves; if all resolve false, the call is denied.
Auto-deny is checked first (safety), then auto-approve, then handlers.

Configuration Options

ApprovalManager constructor options: ApprovalManager instance methods:

Advanced

Per-tool wrapper: withApproval

Wrap a single tool so it gates itself. Throws ToolApprovalDeniedError on deny unless you provide onDenied.

Global manager: getApprovalManager / setApprovalManager

withApproval uses a process-global manager when none is passed. Configure it once.

Dangerous-pattern helpers

Pre-built regexes and checkers flag risky inputs so you can require approval only when needed.
DANGEROUS_PATTERNS exposes fileDelete, dbDestructive, shellDangerous, and networkSensitive.

Events

ApprovalManager is an event emitter — subscribe with .on(...) to drive an out-of-band UI (e.g. a web dashboard that answers via respond()).

Errors

Two approval layers. The ApprovalManager → ApprovalHandler layer is boolean-only (a handler returns true/false). A backend installed via setApprovalBackend (see below) produces a full ApprovalBackendDecision carrying scope ('once' | 'session' | 'always'), scopePattern, and feedback — Python parity with praisonaiagents.approval. The handler projects that decision down to a boolean for the manager.

Approval Backends (Python parity)

Swap the default y/n prompt for a richer backend that matches praisonaiagents.approval — auto-approve, a diff-aware console, an LLM reviewer, or a legacy callback. An Agent with approval: true builds an ApprovalManager; setApprovalBackend answers its requests.
Pass a specific ApprovalManager to install the backend on it instead of the process-global one.

Which backend?

AutoApproveBackend

Approves every call — use in bots, CI, or trusted unattended runs.
Python parity: praisonaiagents.approval.AutoApproveBackend — same always-approve behaviour.

ConsoleBackend

Prompts on the terminal with a coloured unified diff and once/session/always scopes.
What the user sees:
  • y/yes is accepted as once.
  • Empty input defaults to n (deny).
  • d prompts for free-text guidance returned on decision.feedback.
  • Diffs render in ANSI unless NO_COLOR is set or output is not a TTY — override with new ConsoleBackend({ colors: false }).
Python parity: praisonaiagents.approval.ConsoleBackend — same prompt, Rich markup instead of ANSI.

AgentApproval

Delegates the decision to another AI agent that replies APPROVE or DENY.
requestApprovalSync returns a denial in TypeScript — an LLM call cannot block the event loop. Use the async path (requestApproval, which setApprovalBackend uses). Python’s request_approval_sync does prompt synchronously; this is a real divergence.
Python parity: praisonaiagents.approval.AgentApproval(approver_agent=..., llm="gpt-4o-mini").

CallbackBackend

Wraps a legacy (toolName, arguments, riskLevel) => decision | boolean callback.
Python parity: praisonaiagents.approval.CallbackBackend(callback).

Build your own backend

Implement ApprovalBackend — requestApproval (async) and requestApprovalSync. Use the approvalRequest / approvalDecision builders for Python-shaped defaults and suggestScopePattern for a reusable “always” pattern.

Wiring functions

setApprovalBackend installs a backend; getApprovalBackend reads it back.
Adapt a backend into a plain ApprovalHandler, or convert a manager request into the Python shape, directly.
approvalRequest and approvalDecision build the Python-shaped objects with dataclass defaults (approvalId auto-fills like uuid4().hex).
setApprovalBackend(null) clears the backend slot — it does not detach the registered handler. The handler then declines (returns false), letting other handlers or the manager’s event flow decide.

Permission-target helpers

Turn a tool call into a reusable permission target or glob for session/always scopes. ARITY maps commands (git, npm, docker, …) to the number of leading tokens commandPrefix keeps.

API surface

Everything exported from the approval module — all import flat from praisonai. ApprovalBackendRequest — what a custom backend receives: ApprovalBackendDecision — what a backend returns: BackendHandlerOptions — passed to setApprovalBackend / approvalHandlerFromBackend:

Best Practices

Reading data is usually safe. approver.addAutoApprove(/^(read|list|search)_/) skips the prompt for read-only tools.
Auto-deny is checked first. Block deletions and destructive commands with approver.addAutoDeny(/^delete_/).
The default is 5 minutes and default-denies on timeout. Lower it for interactive flows: new ApprovalManager({ defaultTimeout: 60_000 }).
In UIs with concurrent calls, always route decisions by request.toolInvocationId, never by order — positional matching authorises the wrong call.

Reviewing task outputs, not tool calls? See Human-in-the-Loop Review for Task(humanInput: true) and reviewTaskOutput().

Python Approval Backends

The matching Python/CLI backends

Approval Protocol

Cross-SDK backend matrix and request/decision shapes

Human Review

Approve a task’s output before the next task

Guardrails

Input/output validation