Answer
An agent should never hold more access than the person it works for. Give it a scoped, short-lived identity, check permissions on every tool call, put a human in front of destructive actions, and log every decision.
TL;DR
- Give each agent a scoped, short-lived identity that acts on behalf of one user in one tenant, never a long-lived shared key.
- Check permissions on every tool call, inside the tool, against the user the agent is acting for.
- Split read-only and write tools, put a human approval in front of destructive actions, and log every decision.
The agent is not a superuser
The quickest way to ship an agent is a service account that can reach everything, with the prompt deciding what to touch. That works in a demo. In production the prompt is not a security boundary. A confusing request, a bad search result, or an instruction hidden in a document can steer the model anywhere the credentials reach.
Treat the agent as a new kind of client. It gets an identity, the identity gets permissions, and the permissions are enforced outside the model. The model proposes. The authorization layer decides.
Act on behalf of a user, with a scoped identity
An agent should always act on behalf of someone. When a user asks it to do something, the call carries two identities: the agent that acts and the user it acts for. The effective permission is the overlap, never the sum. The agent must not be able to do anything the user could not do themselves.
Issue that identity per session or per task, limited to one tenant and to the tools the task needs, and let it expire within minutes. Standard token exchange flows are built for this kind of delegation. The agent should never see a broad key that lives for months. If a token leaks through a log or a trace, it should be useless within minutes.
Check permissions on every tool call
Checking access once, when the session starts, is not enough. Every tool call is a new request, and the check belongs inside the tool, not in the prompt. The question is always the same: may this user, through this agent, take this action on this resource?
Relationship-based access control is a good fit. Permissions follow relationships you already store, such as user to group, group to project, and project to document, so the check for one document is one question to the authorization service. The web app and the agent ask the same question, which keeps the rules in one place.
async function callTool(ctx: AgentContext, tool: Tool, args: ToolArgs) {
const allowed = await authz.check({
subject: `user:${ctx.userId}`,
actor: `agent:${ctx.agentId}`,
relation: tool.requiredRelation,
object: tool.resourceOf(args),
})
audit.log({ ctx, tool: tool.name, args: redact(args), allowed })
if (!allowed) return { error: "not permitted" }
return tool.run(args, ctx.scopedToken)
}A denied call returns a plain error the model can explain to the user. Not a stack trace, and not a quiet retry with other credentials.
Separate reading from writing
Not every tool carries the same risk. Sort them into classes with their own rules.
- Read-only tools: search, look up, and summarize. Still checked per user, because reading another tenant's data is already an incident.
- Write tools: create a draft, update a field, open a ticket. Scoped narrowly, with arguments validated against a schema before they run.
- Destructive or external actions: delete, pay, send an email, change access. These wait until a person approves that exact action.
The approval shows what will happen, with the real arguments, and the approval itself is checked against the approver's permissions. A button that only says continue, without the details, is not a control.
Log every decision
When something goes wrong, you need to answer who asked, which agent acted, what it tried, what was allowed, and what was denied. Log each tool call with the user, the agent, the tool, the resource, the decision, and a reference to the trace that led to it. Redact the arguments before logging them. Keep the log out of the agent's reach, so it cannot edit or delete its own history.
The same log shows when a permission is too wide. A tool that is never called, or a scope that is never used, can be removed.
Where this meets a delivery
Taking one agent to production with scoped identities, a check on every call, and an audit trail is what an AI production sprint covers. For an agent on a model you host yourself, see our private model and fine-tuning service. Once it is live, Managed AI operations keeps permissions, logs, and evaluations under watch as the model and the tools change.
FAQ
Is the system prompt enough to stop an agent from misusing a tool? No. A prompt guides behaviour, but content the agent reads can override it. Enforce permissions in the tool layer, outside the model.
Should an agent have its own service account? It can have its own identity, but it should act on behalf of a user and get only the overlap of its own and the user's permissions, for a short time.
Which actions need human approval? Anything that is hard to undo or leaves your system: deleting data, moving money, sending messages, and changing who has access.









