Back to Docs
Approvals

Human-in-the-Loop Approvals

When a policy escalates an action, it is queued for human review instead of being executed immediately. A team member can then approve or reject the action from the dashboard or via API. Approved actions are executed immediately; rejected actions are discarded.

Flow

  1. Agent submits intent via POST /api/execute
  2. Policy engine decides to escalate
  3. Intent is queued as a Pending Approval (7-day TTL)
  4. Agent receives "decision": "escalated" response
  5. Human reviews in the Approvals dashboard
  6. On Approve: action is executed via connector and logged
  7. On Reject: action is discarded and logged

Using the Dashboard

The Approvals page shows all pending approvals as cards with:

  • Action & channel — what the agent wants to do
  • Content preview — the message or text being sent
  • Parameters — action-specific details (ticket ID, email recipient, etc.)
  • Risk score — content verification score if applicable
  • Policy matches — which policies triggered the escalation
  • Agent context — which agent, session, and user
  • Approve / Reject buttons

Already-reviewed approvals are shown dimmed with the reviewer name and timestamp.

Approvals API

List pending approvals

curl
curl https://your-domain.com/api/approvals \
  -H "Authorization: Bearer hg_sk_your_api_key"

Response:

json
{
  "approvals": [
    {
      "executionId": "exec_escalated456",
      "action": "send_email",
      "channel": "email",
      "content": "Hi, your refund of $500 has been issued.",
      "params": {
        "to": "customer@gmail.com",
        "subject": "Refund confirmation",
        "body": "Hi, your refund of $500 has been issued."
      },
      "riskScore": 0.35,
      "policyMatches": ["Escalate external emails"],
      "context": {
        "agent": "my-assistant",
        "sessionId": "sess_abc123",
        "userId": "user_jane"
      },
      "status": "pending",
      "createdAt": "2025-03-15T14:30:00.000Z"
    }
  ]
}

Approve a pending action

On approval, the action is executed immediately via the matching connector (e.g., sends the email, replies to the ticket). A new execution record is created with decision: "approved".

curl
curl -X POST https://your-domain.com/api/approvals/exec_escalated456 \
  -H "Authorization: Bearer hg_sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "approve" }'

Response:

json
{
  "executionId": "exec_escalated456",
  "decision": "approved",
  "result": {
    "success": true,
    "data": { "emailId": "em_abc123" }
  }
}

Reject a pending action

The action is discarded. The agent can check the execution status to know it was rejected.

curl
curl -X POST https://your-domain.com/api/approvals/exec_escalated456 \
  -H "Authorization: Bearer hg_sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "reject" }'

Response:

json
{
  "executionId": "exec_escalated456",
  "decision": "rejected"
}

End-to-End Example

Here is a complete walkthrough: create a policy, trigger an escalation, and approve it.

Step 1: Create an escalation policy

curl
curl -X POST https://your-domain.com/api/policies \
  -H "Authorization: Bearer hg_sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Escalate large refunds",
    "trigger": ["issue_refund"],
    "conditions": [
      { "field": "param", "operator": "gt", "value": 200, "paramName": "refund_amount" }
    ],
    "decision": "escalate",
    "priority": 7
  }'

Step 2: Agent submits a $500 refund intent

curl
curl -X POST https://your-domain.com/api/execute \
  -H "Authorization: Bearer hg_sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "issue_refund",
    "params": {
      "refund_amount": 500,
      "order_id": "ORD-9876",
      "reason": "Customer requested cancellation"
    },
    "context": { "agent": "my-assistant", "userId": "user_jane" }
  }'

Response — escalated because refund_amount (500) > 200:

json
{
  "executionId": "exec_refund789",
  "decision": "escalated",
  "riskScore": 0,
  "matchedPolicies": ["Escalate large refunds"],
  "message": "Action requires human approval"
}

Step 3: Manager approves in the dashboard (or via API)

curl
curl -X POST https://your-domain.com/api/approvals/exec_refund789 \
  -H "Authorization: Bearer hg_sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "approve" }'

The refund action is now executed (or the agent is notified to proceed).

Step 4: Check the execution log

curl
curl https://your-domain.com/api/executions \
  -H "Authorization: Bearer hg_sk_your_api_key"

You will see two execution records: the original "escalated" one and the "approved" one created after human approval.

Agent Workflow Resumption

When a CrewAI agent's action is escalated, the workflow can pause and resume automatically using the GroundTruthAwaitApprovalTool. The tool polls GET /api/approvals/{execution_id} until the approval is resolved or a timeout is reached.

Poll approval status

curl
curl https://your-domain.com/api/approvals/exec_escalated456 \
  -H "Authorization: Bearer hg_sk_your_api_key"

Returns the approval object with its current status:

json
{
  "approval": {
    "executionId": "exec_escalated456",
    "action": "reply_ticket",
    "status": "approved",
    "reviewedBy": "admin@example.com",
    "reviewedAt": "2025-03-15T15:05:00.000Z"
  }
}

Webhook notifications

When you have a webhook URL configured, GroundTruth fires execution.approved and execution.rejected events when a human reviews an approval. This lets external orchestrators react in real time without polling.

json
{
  "id": "evt_...",
  "type": "execution.approved",
  "created_at": "2025-03-15T15:05:00Z",
  "data": {
    "executionId": "exec_escalated456",
    "action": "reply_ticket",
    "decision": "approved",
    "reviewedBy": "admin@example.com",
    "sessionId": "sess_abc123"
  }
}

Using the Python tool

See the CrewAI integration guide for full details on GroundTruthAwaitApprovalTool, including a complete multi-step workflow example.

Permissions

ActionWho can do it
Submit intents (POST /api/execute)Any authenticated user or API key
View executions & approvalsAny authenticated user or API key
Approve / rejectOwner, Admin, or API key
Create / edit / delete policiesOwner, Admin, or API key