An AI agent connects to your case system. The first question most teams ask is what the agent can do. That is the wrong question. An agent can attempt anything its instructions suggest. What it can actually do is decided by the credential it holds and the checks that run when it calls a tool. The boundary is the token, not the prompt.
The agent holds a scoped token. The gate runs the same permission check a human would face, then either allows a read-and-draft tool or accepts a request for a high-impact action that an operator still has to approve. What the agent may run is the operator's configuration. Every call, allowed or denied, is recorded with the policy that was in force.
A support agent that can read a case, draft a reply, and surface a recommendation is useful. The same agent holding a credential that can issue a refund, change an account, or delete records is a liability waiting for a bad inference. The difference is not how the agent is written. It is what its token reaches.
This Is For
- Engineers connecting an AI agent or an MCP client to an operations system and deciding what access to grant.
- Operations leads who want AI in the queue without handing it the ability to move money or message customers unattended.
- Anyone who has to answer, after the fact, exactly what an agent did and why a request was refused.
The Prompt Is Not a Permission Boundary
The fast way to connect an agent is to mint an admin key, paste it into the client, and rely on the system prompt to keep the agent inside the lines. This works in a demo. It fails the first time the model misreads a case, follows an injected instruction buried in an email it was asked to summarise, or generalises past its brief.
A prompt is guidance. A permission is enforcement. An agent that has been told not to issue refunds but holds a credential that can will, eventually, issue one. The instruction lives in text the model is free to reinterpret. The permission lives in code that runs before the action does. When the two disagree, the permission wins, and that is the property you want.
Latch treats an agent as an identity with a scope, not a key with a blast radius. The agent authenticates with a credential. The secret is hashed at rest. The credential carries an expiry. It can be revoked. Every call it makes is attributed to that identity. From there, what it can reach is a configuration decision, not a matter of trust in the model.
Give the Agent a Scoped Identity
An agent connects under a profile. The default profile is scoped, which means it grants nothing until you list what the agent may touch:
- Tools, by exact name or pattern. Grant
tickets.searchalone, ortickets.*for the read-and-draft set, orapi.get.*for read-only access to the wider API. - Resources, by pattern, so an agent can be confined to the queues and case types it is meant to work.
- Plugin actions, named as
{provider}:{action}, so the external actions an agent may even request are an explicit list rather than everything the system can do. - A risk ceiling. Tools generated from the API are rated low, medium, or high: a read is low, a write is medium, a delete or a hit on a sensitive path is high. The profile caps the level the agent can reach, so a read-only agent cannot stumble into a destructive one.
There is a second mode, an admin profile with no restrictions, for trusted internal automation. Most agents should never hold it. The scoped profile is the default for a reason. An agent should start with nothing and earn each tool, the same way a new operator does not arrive with every permission switched on.
The Gate Decides Every Call, Not the Tool
Authorization does not live inside each tool handler. It runs in one place, before any handler executes. When the agent calls tickets.comment.create, the gate resolves the case from the call. It builds the agent's identity from its role and profile. It runs the same permission check that runs when a human operator adds a note to that case. If the check fails, the call returns a policy denial with a readable reason, and the handler never runs.
This is the part that holds up as the system grows. A new tool added next quarter passes through the same gate without its author having to remember to re-implement the check. The authorization model lives in one auditable place instead of scattered across every handler, where one missed check becomes a hole. The agent's role defaults to the lowest one available, so an unconfigured agent is harmless rather than dangerous.
The Operator Decides What an Agent Can Execute
A plugin runs actions on external systems. In principle a plugin action can do whatever the external system allows: issue a refund, change an account, send a customer a message. The plugin layer does not decide an agent is safe. The operator does, in how the profile is configured.
The posture is set per action. An agent can be cleared to call a low-risk tool on its own: a lookup, a read, an internal comment. A plugin action that touches an external system can be configured to require approval, so the most the agent does is call actions.request_approval, and the request lands in the same workflow a human-raised request would. An operator approves or denies it before anything runs. Anything is possible in theory; what an agent may run is a configuration choice, and the safe default for work that moves money or reaches a customer is request-then-approve.
This is what makes it safe to put an agent on high-stakes work at all. A refund is exactly the kind of action you would never hand to a model unattended, and that is precisely why it is worth handing over behind a gate. The agent does the gathering and the proposing. A human approves. The system records both. A human in the loop here is not a brake on the agent. It is the thing that lets you point the agent at the hard work in the first place.
Every Call Leaves a Record, Including the Denied Ones
However the agent is configured, every call it makes is recorded. Each one is written to an invocation record: the tool, the outcome, the case it touched, the approval request it raised if any, and the reason if it was denied. Each record also carries the version of the policy in force when the call ran. A later reviewer can reconstruct not only what the agent did, but what it was permitted to do at the time, which is the question that actually comes up when something goes wrong.
Denied attempts are recorded, not silently dropped. A refused request is evidence the boundary held, and a run of refusals is a signal the agent is being asked for work it should not be doing. An audit that can only show successful actions cannot tell the difference between an agent that never overstepped and one that was stopped every time it tried.
The Control Lives in the Gate, Not the Agent
Because the control lives in the gate and not in the agent, it stays agnostic about the parts that change fastest. Swap the model, rewrite the prompt, or point a different MCP client at the endpoint: the tools the agent may call, the cases it may reach, and the actions it may request or run do not move. The agent is the part you are meant to iterate on. The boundary is the part that should not shift underneath you when you do.
State the rule plainly. An agent gets an identity, not a key. Its reach is a scoped list, not a default of everything. What it may execute is a posture you set per action, and the safe default routes high-impact work through human approval. And every call, allowed or denied, is on the record with the policy that decided it.
What Is Still Open
This model covers a single agent acting as one identity. Delegation across several agents, where one agent hands work to another and the permissions have to travel with it, does not yet have a clean answer. The risk ratings on generated tools are coarse, and a finer model would let teams grant more without granting too much. And proving that the case the agent described is the same case the operator approved is the same open problem the approval work names. These are the edges. The scoped identity, the central gate, and the request-then-approve default for high-impact work are the parts that hold today.
Continue reading
- Approval Design for High-Risk Operations — how to design the gate that an agent's request lands in.
- What Is a Plugin Action? — what runs on the other side of
actions.request_approval. - When AI Triage Should Recommend the Next Step and When It Should Stay Silent — the judgment layer above the permission layer.
- AI Triage Needs a Control Plane, Not Just Better Prompts — why control belongs in the system, not the model.