Enforcer

AI agent authorization, and why the prompt is not the place to do it

An agent acting for a user is not the user. It needs its own identity, only the permissions it actually needs, and the ability to be switched off without switching off the person it works for.

A rule in the system prompt is advice, not a boundary

The most common first attempt is an instruction in the system prompt: do not spend more than five hundred, do not email customers, do not delete anything. The model is stochastic, so its adherence to that instruction is stochastic too. A prompt injection can talk it past the rule faster than a user can. As one engineer put it, a promise from the thing you are trying to constrain is not a control, it is a hope.

The check has to sit outside the agent's reasoning loop, in the code that runs the tool, where the model cannot reach it.

What a working enforcement point looks like

Four properties, and most attempts miss at least one.

The approaches, honestly compared

ApproachWhere the check runsHolds up under prompt injectionCost
Rules in the system promptInside the modelNoFree
Hard-coded if statementsYour applicationYesRewritten in every service, drifts
Policy engine (OPA, Cedar, Casbin)A separate serviceYesYou maintain a copy of your data in it
Relationship engine (OpenFGA, SpiceDB, WorkOS FGA)A separate serviceYesDual writes; permissions drift from your database
Identity-aware authorization (Enforcer)A service that already knows the callerYesNothing to sync

Why the sync problem matters more than it sounds

Most fine-grained authorization products are a decision layer. You hand them a subject id and a resource, and they answer true or false. To answer at all, they need a copy of your data, so every time your application creates a project or adds a member you write it twice, once to your database and once to theirs. If the two disagree, your permissions are wrong and nobody finds out until somebody sees something they should not. This is the most common complaint about the whole category, and AuthZed's own CTO names it as the first pain point of the model.

Enforcer avoids it because it signed the user in. When a policy runs it is handed the resolved caller: role, tenant, group memberships, verification status. You write a rule over a real user object and pass the resource you already have in hand.

A worked example

Before the agent issues a refund, the tool handler asks:

POST https://api.instruxi.dev/api/v1/enforcer/authz/check
{ "action": "issue",
  "resource": { "type": "payment", "id": "pay_8f21", "owner_id": "...", "tenant_id": "..." },
  "contexts": { "amount": 820 } }

-> { "allow": false,
     "policy_id": "pol_01J7Q...",
     "reason": "over 500, no approval on file" }

The caller's identity comes from the token, so it is never sent and the agent cannot claim to be someone else. Return reason to the agent and it can tell the user what would make the answer yes, instead of retrying a call that will always fail.

Start with the rule switched off

Create every rule inactive, then switch it on once the wording is agreed. An inactive rule is stored and not evaluated, so there is nothing to watch; log the returned reason and policy_id yourself. A rule that starts blocking the moment it exists is a rule nobody dares create, which is how teams end up with no rules at all.

Last updated 2026-09-11. Enforcer is identity and authorization in one system, for applications and AI agents. Docs at docs.instruxi.dev. Set it up from an AI agent: docs.instruxi.dev/setup.