Use case
Identity and permissions for AI agents
An agent that shares its owner's API key cannot be limited, traced, or switched off on its own. theAuth gives each agent its own token, its own permissions, a named human owner, and a record of every decision.
The problem
Agents act for people, and your logs only know the person.
Once an orchestrator starts sub-agents, calls tools over MCP, and runs without anyone watching, the usual session model stops answering the questions that matter. Which agent made that call? Who is responsible for it? Could it have done less? Can I stop just that one?
Reusing the owner's credentials answers none of those. You need identity at the agent level, with the human owner attached, and a policy check on every action rather than once at login.
The theAuth path
Five controls on one agent record.
- Agent identityauth.agent.create
- Each agent gets a cryptographic bearer token with the
kv_prefix and a type ofautonomous,delegatedorservice. TheownerIdfield ties it to the responsible user. - Permissionsresource + actions
- Permissions are
{ resource, actions }pairs with wildcard matching, for examplemcp:github:*withread.auth.authorize()returnsallowedand anauditId. - DelegationmaxDepth
auth.delegate()hands a subset of an agent's permissions to another with an expiry and amaxDepth(default 3). Anything wider than the parent holds is rejected. Revoking a chain cascades down.- Approval flowsrequireApproval
- A permission with
constraints: { requireApproval: true }denies the call with a reason you can catch. You create an approval request, notify the owner, and approve or deny later. - Budgets and auditpolicies, audit
- Budget policies cap token cost and call counts per agent, user or tenant. Every authorization decision, allowed or denied, is written to the audit log and can be filtered by agent, user and result.
Step by step
One orchestrator, one scoped sub-agent.
Excerpts that build on each other. auth is the instance from createTheAuth.
-
Create the agent with a human owner
ownerIdis required. It is what an auditor follows from an action back to a person.The sub-agent starts with no permissions of its own. It will only get what the orchestrator hands over.
const orchestrator = await auth.agent.create({ ownerId: "user-123", name: "planner", type: "autonomous", permissions: [ { resource: "mcp:github:*", actions: ["read", "write", "comment"] }, ], }); const reviewer = await auth.agent.create({ ownerId: "user-123", name: "code-reviewer", type: "delegated", permissions: [], }); -
Check every action
Call
authorizebefore the tool runs, not after. The decision is logged either way, andauditIdlinks your own logs to it.const result = await auth.authorize(orchestrator.id, { action: "read", resource: "mcp:github:repos", }); // { allowed: true, auditId: "aud_..." } -
Delegate narrowly, with a depth limit
The delegated permissions must be a subset of what the parent holds: narrower resources and fewer actions pass, wider ones fail with
INSUFFICIENT_PERMISSIONS.With
maxDepth: 1the reviewer cannot pass its access to anyone else. Revoking the chain later removes it and everything downstream.const chain = await auth.delegate({ fromAgent: orchestrator.id, toAgent: reviewer.id, permissions: [{ resource: "mcp:github:pulls", actions: ["read", "comment"] }], expiresAt: new Date(Date.now() + 30 * 60_000), // 30 minutes maxDepth: 1, }); // Later: cascades to every chain created downstream. await auth.delegation.revoke(chain.id); -
Put a person in front of sensitive actions
Mark the permission, catch the denial, and create an approval request. theAuth stores the request and fires your
webhookUrloronApprovalNeededhandler. Delivering the notification is your application's job.Requests expire after a TTL, five minutes by default. The agent does not block while it waits.
const filer = await auth.agent.create({ ownerId: "user-123", name: "file-manager", type: "autonomous", permissions: [ { resource: "file:prod-data/*", actions: ["read"] }, { resource: "file:prod-data/*", actions: ["delete"], constraints: { requireApproval: true }, }, ], }); const attempt = await auth.authorize(filer.id, { action: "delete", resource: "file:prod-data/dataset.csv", }); if (!attempt.allowed && attempt.reason?.includes("requires human approval")) { const req = await auth.approval.request({ agentId: filer.id, userId: filer.ownerId, action: "delete", resource: "file:prod-data/dataset.csv", }); // later, from your UI handler: await auth.approval.approve(req.id, "reviewer@example.com"); } -
Cap spend
A budget policy sets limits and an action when they are hit:
warn,throttle,blockorrevoke. Policies live onauth.policies.Evaluate a policy with
checkBudgetbefore you spend. We could not confirm thatauthorize()calls it for you, so call it yourself at the point where your agent makes an LLM request.await auth.policies.create({ agentId: orchestrator.id, limits: { maxTokensCostPerDay: 1000, maxCallsPerDay: 500 }, action: "block", }); const budget = await auth.policies.checkBudget(orchestrator.id); if (!budget.allowed) throw new Error(budget.reason); -
Answer the audit question
Filter by agent, owner, outcome and time. Denied calls are the interesting ones. Exports come out as JSON or CSV.
const denied = await auth.audit.query({ userId: "user-123", result: "denied", since: new Date("2026-10-01"), limit: 100, }); const csv = await auth.audit.export({ format: "csv" });
Checked against the package source for @glinr/theauth 0.5.0 on 2026-10-06. Compliance mappings of the audit log to the EU AI Act, NIST, SOC 2 and ISO 42001 are reports, not certifications; see the compliance docs for what they cover.
Pitfalls
Mistakes that leave agents over-trusted.
- Giving the orchestrator's token to sub-agents.That erases the boundary. Create each sub-agent and delegate a subset instead.
- Delegating with the default depth.
maxDepthdefaults to 3. If a sub-agent has no reason to delegate further, setmaxDepth: 1. - Long-lived delegations.Delegations take an
expiresAt. Give them the length of the task, not of the project. - Revocation does not stop work in flight.It takes effect on the next
authorize()call. Authorize before each action rather than once per session. - Expecting approval notifications to arrive by themselves.theAuth persists the request and calls your webhook or handler. Sending the Slack message or email is yours to build. Poll
listPending()as a fallback if a webhook fails. - Assuming budgets are enforced inside authorize.Call
auth.policies.checkBudget()yourself before LLM calls until you have confirmed otherwise in your version. - Pending approvals that never expire.Expired requests stay marked pending until
auth.approval.cleanup()runs. Schedule it.
Keep reading
Get started
Give your first agent an identity.
Install the package, create an agent with scoped permissions, and read its first audit record. The core runs on Postgres, SQLite, MySQL or D1, and the Go module needs a single go get.
npm install @glinr/theauthgo get github.com/glincker/theauth-go