Skip to main content

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:
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:
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:
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.
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

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.

B. The headless autonomy dial

This is not the agent-definition autonomy described in Agent Runs. 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.

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: 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 — the risk → autonomy default table and the full approval loop.
  • Model tier gate — the other fail-closed operator control, and the one that returns -32003 with requiredTier.
  • Tenant isolation — the facility-boundary guard that runs just before guardrails on the registry path.
  • Idempotency — why a blocked call leaves your key reusable.