// Execution Management (XEM)

Approvals

A human sign-off gate for destructive and irreversible tool calls. Every tool declares a risk level in its manifest; the workspace policy decides which levels demand explicit approval, who can grant it, when it escalates, and what happens when nobody answers in time.

Overview

Approvals can be resolved via the operational dashboard, the chat interface (using the x_ask_user.question SSE frame), the API (POST /v1/exec/approvals/:id/approve|reject), or xeroctl approvals approve|reject.

Risk Levels

Every tool declares one of four risk levels in its manifest entry. The risk level determines the default approval behavior:

// auto-approve read write Dispatch without a human in the loop.
// require approval destructive irreversible Block until a delegated reviewer resolves the request.
LevelDescriptionDefault Policy
read Observes state without modification (e.g. kubectl_get). Auto-approve
write Writes data in a recoverable way (e.g. kubectl_label). Auto-approve
destructive Destroys data or state that may be difficult to reverse (e.g. kubectl_drain). Require approval
irreversible Cannot be undone under any circumstances (e.g. openstack_server_delete). Require approval

Risk levels are ordered read < write < destructive < irreversible. See the MCP exec reference for how risk levels interact with tool dispatch.

Approval Policies

Approval policies are configured per workspace via the approval_policies table. Each policy defines timeout durations, timeout behaviors, and escalation chains for destructive and irreversible risk levels independently.

// schemaPolicy Fields

FieldTypeDefaultDescription
policy_name string - Unique policy name within the project + workspace.
timeout_seconds_destructive integer 900 Seconds to wait before timeout for destructive operations (15 min).
timeout_seconds_irreversible integer 3600 Seconds to wait before timeout for irreversible operations (1 hour).
on_timeout_destructive string escalate Action on destructive timeout: escalate, reject, or approve.
on_timeout_irreversible string escalate Action on irreversible timeout: escalate, reject, or approve.
escalation_chain_json JSON null Ordered list of escalation stages (see Escalation Chains).

// timeoutTimeout Behaviors

BehaviorEffectOutcome
escalate Advance to the next stage in the escalation chain. If no stages remain, the approval stays in pending_approval until manually resolved. Holds and advances
reject Auto-reject the approval and inform the model. The invocation fails. Fails
approve Auto-approve and dispatch. Use only when missing reviewers must not block work. Dispatches
block (legacy) Unknown on_timeout values coerce to escalate at policy-load time; a warning is logged. The legacy block value travels this path. It is not first-class. Coerced to escalate

SLA & Timeouts

The approval SLA worker runs on the router and periodically checks for approvals that have exceeded their timeout. When an approval times out, the configured on_timeout behavior is applied:

  1. The worker scans for approvals in pending_approval past their deadline.
  2. For each timed-out approval, it applies the policy's on_timeout action.
  3. If the action is escalate, it advances the escalation chain and fires an exec.approval_escalated webhook event.
  4. If the action is reject, it auto-rejects and fires an exec.approval_timed_out webhook event.
  5. If the action is approve, the approval is resolved as if a delegated reviewer had approved it and the tool call is dispatched.

SLO tracking for approval latency uses the exec_approval_latency_ms metric, measuring time from approval_requested to resolution.

Escalation Chains

An escalation chain is an ordered JSON array of stages stored on the policy row. Each stage carries exactly one target (a role, a user, or a webhook) and a delay_seconds budget before the next stage fires.

json
[ { "target": { "role": "owner" }, "delay_seconds": 300 }, { "target": { "user": "usr_01HCCCC" }, "delay_seconds": 600 }, { "target": { "webhook": "https://pagerduty.example.com/xerotier" }, "delay_seconds": 900 } ]

The exec.approval_escalated webhook event fires at each escalation, carrying the current stage index. If the policy row has no chain configured, the router falls back to a single owner-role stage whose delay matches the policy timeout.

Delegation

Project owners can delegate approval authority to team members via the project_role_delegations table. A delegation grants a member the ability to approve operations up to a specified risk level within a specific workspace.

// schemaDelegation Fields

FieldDescription
project_idProject scope.
workspace_idWorkspace scope (null = all workspaces).
user_idDelegated user.
can_approve_riskMaximum risk level the user can approve: destructive or irreversible.
granted_by_user_idUser who granted the delegation.
granted_atWhen the delegation was granted.
expires_atOptional expiration timestamp.
revoked_atIf set, the delegation is no longer active.

Delegations are checked at approval time, not at invocation time. A member without delegation can still invoke destructive tools, they just cannot approve them. The approval gate ensures a second person reviews the operation.

Approval Flow

The end-to-end approval flow works as follows:

  1. The inference model emits a tool call with a risk level that requires approval.
  2. The router creates an approval record and emits exec.approval_requested webhook + SSE frame.
  3. Eligible approvers are notified via dashboard, webhook, or escalation chain.
  4. An approver resolves the approval via API, CLI, or dashboard.
  5. If approved, the router dispatches the tool call to the XEM.
  6. If rejected, the router fails the invocation and informs the inference model.
  7. If timed out, the configured on_timeout behavior applies.

Notifications

Approvers can be notified through multiple channels:

  • Dashboard, the Approvals tab in the /ops dashboard shows pending approvals with live updates via SSE.
  • Chat, in-chat users see the approval prompt via the x_ask_user.question SSE frame.
  • Webhook, subscribe to exec.approval_requested events. Commonly used to post to a Slack channel with approve/reject buttons.
  • PagerDuty / Opsgenie, via the escalation chain, configured to fire exec.approval_escalated events to a webhook target.
  • xeroctl, use xeroctl approvals watch to poll for pending approvals from the terminal.

API Endpoints

MethodPathDescription
GET /v1/exec/approvals List approvals (filter by status, risk level).
GET /v1/exec/approvals/stream SSE stream of approval events.
GET /v1/exec/approvals/:id Get approval details.
POST /v1/exec/approvals/:id/approve Approve a pending request.
POST /v1/exec/approvals/:id/reject Reject a pending request.