Skip to content

Enforcement Actions Open-Core ​

Every tool call that flows through the Intutic proxy receives exactly one enforcement action — a verdict that determines what happens to the request. There are five, in increasing severity:

ActionWhat happensAgent sees
BYPASSRequest passes through unchangedNormal response from the LLM
ENHANCERequest passes through with metadata, annotations, or warnings attached (a control plane's evaluator only)Normal response + governance annotations in the trace
HIJACKRequest is modified before reaching the LLM (e.g., DLP redaction, prompt rewriting)Modified response — agent may notice content was changed
REASKThe attempt is refused and the reason is handed back to the agent, which may retry a bounded number of times before the finding escalates to a blockAn error it can read and act on, then another turn
KILLRequest is blocked entirelyError response with the reason for blocking

A sixth value, OBSERVED, sits outside this ladder: it records a request the model provider failed to answer (a provider error or an unservable model), where nothing was enforced.


How a verdict is decided ​

A tool call is decided on the machine it runs on, before it runs. Each harness hook gate, and each gate SDK in an agent's own code, decides it from the policy snapshot (~/.intutic/hooks/policy-snapshot.rules) that intutic gates install and intutic start compile from .intutic/sops/. The gate SDKs run eight tiers in order, and the first refusal wins:

Tool call arrives at a gate SDK (@intutic/gate, intutic_clawde.gate)
       │
       ▼
┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│ A0 SSO group │────▶│ A1 snapshot  │────▶│ R  rule      │────▶│ M  MCP       │───┐
│    policy    │     │    rules     │     │    records   │     │    registry  │   │
└──────┬───────┘     └──────┬───────┘     └──────┬───────┘     └──────┬───────┘   │
       │ refused            │ refused            │ refused            │ refused   │
       ▼                    ▼                    ▼                    ▼           │
    blocked              blocked              blocked              blocked        │
┌─────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐     ┌──────────────┐     ┌──────────────┐     ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐
┆ A3 SOP rules ┆────▶│ A2 image     │────▶│ D  decide    │────▶┆ B  hook gate ┆
┆ (control     ┆     │    integrity │     │    rules     │     ┆ (control     ┆
┆  plane only) ┆     │              │     │              │     ┆  plane only) ┆
└╌╌╌╌╌╌┬╌╌╌╌╌╌╌┘     └──────┬───────┘     └──────┬───────┘     └╌╌╌╌╌╌┬╌╌╌╌╌╌╌┘
       │ refused            │ refused            │ refused            │ refused
       ▼                    ▼                    ▼                    ▼
    blocked              blocked              blocked              blocked

  refused at no tier ──▶ allowed
  • A0, A1, R and M are deterministic checks on the snapshot's records: the SSO group policy (a member whose groups are unknown is refused a high-risk tool), the block and approval rules, the requires_before:, scope_paths_mode: enforce and plan_approval: rule records, and the MCP server registry and allowlist. A1 and R fail closed, and a snapshot that fails its integrity check admits no MCP call.
  • A2 checks the container images a deploy command names against the local allowlist, and fails closed.
  • D runs only on a call every deterministic tier allowed: the snapshot's decide: rules, asked of your self-hosted decide engine. An enforcing rule the engine cannot answer refuses.
  • A3 and B need a control plane: A3 reads the SOP rules from its register, and B calls its hook gate (below). A gate SDK with no API key skips both.

The harness hook gates (gate body v23) read the same snapshot and run its checks in one order in every harness: the deterministic refusals (the protected-path and shell-bypass floor, the snapshot's block rules, the requires_before: records, the MCP registry and allowlist), then holds, then the trajectory records (scope_paths, plan_approval), then decide. They never call the hook gate (see the note at the end of this section).

With a control plane: the hook gate ​

With a control plane connected, a gate SDK's tier B evaluates the call at the hook gate (POST /api/v1/hook-gate), which returns an allow/block decision synchronously:

Tool call arrives
       │
       ▼
┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  DLP         │────▶│  BLOCK: SOP  │────▶│  SSO group   │────▶│  MCP server  │────▶│  Promoted    │
│  Scanner     │     │  match       │     │  policy      │     │  registry    │     │  findings    │
└──────┬───────┘     └──────┬───────┘     └──────┬───────┘     └──────┬───────┘     └──────┬───────┘
       │ hit                │ hit                │ DENIED             │ refused            │ KILL
       ▼                    ▼                    ▼                    ▼                    ▼
    blocked              blocked              blocked              blocked              blocked

  no hit at any stage ──▶ allowed
  1. DLP Scanner — credential patterns matched against the serialised tool arguments, and destructive commands (rm -rf /, DROP TABLE, DROP DATABASE, DROP SCHEMA, TRUNCATE TABLE) matched against each argument string as the tool receives it, with the SQL keywords split by any whitespace, a block or -- comment, or an escaped \n. The SQL patterns are the ones the MCP governance proxy uses. A hit blocks the call and opens an incident, which the response names as incidentId.
  2. BLOCK: SOP match — VALIDATED SOPs whose title begins BLOCK: are compiled to a pattern and tested against the tool name, and, in the BLOCK:<tool> WHERE <args>:<reason> form, against the serialised arguments too. The gate evaluates only BLOCK: titles. A title beginning REQUIRE_APPROVAL: is the same rule at the hold tier: the harness hook gates refuse the call and record it for Findings › Review Queue › Held Changes (approving it on a control plane lets that exact call through once while the workspace's review-hold bypass is on, and otherwise records the decision); the MCP proxy and the SDK gates hold it the same way through the same decisions API (see Approval holds); the proxy, which has no reviewer in the loop, treats it as a block.
  3. Policy guardrails — the workspace's hook_rule policy guardrails, tested against the tool name and the serialised arguments. An enforcing match blocks, with ruleId: guardrail.<id> and the rule id in brackets at the end of the reason; a shadow match lets the call on to the next check and counts as the guardrail's evidence. A rule scoped to roles applies only to callers with one of those roles.
  4. SSO group policy — decides the caller's group clearance for the tool, from their SCIM groups while SCIM provisioning is on and from their last SSO sign-in otherwise. DENIED or REQUIRES_OBO blocks, and the reason names the deciding rule ([sso_group.high_risk.<tool>] or [sso_group.require_obo.<tool>]).
  5. MCP server registry — on an mcp__<server>__<tool> call, the workspace's MCP server registry: a blocked or held server, a server not approved under mcpDefaultPolicy: deny, or a disabled tool blocks, with the MCP proxy's reason; then a server missing from a non-empty mcpAllowedServers list blocks. The response names the refusal in code (SERVER_BLOCKED, SERVER_HELD, SERVER_NOT_APPROVED, TOOL_DISABLED, SERVER_NOT_ALLOWED) and ruleId, and a server refused as unapproved joins the approval queue on the MCP Servers page.
  6. Promoted findings — repeat anomaly findings that have been promoted to enforcement. Only a promoted KILL blocks; a promoted HIJACK allows the call and queues a corrective card for the session.

The diagram leaves out the policy guardrails (step 3) and two more checks that run between the registry and the promoted findings: container-image provenance, which blocks only in a workspace that has turned it on, and SSL enforcement, which runs in shadow and never blocks (see KILL).

The first match wins, and the order is deliberate — this is a short-circuit, not a strictest-wins merge. Deterministic policy (DLP, an operator-authored BLOCK: SOP, an SSO group decision, a registry decision) is evaluated before the heuristic, so a promoted detector can never override a decision an operator stated explicitly.

Every stage after DLP fails closed, except the shadowed SSL evaluation. If a check errors or cannot run (a database or Valkey outage, say), the remaining checks still run, and one that blocks answers with its own reason. If none does, the call is refused with HTTP 403 and code: GOVERNANCE_UNAVAILABLE. A check the workspace has not set up (no SSO group policy, an empty registry, promotion off) is not an error and blocks nothing. In a workspace in shadow mode (ff_shadow_enforcement, the observe posture) the call is allowed instead, and the response carries degraded: true and the same code; a workspace whose shadow setting cannot be read counts as enforcing. A request body the gate cannot read is refused with HTTP 400 and code: INVALID_REQUEST. The gate SDKs decide on GOVERNANCE_UNAVAILABLE as they do when the control plane is unreachable, by their failClosed setting, which refuses by default. Each check that could not run is logged and counted in the governance_check_unavailable metric, by surface, check and outcome (refused or allowed_shadow). The AgentCore gateway check and the QM security screen follow the same rules in their own response shapes.

Every answer but a 400 carries a decisionId, the decision_id of the decision's SIEM gate_decisions record. A gate SDK reports the call with it, so the decision is exported and filed once.

The harness hook gates do not consult the gate

POST /api/v1/hook-gate is called synchronously by the gate SDKs (@intutic/gate and intutic_clawde.gate, and the framework adapters built on them) when they have a control-plane client, by the TrueForge bridge, and by any integration of your own. No harness hook gate calls it: Claude Code, Cursor, Cline, goose, OpenHands and every other hook-gated harness decide locally and report to the control plane asynchronously (POST /api/v1/hook-events), which keeps their tool path free of a network round-trip. Everything in this subsection describes the gate. The local path enforces the protected-path and shell-bypass guards written into the generated hook script, plus the policy snapshot, which the sync daemon refreshes from the control plane: the workspace's BLOCK: and REQUIRE_APPROVAL: rules, its policy guardrails, its SSO group policy, decided for the member the snapshot was issued to, and its MCP server registry and allowlist. A local gate refuses a high-risk tool when it does not know the member's groups. Promoted findings and the gate's DLP scan run only at the gate; the local gates carry their own secret-content patterns.

A harness with no hook system, such as Roo Code or aider pointed at the proxy, gets the SSO group policy from the proxy's response gate instead: the proxy withholds a refused tool call from the model's response before the harness sees it, for the member behind the request's virtual key, with the same decision and rule id. A policy change or a SCIM group change reaches it on the next request.


Two rungs, not one bar ​

Everything above answers what a verdict does. This section answers how a check earns the right to block at all — because Intutic runs two different kinds of check side by side, and they are held to different bars on purpose.

Rung 1 — deterministic, blocks today. DLP pattern matches, BLOCK: SOPs, SSO group policy, and budget limits are all authored by an operator or computed from a fixed rule. There is no learning curve and no false-positive rate to earn: the rule either matches or it doesn't, so it enforces (KILL/HIJACK) from the moment it's turned on. This rung is exactly the open-core pitch — every check on it runs deterministically, and the DLP and SOP checks ship in the public proxy and need no control plane to decide.

Rung 2 — learned, shadow-first. SSL's structural and logical layers, anomaly-finding promotion, and loop detection are heuristic: they're right most of the time on most workspaces, which is a different guarantee than "right." Each one launches in shadow mode — it evaluates every call and records what it would have done, without changing what the agent experiences — until its false-positive rate is measured on that workspace's own real traffic and clears the promotion rule. Only then does it graduate onto the enforcement table above (SSL as KILL, loop detection as today's REASK, findings as a promoted KILL/HIJACK).

Two opt-in paths escalate a judged finding. Everything above is deterministic by default: no LLM verdict blocks a request on its own. Two paths a workspace can turn on change that. The trajectory monitor's ACTIVE mode (default PASSIVE, see Trajectory Monitor) lets a trajectory verdict suggest a KILL instead of an alert, and repetition-based promotion lets an anomaly finding that keeps recurring graduate to enforcement under the promotion rule. A scoped break-glass grant runs in the other direction: it narrows what is evaluated for one request and never widens it.

Generated guardrails start on rung 2 and end on rung 1. A rule the Policy Clause Ledger derived from a policy document had a model in its derivation, so it launches in shadow like any other rung-2 check, whatever it renders to. When a named owner or admin promotes it, the emitted artifact — a hook rule (enforced alike by the hook gates, the SDK gates, the MCP proxy, the hosted hook gate and the LLM proxy's response gate), a front-matter key, a compiled WASM rule — is enforced by the rung-1 machinery above and is indistinguishable from one an operator wrote by hand. The citation it carries says why it exists; the promotion is what lets it act.

This is a deliberate trade, not a hedge: we don't let an LLM block your engineers until it has proven a low false-positive rate on your traffic. A heuristic that blocked from day one would be indistinguishable, from the agent's side, from a broken one — and the cost of that mistake is a blocked engineer, not a missed detection. Shadow mode is how rung 2 pays down that risk before it can ever cost anyone a turn.

The promotion rule ​

A rung-2 check earns enforcement on counted evidence from the workspace's own traffic, judged by a person:

  • Generated guardrails (hook rules and front-matter rules from policy documents): at least 200 shadow evaluations; a would-act rate of at most 5 %; and, among the calls it would have acted on, at least min(10, fires) adjudicated as true positives with a false-positive rate of at most 1 % — over the adjudicated fires, never the total. A rule that never fired is promotable only with an explicit acknowledgement, recorded on the event.
  • Generated WASM rules and rule candidates: at least 200 shadow evaluations and a would-block rate of at most 1 %, per rule, by an authenticated member. Both proxies count: each LLM request and each MCP tool call the rule is evaluated on is one evaluation, so a rule that only governs MCP tools earns promotion on MCP traffic.
  • Detector findings: adjudicated on the Findings page; the meter counts adjudicated findings, not total findings.

No workspace setting, plan flag or feature flag promotes anything on its own.


BYPASS ​

The request is permitted and passes through the proxy without modification. This is the default when all policy checks pass.

When it happens:

  • No SOP matches the tool call
  • The matched SOP explicitly allows the action
  • Budget is within limits, no DLP matches, and no SSO group policy withholds the tool

In the proxy verdict mapping:

typescript
// services/control-plane/src/lib/valkeySubscriber.ts — mapVerdict(trace.verdict)
case 'allowed':
  return 'BYPASS'

Example trace row (GET /api/v1/traces):

json
{
  "traceId": "8c1f0e2a-5b7d-4c3e-9a61-2f4d8b0c7e15",
  "timestamp": "2026-10-14T09:12:44.512Z",
  "requestedModel": "claude-sonnet-4-5",
  "actualModelRouted": "claude-sonnet-4-5",
  "enforcementAction": "BYPASS",
  "complianceScore": 1,
  "actualCostUsd": 0.0042,
  "anomalyDetected": null,
  "breakGlass": false,
  "breakGlassRequestId": null
}

ENHANCE ​

The request passes through, but Intutic attaches governance metadata — warnings, annotations, or cost attribution tags — to the trace. The LLM request itself is not modified.

ENHANCE is an evaluator verdict rather than a value the proxy writes on a trace: the evaluator scores a sample ENHANCE between its bypass and hijack thresholds. The proxy's own verdicts map only to BYPASS, HIJACK, REASK, KILL and OBSERVED, so no trace records ENHANCE: an annotation or a cost tag rides on a BYPASS trace, and a detector finding that steers the agent records HIJACK (below).

When it happens:

  • A detector raises an advisory finding (recorded for review, not blocking)
  • Cost tracking attaches attribution metadata
  • Anomaly detection flags the pattern but doesn't block

Most detectors ship on STEER or REASK rather than KILL for exactly this reason: a threshold that has never had its false-positive rate measured annotates the trace instead of stopping the work. See the promotion rule.


HIJACK ​

The request is intercepted and modified before reaching the LLM. The most common cause is DLP redaction — secrets detected in prompts are replaced with placeholder tokens before the request leaves the proxy.

When it happens:

  • DLP scanner detects API keys, passwords, or PII in the prompt → redacts before forwarding
  • PCAS downgrades a model selection (e.g., routes to a cheaper model)

A custom WASM or Rego rule cannot return HIJACK or ENHANCE. Its verdicts are allow, block and reask, plus hold for a Rego rule: a rule never receives the request body, so it has nothing to rewrite, and no way to supply guidance to inject. A rule result the proxy cannot enforce refuses the call as a rule that reached no verdict, never allows it.

On the trace, the proxy records hijacked (HIJACK) when a detector's finding steers the request — the corrective card is injected and the call proceeds. A DLP redaction, a rewrite or a model downgrade records the trace as allowed (BYPASS); a tool call the response-path DLP rewrote is reported separately, as a substitution (POST /api/v1/decisions/substitutions).

In the proxy verdict mapping:

typescript
// services/control-plane/src/lib/valkeySubscriber.ts — mapVerdict(trace.verdict)
case 'hijacked':
  return 'HIJACK'

Example: DLP redaction in action

Original prompt:
  "Use API key sk-abc123xyz789 to call the endpoint"

After HIJACK:
  "Use API key [REDACTED] to call the endpoint"

The agent receives a normal response, but the sensitive data never reaches the LLM provider.


KILL ​

The request is blocked entirely. The agent receives an error response explaining why the action was denied.

When it happens:

  • Budget exhausted (session or workspace spend limit reached)
  • DLP scanner detects an unredactable secret leak
  • A BLOCK: SOP matches the tool name
  • SSO group policy denies the caller
  • A repeat anomaly finding has been promoted to enforcement

Note what is not on this list. SSL enforcement runs at the gate but is shadowed — it records what it would have done and the call proceeds. Loop detection ships on REASK. Both are held back by the promotion rule until their false-positive rates are measured on real traffic.

In the proxy verdict mapping:

typescript
// services/control-plane/src/lib/valkeySubscriber.ts — mapVerdict(trace.verdict)
case 'killed':
  return 'KILL'

// An unrecognised verdict also records as KILL, deliberately: a value this service
// cannot interpret means the proxy is ahead of the control plane, and assuming
// BYPASS would log an unenforced call as allowed.

Example: a BLOCK: SOP that stops destructive commands

A SOP whose title begins BLOCK: is enforced at the gate once it reaches VALIDATED. The title carries the rule — BLOCK:<pattern>:<reason> — where <pattern> is a regular expression tested against the tool name. No scripting is involved:

BLOCK:^run_command$:Recursive delete at filesystem root is prohibited

The reason is optional. The BLOCK: prefix is case-insensitive, but the pattern is matched case-sensitively. In this form the pattern ends at the first colon, so it cannot itself contain one, and a title whose pattern is not a valid regular expression is skipped rather than blocking every call.

For an argument-level condition — matching rm -rf / inside the arguments rather than the tool name — add WHERE <argPattern>, a regular expression tested against the JSON-serialised arguments; it may contain colons:

BLOCK:^run_command$ WHERE rm -rf /[\s"]:Recursive delete at filesystem root is prohibited

For anything a pair of patterns cannot express, write a WASM rule, which runs in the proxy with an explicit host-import allowlist.

What the agent sees: the gate answers

json
{ "allowed": false, "reason": "Recursive delete at filesystem root is prohibited" }

and a gate SDK raises it as

[Intutic Governance] BLOCKED: Recursive delete at filesystem root is prohibited

In a workspace whose intervention mode is opaque, the agent is given a generic refusal instead of the rule's reason; the enforcement log and incident keep the full reason.


  • Core Concepts — Workspaces, harnesses, SOPs, and scoring
  • Security — Threat model and data flow
  • Integrations — How each harness connects to the enforcement pipeline

The circuit breaker for AI agents