> ## Documentation Index
> Fetch the complete documentation index at: https://developers.nexspace365.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Governance

> Organization AI guardrails and the headless autonomy dial — the two operator controls that change how your writes behave

# Governance

Two operator-controlled mechanisms sit between your `tools/call` and the tool's
handler. Neither is configurable through the API, both are invisible in
`tools/list`, and both are common explanations for "it works for my colleague in
the app but not for my connector":

* **Organization AI guardrails** — policies that can refuse a call outright or
  force it through the approval gate.
* **The headless autonomy dial** — a per-key / per-facility / fleet-wide switch
  that can force every write through the approval gate.

Both compose **tighten-only**: either can add a restriction, neither can remove
one.

## A. Organization AI guardrails

Guardrails are the same `block` / `require_approval` / `warn` policies the
in-app assistant enforces, evaluated on the headless surface too. An
organization defines them; a connector inherits them.

Policies are inherited down the org tree — a guardrail set on a parent
organization applies to every facility beneath it. The resolved list is sorted
by `priority` descending (ties broken by policy id descending) and the **first
matching policy wins**. Ordering between kinds is therefore a `priority`
question, not a "block beats warn" rule.

### What each kind looks like to a caller

#### `block`

The call is refused with JSON-RPC `-32003` (FORBIDDEN). The `message` is the
policy's **own** reason text, and `error.data.guardrail` carries the policy
name:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 11,
  "error": {
    "code": -32003,
    "message": "Outbound SMS to staff must go through the on-call manager.",
    "data": {
      "guardrail": "No unattended staff SMS",
      "suggestion": "An organization guardrail blocks this action. Contact an admin to adjust AI guardrails.",
      "retryable": false
    }
  }
}
```

When the policy has no custom message, `message` falls back to
`Blocked by guardrail "<policy name>"`. Either way, the presence of
`data.guardrail` is the reliable signal — branch on that, not on the prose.

#### `require_approval`

There is **no error**. Your call returns the ordinary
`status: "pending_approval"` result with an `approvalId`, even for a tool whose
risk level would normally auto-execute, and regardless of the
[autonomy dial](#b-the-headless-autonomy-dial):

```json theme={null}
{
  "status": "pending_approval",
  "approvalId": 4812,
  "message": "This action requires human approval in NexSpace before it runs."
}
```

Nothing in that payload names the guardrail. If a `low`-risk write that used to
execute inline starts pausing, a `require_approval` guardrail (or the autonomy
dial) is the reason. Poll `get_approval_status` with the `approvalId` — it needs
`general:read`.

#### `warn`

**Nothing observable.** The call proceeds exactly as it would have; the match is
logged server-side with the tool name and policy name. Do not build detection
logic around `warn` — there is no field, header, or result flag for it.

### Which predicates can match a headless call

A policy carries exactly one predicate. Two of the three can match over MCP:

| Predicate | Shape | Matches when |
| - | - | - |
| `tool_name` | `{ type: "tool_name", values: string[] }` | the called tool's name is in `values` (exact string match) |
| `risk_level` | `{ type: "risk_level", min: "low" \| "medium" \| "high" \| "critical" }` | the call's risk level ranks at or above `min`, ranked `low` → `medium` → `high` → `critical` |
| `message_contains` | `{ type: "message_contains", substrings: string[] }` | **never, over MCP** |

<Warning>
  **`message_contains` can never match a connector call.** The predicate tests the
  turn's user message, and there is no chat message on the connector surface — the
  headless gate passes a null user message, so the predicate always evaluates
  false. Do not expect a content-keyword guardrail to protect the MCP surface;
  express the same intent as a `tool_name` or `risk_level` policy.
</Warning>

Two more mechanics worth knowing:

* A `risk_level` predicate needs the call to *have* a risk level. Registry tools
  and connected-app tools always carry one (reads are `low`), so a
  `min: "low"` policy matches essentially everything on that path.
* An unrecognized predicate type is logged and **skipped**, not treated as a
  match — a forward-compatible policy row from a newer version cannot
  accidentally block you.

### Which calls are evaluated

| Call | Guardrails evaluated? |
| - | - |
| Registry tools and connected-app tools | Yes — reads included |
| The six governed verbs (`fillShift`, `swapShifts`, `notifyShiftSwap`, `verifyCredential`, `runPayroll`, `qualifyLead`) | Yes |
| Read-only named verbs (`searchStaff`, `getFacilityCoverage`, …) | No |
| The approval verbs (`listPendingApprovals`, `approveAction`, `rejectAction`) | No — they are the resolution mechanism, so gating them would deadlock |

### Guarantees you can rely on

* **Tighten-only.** A guardrail can block a call or force approval on one that
  would have auto-executed. There is no policy kind that lowers a risk default
  or removes an approval requirement.
* **Resolution fails open.** If the guardrail lookup itself errors, the resolver
  returns an empty policy list and the call proceeds through the rest of the
  stack. Guardrails are a policy layer on top of scopes, RBAC, suite
  entitlements, and the tenant-boundary guard — those gate independently and do
  not fail open.
* **Evaluated before the idempotency reservation.** A guardrail-blocked call
  never consumes your `Idempotency-Key`: the reservation is taken *after* the
  guardrail verdict on both the verb path and the registry path. You can fix the
  policy and retry with the same key. See
  [Idempotency](/concepts/idempotency).

## B. The headless autonomy dial

<Note>
  This is **not** the agent-definition autonomy described in
  [Agent Runs](/concepts/agent-runs#autonomy). That setting belongs to a
  configured agent and governs its run loop. The dial on this page belongs to a
  **headless credential** and governs MCP `tools/call` dispatch. They share the
  level names, which is the confusing part; they are separate settings resolved
  from separate sources.
</Note>

### Levels

`auto_execute` · `notify_after` · `require_approval`

Only `require_approval` changes dispatch: it forces the approval gate. The other
two add no restriction of their own — they leave the decision to the tool's risk
level and the org's rules.

### Resolution order

Most specific first; the first source that yields a valid level wins:

| Order | Source | Keyed by |
| - | - | - |
| 1 | the API key's `headlessAutonomy` policy on the loaded credential | the credential itself |
| 2 | `HEADLESS_MCP_AUTONOMY_BY_KEY` | the API key's id |
| 3 | `HEADLESS_MCP_AUTONOMY_BY_FACILITY` | the API key's facility, else the bound user's facility |
| 4 | `HEADLESS_MCP_AUTONOMY` | fleet-wide default |
| 5 | unset → `auto_execute` | — |

The two maps are JSON objects of the form `{ "<id>": "<level>" }`. Malformed
JSON, or a value that is not one of the three levels, is dropped and resolution
falls through to the next source.

`auto_execute` at step 5 is deliberate: tightening is opt-in, so operators who
never configured the dial keep the behavior MCP had before it existed. "Unset"
is permissive here, not ungoverned — org AI rules and the risk floor still apply.

### Tighten-only, and what it does not do

When the dial resolves to `require_approval`:

* **Every write tool goes through the approval gate**, even one whose risk level
  would auto-execute. A `low`-risk write like `create_crm_note` returns
  `pending_approval`.
* **Reads are untouched.** The registry path applies the dial only to
  non-read-only tools; read tools never enter the approval path at all.
* **It never relaxes anything.** It cannot lower the org AI-rule decision, and
  it cannot lift the hard risk floor that keeps `high` and `critical` actions at
  `require_approval` on every surface. It only ever adds the gate.

It composes with a `require_approval` guardrail by OR — either one forcing the
gate is enough.

### Troubleshooting: all my writes return `pending_approval`

If **every** write comes back as `pending_approval` — including `low`-risk ones
that have no business pausing — the cause is almost always one of:

1. the headless autonomy dial resolving to `require_approval` for your key, your
   facility, or the whole fleet, or
2. a `require_approval` guardrail with a broad predicate (a `risk_level` policy
   with `min: "low"`, or a `tool_name` list covering your writes).

Neither is visible in your credential's scopes and **neither can be changed
through the API** — both are operator settings. Ask a NexSpace admin which one
is in play. In the meantime, treat `status: "pending_approval"` as a first-class
outcome and poll `get_approval_status`; a credential holding `agents:approve`
can also resolve its own pending actions with `listPendingApprovals` /
`approveAction` / `rejectAction`.

If only *some* writes pause, this dial is probably not the cause — look at the
tool's risk level instead.

## See also

* [MCP](/concepts/mcp#what-decides-whether-a-write-pauses) — the risk → autonomy
  default table and the full approval loop.
* [Model tier gate](/concepts/model-tier-gate) — the other fail-closed operator
  control, and the one that returns `-32003` with `requiredTier`.
* [Tenant isolation](/concepts/tenant-isolation) — the facility-boundary guard
  that runs just before guardrails on the registry path.
* [Idempotency](/concepts/idempotency) — why a blocked call leaves your key
  reusable.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.