A Databricks agent deployed on Model Serving reaches other resources in one of three ways: automatic authentication passthrough, where a hidden service principal uses the deployer's checked access to resources declared at log time; on-behalf-of-user authentication, where it acts as the person querying it; or manual authentication with credentials kept in secrets.
Last updated November 2026.
Chapter 36 of the course covers the idea: a deployed agent reaches only what it declared, through a shared identity or the caller's own, and endpoint permissions are a separate layer. This post adds the detail: who the agent runs as in each mode, every declarable resource with its required privilege, complete code for all three modes, and the endpoint permission levels.
The objective is "Control access to resources from model serving endpoints", in Section 4 (Assembling and Deploying Applications) of the 18 Mar 2026 guide for the Databricks Certified Generative AI Engineer Associate exam. The docs now steer new agents to Databricks Apps; everything below is the Model Serving version the objective names.
The downstream resource checks the agent's identity, not the caller's, and that identity depends on the mode:
Under passthrough, Databricks first verifies that the endpoint creator can access every declared resource, then creates a service principal for that agent model version, grants it read access to those resources, and injects short-lived M2M OAuth credentials that it rotates. That service principal does not appear in API or UI listings and is deleted when the model version leaves the endpoint, so there is nothing to grant by hand: a missing permission is fixed by the declaration.
These are the resource types the docs list for automatic passthrough, with the mlflow.models.resources class, its argument, and the permission the endpoint creator must hold at deploy time:
| Resource and class | Deployer needs |
|---|---|
Vector Search indexDatabricksVectorSearchIndex(index_name=) | Can Use (MLflow 2.13.1+) |
Model Serving endpointDatabricksServingEndpoint(endpoint_name=) | Can Query (2.13.1+) |
SQL warehouseDatabricksSQLWarehouse(warehouse_id=) | Use Endpoint (2.16.1+) |
Unity Catalog functionDatabricksFunction(function_name=) | EXECUTE (2.16.1+) |
Genie SpaceDatabricksGenieSpace(genie_space_id=) | Can Run (2.17.1+) |
Unity Catalog connectionDatabricksUCConnection(connection_name=) | Use Connection (2.17.1+) |
Unity Catalog tableDatabricksTable(table_name=) | SELECT (2.18.0+) |
LakebaseDatabricksLakebase(database_instance_name=) | databricks_superuser (3.3.2+) |
Sourced from the Databricks documentation pages "Authentication for agents (Model Serving)" and "Access control lists" (serving endpoint ACLs), checked November 2026. Unity Catalog resources also need USE SCHEMA and USE CATALOG on their parents. The docs also show DatabricksApp(app_name=) in the logging example, for a custom MCP server hosted on a Databricks App, but list no permission for it.
Declare downstream resources too: a Genie Space needs its tables, warehouses and functions alongside it. For workspace MCP endpoints, the get_databricks_resources() method of DatabricksMCPClient, in the databricks-mcp package, lists everything to declare.
import mlflow
from mlflow.models.resources import (
DatabricksVectorSearchIndex, DatabricksServingEndpoint,
DatabricksFunction, DatabricksSQLWarehouse, DatabricksGenieSpace,
DatabricksTable,
)
with mlflow.start_run():
logged = mlflow.pyfunc.log_model(
name="agent",
python_model="agent.py",
input_example=input_example,
resources=[
DatabricksVectorSearchIndex(index_name="prod.support.docs_index"),
DatabricksServingEndpoint(endpoint_name="databricks-bge-large-en"),
DatabricksServingEndpoint(endpoint_name="my-llm-endpoint"),
DatabricksFunction(function_name="prod.support.lookup_order"),
# The Genie Space, plus what it depends on
DatabricksGenieSpace(genie_space_id="<genie_space_id>"),
DatabricksSQLWarehouse(warehouse_id="<warehouse_id>"),
DatabricksTable(table_name="prod.sales.orders"),
],
)
The resources list is stored with the logged model. When the registered version is deployed, the permission check runs against the deployer and the hidden service principal is granted exactly this list. Anything the code touches that is missing here fails at query time, not at deploy time.
On-behalf-of-user authentication is in Public Preview, needs MLflow 2.22.1 or above, and is off until a workspace admin enables it. On Model Serving it covers Vector Search indexes, serving endpoints, SQL warehouses, Unity Catalog connections, tables (through SQL statement execution) and functions, Genie Spaces and MCP servers, but not volumes.
from databricks.sdk import WorkspaceClient
from databricks_ai_bridge import ModelServingUserCredentials
from databricks.vector_search.client import VectorSearchClient
from databricks.vector_search.utils import CredentialStrategy
from mlflow.pyfunc import ResponsesAgent
class SupportAgent(ResponsesAgent):
def predict(self, request):
# Built per request: the caller is only known now
user_client = WorkspaceClient(
credentials_strategy=ModelServingUserCredentials())
user_vsc = VectorSearchClient(
credential_strategy=CredentialStrategy.MODEL_SERVING_USER_CREDENTIALS)
# Runs as the agent's own identity, not the caller
system_client = WorkspaceClient()
try:
index = user_vsc.get_index(endpoint_name="vs-endpoint",
index_name="prod.support.docs_index")
except Exception:
index = None # caller lacks access: skip the tool, do not crash
...
Every user-scoped client is created inside predict (or predict_stream), never in __init__ or at import time: the user's identity only exists once a request arrives, and building the client per request keeps invocations isolated from each other. The try/except lets a user without access to one resource still use the rest; the plain WorkspaceClient() runs as the agent.
import mlflow
from mlflow.models.auth_policy import AuthPolicy, SystemAuthPolicy, UserAuthPolicy
from mlflow.models.resources import DatabricksServingEndpoint
system_policy = SystemAuthPolicy(
resources=[DatabricksServingEndpoint(endpoint_name="my-llm-endpoint")])
user_policy = UserAuthPolicy(api_scopes=["model-serving", "ai-search"])
with mlflow.start_run():
mlflow.pyfunc.log_model(
name="agent",
python_model="agent.py",
auth_policy=AuthPolicy(system_auth_policy=system_policy,
user_auth_policy=user_policy),
)
For on-behalf-of-user, the docs pass an auth_policy instead of a bare resources list: the system policy holds resources reached with the agent's own credentials, the user policy holds the REST API scopes the caller's token is downscoped to. On Model Serving, only scopes matching the supported resources are allowed. The docs flag one risk: the model-serving scope lets the agent call an endpoint as the user, and that endpoint may reach APIs beyond the agent's own scopes.
Once the caller's identity reaches the data, Unity Catalog's own per-user controls apply to it, which is how row filters and column masks stay in force through an agent.
Manual authentication is for a resource passthrough does not cover, credentials other than the deployer's, APIs outside Databricks, or the prompt registry. The docs recommend OAuth for a service principal you create and grant yourself:
import os
from databricks.sdk import WorkspaceClient
from databricks import agents
scope = "agent-secrets"
os.environ["DATABRICKS_HOST"] = "https://<workspace-url>"
os.environ["DATABRICKS_CLIENT_ID"] = dbutils.secrets.get(scope=scope, key="sp-client-id")
os.environ["DATABRICKS_CLIENT_SECRET"] = dbutils.secrets.get(scope=scope, key="sp-client-secret")
w = WorkspaceClient(host=os.environ["DATABRICKS_HOST"],
client_id=os.environ["DATABRICKS_CLIENT_ID"],
client_secret=os.environ["DATABRICKS_CLIENT_SECRET"])
agents.deploy(UC_MODEL_NAME, version,
environment_vars={
"DATABRICKS_HOST": "https://<workspace-url>",
"DATABRICKS_CLIENT_ID": "{{secrets/agent-secrets/sp-client-id}}",
"DATABRICKS_CLIENT_SECRET": "{{secrets/agent-secrets/sp-client-secret}}",
})
The agent code builds its client from the three environment variables, reading the client ID and secret from a secret scope as the docs show. agents.deploy() passes the endpoint the same variables as {{secrets/scope/key}} references, so the secret never appears in plain text in the endpoint config. A PAT works the same way with DATABRICKS_TOKEN, preferably for a service principal. For the prompt registry, that principal needs CREATE FUNCTION, EXECUTE and MANAGE on the prompts schema.
The trap: overriding these authentication environment variables disables automatic passthrough for every other resource the agent depends on. Adding one external API key can break the index that worked yesterday.
Serving endpoint ACLs have three levels. CAN VIEW gets and lists the endpoint. CAN QUERY adds sending requests. CAN MANAGE adds updating the config, deleting the endpoint and changing its permissions. The levels stack, so a manager can also query. Under passthrough, granting CAN QUERY shares the deployer-checked reach; under on-behalf-of-user, each caller still needs their own data access.
| Situation | Mode |
|---|---|
| Per-user access or user-attributed auditing is required | On-behalf-of-user |
| Shared resources, all of a supported type | Automatic passthrough (the docs' recommendation) |
| An unsupported resource, an external API or the prompt registry | Manual, OAuth service principal in secrets |
| OBO needed for a resource Model Serving does not support, such as volumes | Deploy on Databricks Apps |
From the docs' decision flowchart. Modes can be mixed per resource within one agent.
Exam tip: "works in the notebook, auth error on the endpoint" points to a resource missing from resources=, fixed by re-logging and redeploying. "Each user sees only their own rows" points to on-behalf-of-user. "Who may invoke the endpoint" is an endpoint ACL question, never a resource question. A shared admin token is always the wrong answer.
How the agent itself is authored, traced and deployed with agents.deploy() is in the Agent Framework and Agent Bricks post.
With automatic authentication passthrough, the agent runs as a hidden service principal created for its model version, with access to the resources declared at log time. With on-behalf-of-user authentication it acts as the person querying it, and with manual authentication it uses the credentials you supply as environment variables.
In the notebook the agent runs as you; deployed, it reaches only the resources declared in log_model's resources parameter. Add the missing index, function or endpoint to resources, log a new version and redeploy.
No. On-behalf-of-user authentication for agents is in Public Preview, requires MLflow 2.22.1 or above, and is disabled until a workspace admin enables it.
CAN QUERY on the endpoint, or CAN MANAGE, which includes it. CAN VIEW only gets and lists the endpoint.
Inside predict or predict_stream, not in __init__ or at import time, because the caller's identity only exists when a request arrives.
Controlling access to resources from a serving endpoint is Chapter 36 of the Databricks Generative AI Engineer Associate course, in Unit 4, Assembling and Deploying Applications, with declared resources, the two identities and endpoint ACLs built up card by card and exam-style checks.
Open Chapter 36 →