Skip to main content

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.
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.
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.

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. 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: 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: 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.
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.
Configure the no-coverage defaults under Settings; all of the above (including Autopilot and the end-user approvals toggle) via PUT /allow/settings (reference).

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.