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¶
- Agent calls a connector operation. The call enters the intercept layer (
BaseConnector.execute()orintercept_connector_call()). - Policy engine evaluates the call and returns
ESCALATEwith the computed risk score and matched policy ID. - Audit entry is written with status
escalated, recording the connector, operation, risk score, and policy ID. - Approval request is created. A row is inserted into the
approval_requeststable with statuspendingand an expiration timestamp (1 hour from creation by default). - Notification is dispatched. The approval notifier sends a Slack Block Kit card (or Teams adaptive card) to the configured approval channel.
- Agent blocks. The
BaseConnector.execute()method returnsFalsefrom_request_human_approval()and raisesApprovalDeniedError. The agent receives an error indicating that the operation requires approval. The pendingapproval_idis stored on the connector instance for async polling. - Reviewer acts. A user with
adminordeployerrole callsPOST /v1/approvals/{id}/reviewwith a decision ofapprovedordenied. - Agent polls for status. The agent (or SDK) polls
GET /v1/approvals/{id}/statusto learn when the reviewer has acted. - 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:
- Agent name -- the human-readable name of the agent (resolved from the
agentstable). - Connector -- which system the agent is trying to access.
- Operation -- the specific action being attempted.
- Risk score -- the computed score with visual severity indicator.
- Affected resource -- the target resource from the operation parameters.
- Approve / Deny buttons -- interactive elements that call back to the ARX API.
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.