> ## Documentation Index
> Fetch the complete documentation index at: https://docs.visiqlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Human-in-the-Loop

> Route sensitive AI agent actions to humans for real-time approval over Slack, Teams, and email.

## Overview

Not every action can be decided by a static rule. When evaluation returns `approval_required`, the tool call pauses while a human decides — from an interactive Slack message, a Microsoft Teams card, a one-click email link, or the dashboard queue. Approve, and the tool runs; reject (or let it time out), and the agent receives the block message as the tool's output and moves on.

The `visiq()` harness manages the pause/resume cycle automatically — there is no approval-handling code to write in your agent.

***

## How the pause works

1. A rule (or the `ask` no-coverage default) resolves the tool call to `approval_required`. The backend records the decision, creates a queue item with an expiry, and pushes notifications to your configured channels.
2. The SDK polls `GET /v1/allow/decisions/:id` every 2 seconds for the resolution.
3. **Approved** → the tool runs with its full arguments. **Rejected, timed out, or unresolvable** → the tool never runs; the agent receives the standard `[VisIQ <rule-code>] Action blocked: ...` message — unless the rule opted into the [mask fallback](#timeouts-and-the-mask-fallback).

The approval window is capped at a hard ceiling of **120 seconds** — an agent never hangs longer than two minutes waiting for a human. Uncertainty fails closed: a persistently unreachable backend or an unknown resolution status blocks the call rather than running it.

***

## Notification channels

Approvals are delivered over three channels — Email is configurable today on the dashboard under **Integration → Connectors** (**Human-in-the-Loop** category); Slack and Microsoft Teams delivery is built server-side, with their connector cards marked "Coming soon" while final verification completes:

* **Slack** — a fully interactive message with **Approve** / **Deny** buttons, posted by your Slack app's bot token to a channel you choose. Button clicks resolve the item directly from Slack.
* **Microsoft Teams** — an Adaptive Card delivered to an incoming webhook, with **Approve** / **Deny** buttons carrying signed, single-use action links.
* **Email** — an approval email with the same signed one-click **Approve** / **Deny** links.

The dashboard queue is always active regardless of channel configuration, and every queue item records which channels it was delivered to (`notified_via`), so you can see at a glance how a reviewer was reached. Each prompt includes the agent, the target app, the action and its context, and an AI-generated risk summary of what is being asked.

### Owner routing

Approvals can route to the person who owns the agent instead of a shared channel. Set `owner_email` on the agent from the Agents page; set both it and `hitl_pathway` (`slack` | `teams` | `email`) via `PUT /allow/agents/:id` or the SDK-install claim flow on the Connectors page:

* `slack` — the owner is direct-messaged in Slack (looked up by email).
* `email` — the owner receives the approval email directly.
* `teams` — Teams owner DMs currently deliver via email; channel-level Teams cards are unaffected.

For an owned agent, the shared channels are skipped — the approval goes straight to the owner, with the shared channels as the fail-safe if owner delivery fails, so an approval is never silently lost. Agents without an owner notify the organization-level channels, with a banner identifying the unclaimed agent so an admin can assign one.

***

## Two categories

Queue items come in two kinds with different audiences — and different response schemas.

### End-user approval (`enduser`)

A **live approval gate**: a rule decided this action needs human sign-off, and the agent is paused waiting. These are the only items that push Slack/Teams/email prompts — there is a real, pending decision to make.

**Use cases:** high-value transactions, bulk data export, permission changes, anything irreversible.

### Engineer coverage-gap (`engineer`)

Created when **no rule matched** and the action was denied by a no-coverage default (with an AI-recommended covering rule), or permitted by Autopilot (which drafts a covering rule). Actions permitted by the plain `approve` default are not queued — they appear only in the audit log. Nothing is waiting — so nothing is pushed. The item appears on the dashboard queue with an **AI-recommended covering rule**, asking your engineering team a different question: *should a rule exist for this?* As you add coverage, these approach zero.

<Tip>Push notifications fire **only** for genuine approval gates. Coverage-gap items are a pull surface on the dashboard — reviewers are never paged about an action that was already decided.</Tip>

***

## The queue

Pending items appear on the dashboard under **Harness → Escalations**, oldest first, with full context: agent, target app, action, arguments, category, expiry, and (for coverage gaps) the recommended rule. Approve or reject in one click; the waiting agent picks the result up on its next poll.

Programmatically:

* `GET /allow/hitl/queue` — list items; filter by `status` (`pending`, `approved`, `rejected`, `timeout`, `expired`, `dismissed`, `resolved`) and `category`.
* `POST /allow/hitl/queue` — create an item directly (`timeout_seconds` 10–120, default 120).
* `POST /allow/hitl/queue/:id` — respond. `enduser` items take `{ "decision": "approved" | "rejected", "responded_by": "..." }`; other categories take `{ "action": "dismiss" | "create_rule", "responded_by": "..." }` with a `linked_rule_id` for `create_rule`. `responded_by` is required on both. Sending the wrong shape for the item's category returns `400`.

Full request/response schemas are in the [API Reference](/rules/action/api-reference#human-in-the-loop-queue). Every human response emits a signed record envelope, so approvals are part of the verifiable audit trail.

***

## Timeouts and the mask fallback

The approval window is `hitl_timeout_seconds` in your organization settings — **30 to 120 seconds, default 120**. 120 is a hard ceiling: the evaluation path clamps to it regardless of stored settings, and a background sweep marks expired pending items `timeout` (mirrored to the audit log as `hitl_result: "timeout"`).

What a never-approved request becomes is the rule's `hitl_fallback`:

| `hitl_fallback`  | Behavior on reject / timeout / poll failure                                                                                                                                                                                 |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deny` (default) | The tool call is blocked — fail closed                                                                                                                                                                                      |
| `mask`           | The tool call proceeds with the rule's argument-masking applied — the graduated outcome for calls that should degrade rather than block. Requires the rule to carry masking directives; without them the fallback is `deny` |

An explicit human **approval always grants the full, unmasked call** — the fallback only governs the no-approval outcomes.

On the SDK side, the wait budget defaults to 120 seconds to match the server ceiling; shorten it with the `hitlTimeoutMs` option or the `VISIQ_HITL_TIMEOUT_MS` environment variable. Timed-out items stay visible in the queue for audit and rule-creation purposes, but they can no longer be responded to — the API returns `409` once an item is marked `timeout`, and the agent has already moved on.

***

## Uncovered actions and coverage gaps

When no rule matches, the per-operation-type **no-coverage default** (`read`, `write`, `delete`, `admin` in `no_coverage_defaults`) decides:

| Setting             | Behavior                                                                                                                                                                                             |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `approve` (default) | Permit the action — no queue item, no push, no pause; the decision is visible in the audit log only. Enable Autopilot or tighten the default to `ask`/`deny` to surface uncovered actions for review |
| `deny`              | Deny the action and record an `engineer` coverage-gap item — no push                                                                                                                                 |
| `ask`               | Hold the action as a live `enduser` approval gate — notifications are pushed                                                                                                                         |

Two settings modify this flow:

* **Autopilot** (`autopilot_enabled`) — for uncovered actions, AI drafts a covering rule automatically. The action is permitted, and the draft is created **disabled** with an `autopilot` source marker for you to review, edit, and enable from the Rules page. Useful during rollout when agents touch many APIs for the first time.
* **End-user approvals toggle** (`enduser_hitl_enabled`) — when `false`, the `ask` default resolves without holding the agent, and the audit log records the bypass (`hitl_result: "bypassed_disabled"`). Rule-triggered approval gates are unaffected by this toggle.

<Warning>Leaving every no-coverage default on `approve` means new API patterns your agents discover are permitted automatically. That is the deliberate no-default-disruption posture — but review the audit log for uncovered actions (or enable Autopilot to surface them as draft rules), and tighten `write`, `delete`, and `admin` to `ask` or `deny` once your rule coverage matures.</Warning>

Configure the no-coverage defaults under **Settings**; all of the above (including Autopilot and the end-user approvals toggle) via `PUT /allow/settings` ([reference](/rules/action/api-reference#settings)).

***

## Handling approvals in your agent

There is nothing to handle. The harness pauses the tool call, polls for the decision, and either runs the tool or returns the block message as the tool's output — the same shape as any policy denial, so your agent treats every blocked action identically. No callbacks, no retry logic, no special casing beyond the standard `visiq()` integration from the [Quickstart](/quickstart).
