Overview
Not every action can be decided by a static rule. When evaluation returnsapproval_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
- A rule (or the
askno-coverage default) resolves the tool call toapproval_required. The backend records the decision, creates a queue item with an expiry, and pushes notifications to your configured channels. - The SDK polls
GET /v1/allow/decisions/:idevery 2 seconds for the resolution. - 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.
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.
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. Setowner_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.
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.
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 bystatus(pending,approved,rejected,timeout,expired,dismissed,resolved) andcategory.POST /allow/hitl/queue— create an item directly (timeout_seconds10–120, default 120).POST /allow/hitl/queue/:id— respond.enduseritems take{ "decision": "approved" | "rejected", "responded_by": "..." }; other categories take{ "action": "dismiss" | "create_rule", "responded_by": "..." }with alinked_rule_idforcreate_rule.responded_byis required on both. Sending the wrong shape for the item’s category returns400.
Timeouts and the mask fallback
The approval window ishitl_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 anautopilotsource 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) — whenfalse, theaskdefault 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.
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 standardvisiq() integration from the Quickstart.