Create and attach a service policy
This feature is in Beta. Account admins can control access to this feature from the account console Previews page. See Manage Databricks previews.
For an overview of service policies, see Service policies for AI securables.
To create a service policy, you write a SQL policy function, then attach it to an MCP Service, Model Service, or Model Provider Service through the Unity Gateway UI.
The policy governs each interaction at two evaluation points:
- The input phase (ON CALL) before Databricks invokes the service.
- The output phase (ON RESULT) after the service responds.
Prerequisites
- An account administrator must enable the beta for your account from the Previews page in the account console.
- To create the policy function:
CREATE FUNCTIONprivilege on the target schema. - To attach a policy to a service:
MANAGEon the target service securable andEXECUTEon the policy function.
Step 1: Write the policy function
A service policy function is a SQL UDF registered in Unity Catalog. It takes a single VARIANT parameter, event (the interaction data and context), and returns a VARIANT result:
CREATE OR REPLACE FUNCTION <catalog>.<schema>.<function_name>(
event VARIANT
)
RETURNS VARIANT
LANGUAGE SQL
RETURN <expression>;
The function runs at both evaluation points; branch on event:type::string ('request' for the input phase, 'response' for the output phase) to act on a single phase. For the full event fields, the return value, and the supported SQL subset, see Service policy function reference.
Write a decision policy
A Decision Policy returns a VARIANT with a result field of ALLOW, DENY, or ASK and an optional reason. Build the result with named_struct and wrap it in to_variant_object so the function returns a VARIANT, keeping result and reason as top-level fields.
The result value determines what happens (it is case-insensitive):
ALLOW: the interaction proceeds.DENY: Databricks blocks the interaction. Instead of an error, the caller receives a successful (HTTP 200) response whose assistant turn reports the block, with thereasonin a top-leveldatabricks_service_policyobject.ASK: the interaction pauses for human approval before proceeding.
For the supported SQL subset and the rules for returning a VARIANT, see Service policy function reference.
Example: deny a GitHub push from an MCP Service
This policy blocks any call to the push_files tool and allows all other interactions. Because the policy is attached to a specific MCP Service, the function only needs to check the tool name:
CREATE OR REPLACE FUNCTION main.governance.block_github_push(
event VARIANT
)
RETURNS VARIANT
LANGUAGE SQL
RETURN
CASE
WHEN event:type::string = 'request'
AND event:context.tool.name::string = 'push_files'
THEN to_variant_object(named_struct('result', 'DENY', 'reason', 'GitHub push operations are not permitted by policy.'))
ELSE to_variant_object(named_struct('result', 'ALLOW', 'reason', ''))
END;
Example: require human approval before a destructive tool runs
This policy pauses any call to the delete_repository tool for human approval and allows all other interactions.
CREATE OR REPLACE FUNCTION main.governance.ask_before_repo_delete(
event VARIANT
)
RETURNS VARIANT
LANGUAGE SQL
RETURN
CASE
WHEN event:context.tool.name::string = 'delete_repository'
THEN to_variant_object(named_struct('result', 'ASK', 'reason', 'Deleting a repository requires human approval.'))
ELSE to_variant_object(named_struct('result', 'ALLOW', 'reason', ''))
END;
When this policy returns ASK, Databricks pauses the call for human approval before it runs.
For external agents calling MCP Services, Databricks delivers the approval decision using MCP URL-mode elicitation: the user opens the provided URL to approve or decline the call. The external agent must retry the call after approval. To use ASK with an external agent, the agent's MCP client must support MCP protocol version 2025-11-25 or later.
For MCP Services, approving a tool call caches the approval for one hour, so an identical call isn't prompted again within that window.
For more custom policies, including tool allowlists, keyword and topic blocks, prompt-length limits, response checks, and LLM-as-a-judge classifiers, see Service policy examples.
Step 2: Attach the policy to a service
During the Beta, you attach a service policy through the Unity Gateway UI, on an individual MCP Service, Model Service, or Model Provider Service. You can attach more than one policy to a service: each attachment has a priority (rank), and the chain stops at the first DENY. On the input phase, policies are evaluated in ascending rank order (lowest first); on the output phase, in the reverse order.
To attach a policy:
- In the workspace sidebar, click AI Gateway.
- Select the service to govern: a model service on the Models tab, a model provider service on the Providers tab, or an MCP service on the MCPs tab.
- Open the Policies tab, then click New policy.
- Enter a Name for the policy.
- Under Applied to, select which principals the policy applies to. The default, All account users, applies it to everyone.
- In Guardrail type, select what the policy runs:
- A built-in guardrail, such as Unsafe Content or Jailbreak. The Evaluator model service that runs the check (the LLM judge) is preselected; to use a different one, expand Advanced options and select it (you need
CAN_QUERYon the model you choose). - Custom: click Custom function, then Select function, and select the SQL function you wrote in Step 1.
- A built-in guardrail, such as Unsafe Content or Jailbreak. The Evaluator model service that runs the check (the LLM judge) is preselected; to use a different one, expand Advanced options and select it (you need
- Under Phase, select where the policy runs: Input guardrails (ON CALL, before the service is invoked), Output guardrails (ON RESULT, after it responds), or both. The phase selection applies to built-in guardrails and custom LLM-as-a-judge policies. Some built-in guardrails run in only one phase, such as jailbreak detection on input and hallucination detection on output. A custom SQL function has no Phase setting: it runs at both phases, so branch on
event:typein the function body to scope its behavior (see Step 1). - Set the Rank to control evaluation order. The lowest rank runs first on the request and last on the response.
- Click Create policy.
The policy appears on the service's Policies tab. During the Beta, allow a short time for it to propagate before you test.
During the Beta, the policy UI might change. If a label differs from these steps, follow the in-product labels.
Use a built-in policy
Databricks provides built-in service policies under the system.ai namespace, such as system.ai.block_unsafe_content to block unsafe or harmful content. To use one, follow Step 2 but choose the built-in guardrail in Guardrail type instead of Custom. Built-in guardrails don't take any policy-specific configuration. You set the standard Phase, Rank, Evaluator model service, and Mode fields when you attach them.
For the full list of built-in policies, and a note on how they appear in Unity Catalog, see Built-in service policies.
You need the EXECUTE privilege on the built-in policy function and MANAGE on the target service.
Verify the policy
After you attach a policy, verify that it is active and producing the expected outcomes.
After you attach or change a policy, allow a short time for the change to take effect before you test. During the beta, policy changes can take up to a minute or two to propagate.
Confirm attachment
In the Unity Gateway, open the target service and view its attached policies. The policy you created appears in the list.
Observe policy outcomes
You can confirm a policy is taking effect:
- From the caller: when the policy returns
DENY, the caller receives a successful (HTTP 200) response rather than an error. The assistant turn reports that the content was blocked, and a top-leveldatabricks_service_policyobject carries thereasonyou specified.ASKpauses the call for human approval. - In system tables: model and MCP activity is recorded in the usage tables, and full request and response payloads in inference tables.
Debug and audit a policy decision
When a policy blocks an interaction, the block reason in the databricks_service_policy object names the policy and gives a short explanation. See Observe policy outcomes.
To see the full reasoning behind a built-in or custom LLM-as-a-judge decision, including the evaluator's confidence and the exact content it judged, review the evaluator model service's inference table.
An LLM-as-a-judge policy runs its prompt on a separate evaluator model service (the judge). The judge's verdict isn't recorded in the protected service's inference table, which logs only the protected service's own request and response. The judge's input and verdict are captured only when an inference table is enabled on the evaluator model service.
Capture the evaluator's verdicts
Enable an inference table on the model service that runs the check. You have two options:
- Enable an inference table directly on the evaluator the guardrail already uses. The default evaluator is a
system.aimodel service, and you can enable an inference table on it. - Under Advanced options when you attach the policy, point the guardrail at an evaluator model service you own (it doesn't have to be a
system.aimodel), then enable an inference table on that service.
To enable an inference table, see Log requests and responses to inference tables. Turn it on before the interactions you want to audit: only evaluations that run after logging is enabled are captured, and rows can take a few minutes to appear.
Read a verdict
Each evaluation writes one row per policy per phase to the evaluator's inference table:
requestis the assembled judge prompt: the policy's criteria, the JSON output contract, and the content under evaluation wrapped in<ContentToEvaluate>markers. For a multi-turn (conversation-window) input policy, the evaluated content is the recent turns plus the latest input; otherwise it's the single message.responseis the evaluator's raw completion. The verdict is the assistant message content: a JSON object withflagged,confidence, and, whenflaggedistrue,reason.destination_nameidentifies the evaluator, andrequest_idties the evaluation to the interaction that triggered it.
To find the evaluations where the judge flagged content, filter the evaluator's inference table on the verdict:
SELECT
event_time,
request_id,
get_json_object(response, '$.choices[0].message.content') AS verdict,
request
FROM <catalog>.<schema>.<evaluator_inference_table>
WHERE get_json_object(response, '$.choices[0].message.content') ILIKE '%"flagged":true%'
ORDER BY event_time DESC;
The verdict column shows the evaluator's decision, such as {"flagged":true,"confidence":0.87,"reason":"..."}. A reason appears only when flagged is true. A flagged verdict is the judge's decision, not proof the interaction was blocked. In Log mode, a would-be DENY is recorded here but not enforced, and the table doesn't record the mode, so confirm an actual block from the caller's databricks_service_policy response (see Observe policy outcomes). To trace one specific interaction instead, filter by its request_id.
Audit blocks from the evaluator's inference table, not the protected service's. When an input-phase policy denies a request, Databricks doesn't invoke the underlying service, so a blocked interaction may not produce a row in the protected service's inference table. Sourcing request IDs from the protected table therefore misses blocked interactions; filter the evaluator's table on the verdict instead.
Narrow to one interaction in a large table
The evaluator's inference table has no policy-name column, and the id in the response body (chatcmpl-...) isn't the request_id. Narrow to the interaction you're debugging with these filters, and bound the scan by time:
request_id(most precise): every evaluation for one interaction shares it. Capture it from the call'sdatabricks-request-idresponse header, then filterWHERE request_id = '<id>'. This works for a block. Confirm that the header value matches the column, because the response body'sidfield is a different value.- Request content: the judge's
requestholds the evaluated content, so a distinctive string in your prompt pins the interaction down, for examplerequest ILIKE '%<your marker>%'. - Time:
event_time >= current_timestamp() - INTERVAL 30 MINUTESlimits the scan.
To tell which policy produced a row, read its request. The system message is that policy's criteria, so you can filter and identify the policy. For example request ILIKE '%<distinctive phrase from the policy prompt>%'. The reason in the verdict usually restates the trigger.
SELECT event_time, request_id, invocation_id,
get_json_object(response, '$.choices[0].message.content') AS verdict,
request
FROM <catalog>.<schema>.<evaluator_inference_table>
WHERE event_time >= current_timestamp() - INTERVAL 30 MINUTES
AND request ILIKE '%<your marker or prompt text>%'
AND get_json_object(response, '$.choices[0].message.content') ILIKE '%"flagged":true%'
ORDER BY event_time DESC;
Non-determinism and dry-run testing
The evaluator is a model, so its verdicts are non-deterministic: the same input can return different verdicts across runs, and the variation is larger across different evaluator models or when request routing spans model versions. The reason is short free text, not structured per-entity data, and it can quote the flagged content. For a repeatable, deterministic check, use the built-in Sensitive Data Detection policy or a custom SQL policy instead of an LLM judge.
Databricks recommends attaching an LLM-as-a-judge policy in Log mode first, which records the verdict without blocking, with an inference table enabled on its evaluator. Inspect the verdicts on real traffic, refine the prompt and rank, then switch the policy to Enforce. Sensitive Data Detection runs in enforce mode only.
Access rights
The steps above require the following privileges:
Step | Required access |
|---|---|
Attach the guardrail |
|
Select a non-default evaluator |
|
Enable an inference table on the evaluator | Permission to manage the evaluator model service (for example, you created it), plus |
Read the evaluator's inference table |
|
Limitations
The following limitations apply during the beta:
- Transformation: Service policies return a decision (ALLOW, DENY, or ASK); they don't transform request or response content during the beta.
- Policy language: Custom policy functions support only
LANGUAGE SQL. - Attachment scope: Policy attachment is UI-only and scoped to an individual service, and the policy applies to all account users. Attaching policies at the catalog or schema level, attribute-based access control (ABAC) conditions, and custom principals are not available.