Human Approval Gates

When the policy engine returns an ESCALATE verdict, ARX pauses the agent's operation and creates a human approval request. The agent cannot proceed until a human reviewer explicitly approves or denies the action. This mechanism enforces the principle that high-risk autonomous actions always require human judgment.

End-to-End ESCALATE Flow

  1. Agent calls a connector operation. The call enters the intercept layer (BaseConnector.execute() or intercept_connector_call()).
  2. Policy engine evaluates the call and returns ESCALATE with the computed risk score and matched policy ID.
  3. Audit entry is written with status escalated, recording the connector, operation, risk score, and policy ID.
  4. Approval request is created. A row is inserted into the approval_requests table with status pending and an expiration timestamp (1 hour from creation by default).
  5. Notification is dispatched. The approval notifier sends a Slack Block Kit card (or Teams adaptive card) to the configured approval channel.
  6. Agent blocks. The BaseConnector.execute() method returns False from _request_human_approval() and raises ApprovalDeniedError. The agent receives an error indicating that the operation requires approval. The pending approval_id is stored on the connector instance for async polling.
  7. Reviewer acts. A user with admin or deployer role calls POST /v1/approvals/{id}/review with a decision of approved or denied.
  8. Agent polls for status. The agent (or SDK) polls GET /v1/approvals/{id}/status to learn when the reviewer has acted.
  9. Agent resumes or aborts. If approved, the agent retries the connector call (which will now pass policy evaluation). If denied, the agent handles the rejection according to its own logic.

Approval Request Creation

Approval requests are created by BaseConnector._request_human_approval() in the connector base class, and by _create_approval_request() in the intercept layer. Both paths produce identical records.

Approval Record Schema

Field Type Description
id UUID Auto-generated unique identifier.
org_id UUID Organization that owns the agent.
agent_id UUID Agent that triggered the escalation.
policy_id UUID or null Policy rule that triggered the escalation, if any.
action_type string The operation string (e.g., host:isolate).
connector string Connector identifier (e.g., crowdstrike).
action_detail object Operation parameters, with internal fields (prefixed with _) stripped.
risk_score integer Computed risk score at the time of escalation.
status string One of pending, approved, denied, expired.
requested_at datetime Timestamp when the request was created.
reviewed_by UUID or null User who reviewed the request.
reviewed_at datetime or null Timestamp of the review decision.
review_notes string or null Free-text notes from the reviewer.
expires_at datetime Expiration timestamp. Defaults to 1 hour after requested_at.

Reviewer Experience

Slack Block Kit Card

When an approval request is created, the approval notifier sends a Slack Block Kit message to the configured channel. The card includes:

The card provides sufficient context for a reviewer to make an informed decision without needing to log into the ARX console.

Teams Adaptive Card

For organizations using Microsoft Teams, the approval notifier sends an equivalent adaptive card through the configured Teams webhook. The card contains the same fields and interactive elements.

Reviewing via REST API

Reviewers can also act on approvals through the REST API directly.

Submit a Decision

POST /v1/approvals/{approval_id}/review
Authorization: Bearer <token>
Content-Type: application/json

{
  "status": "approved",
  "review_notes": "Confirmed with incident commander. Proceed with isolation."
}

The status field must be exactly approved or denied. The review_notes field is optional but recommended for audit purposes.

Access control: Only users with admin or deployer roles can review approvals. The @require_role(["admin", "deployer"]) decorator enforces this.

Error Conditions

HTTP Status Condition
404 Not Found Approval request does not exist or belongs to a different organization.
409 Conflict Approval has already been reviewed (status is not pending).
410 Gone Approval has expired. The status is automatically updated to expired.

Check Approval Status

Agents and automation poll this endpoint to learn the outcome:

GET /v1/approvals/{approval_id}/status

Returns:

{
  "approval_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "status": "approved"
}

List Pending Approvals

GET /v1/approvals/pending

Returns all pending approvals for the authenticated user's organization that have not yet expired. Results are ordered by requested_at descending.

List All Approvals

GET /v1/approvals
GET /v1/approvals?status=denied
GET /v1/approvals?agent_id=a1b2c3d4-e5f6-7890-abcd-ef1234567890

Supports filtering by status and agent_id. Pagination is available via limit (default 50, max 500) and offset (default 0) query parameters.

Approval Expiry

Approval requests expire 1 hour after creation by default. When a reviewer attempts to act on an expired request, the API returns 410 Gone and updates the record status to expired.

Expired approvals are functionally equivalent to denials -- the agent cannot proceed. The expiry mechanism prevents stale approval requests from being acted on long after the operational context has changed.

The pending approvals endpoint (GET /v1/approvals/pending) automatically filters out expired requests by comparing expires_at against the current UTC time.

Audit Logging

Every approval lifecycle event is recorded in the audit log:

Event action_type Key Metadata
Approval created connector.called (status: escalated) risk_score, policy_id
Approval reviewed approval.reviewed decision, risk_score, review_notes

The audit entry for a reviewed approval includes the user_id of the reviewer, the agent_id of the agent, and the connector and operation that triggered the escalation. This provides a complete chain of accountability from agent action to human decision.

Notification Configuration

Approval notifications are routed through the organization's configured notification channels. The approval_channel field on a policy rule specifies which Slack or Teams channel receives the notification for escalations triggered by that rule.

If no approval_channel is specified on the matched policy, notifications are sent to the organization's default approval channel (configured in organization settings).

Notification delivery failures are logged at WARNING level but do not block the approval flow. The approval request is still created and can be acted on through the API or console regardless of whether the notification was delivered.

Configuring Approval Channels

Set the approval_channel field when creating or updating a policy rule:

POST /v1/policies
Content-Type: application/json

{
  "agent_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Escalate Okta writes to IAM team",
  "rule_type": "escalate",
  "connector": "okta",
  "action_pattern": "*:write",
  "approval_channel": "#iam-approvals"
}

Different policy rules can route to different channels, allowing organizations to direct approvals to the appropriate team based on the connector and action type.