theAuth Guide: Cap Agent Spend and Require Human Approval A developer has released theAuth, an open-source authentication library for AI agents and humans, and published a guide showing how to cap agent spending and require human approval for risky actions. The guide builds three controls on top of agent identity: scoped, expiring delegation grants via theauth.delegate(), a budget policy that warns at one threshold and blocks at another, and an approval flow that stores a pending human decision before irreversible actions. The author notes that only delegation and permission constraints are enforced inside authorize(), while budgets and approvals are modules the developer must call. theAuth is open-source auth for AI agents and humans. Star the repo on GitHub https://github.com/glincker/theauth · Read the docs https://docs.theauth.dev?utm source=devto&utm medium=article&utm campaign=guide-6-agent-delegation-budgets-human-approval · Run the quickstart https://docs.theauth.dev/quickstart?utm source=devto&utm medium=article&utm campaign=guide-6-agent-delegation-budgets-human-approval · theauth.dev https://theauth.dev?utm source=devto&utm medium=article&utm campaign=guide-6-agent-delegation-budgets-human-approval A planner agent of mine once fanned out to six workers on a Friday night. By Saturday morning one worker kept retrying a failed summarization, and my provider dashboard showed a line I did not like. Nobody broke in. Every credential was valid. The system simply had no idea that "valid" and "affordable" are different questions. That weekend taught me that agent fleets need three controls beyond identity. Narrow grants that expire. A spend cap that actually stops calls. And a human checkpoint in front of the actions you cannot undo. This guide builds all three with theAuth, an open-source auth library for AI agents and humans @glinr/theauth on npm . By the end you will have a planner that delegates scoped, short-lived access to a worker, a budget guard that warns at one threshold and blocks at another, an approval flow for a destructive action, and a cost report that rolls up per delegation chain. I already covered the basics of sub-agent delegation in an earlier post. This one starts where that one stopped: what happens to the money and the risky actions once the chain exists. This is guide 6 of 8 in the theAuth guides. It stands on its own, so you can start right here. It uses agent identities from guide 5. If you already know how to create an agent, start here. | Guide | Title | Read it when | |---|---|---| | 1 | Add login to an existing Next.js app https://dev.to/thegdsks/add-login-to-an-existing-nextjs-app-with-theauth-4kpk | You have an app with no auth yet | | 2 | Passwordless login: passkeys, links, OTP https://dev.to/thegdsks/passwordless-login-in-typescript-passkeys-links-otp-59ja | You want to drop passwords or add 2FA | | 3 | Multi-tenant SaaS auth: orgs, RBAC, SSO, SCIM https://dev.to/thegdsks/multi-tenant-saas-auth-orgs-rbac-sso-and-scim-1fod | You sell to teams and companies | | 4 | Migrate from Auth0 or Clerk https://dev.to/thegdsks/migrate-from-auth0-or-clerk-to-theauth-step-by-step-4o47 | You already run another provider | | 5 | Give every AI agent its own identity https://dev.to/thegdsks/give-every-ai-agent-its-own-identity-and-permissions-49mf | You run AI agents and need to start somewhere | | 6 | Cap agent spend and require human approval this guide | Your agents spend money or act on risky things | | 7 | Secure an MCP server for production https://dev.to/thegdsks/securing-an-mcp-server-for-production-step-by-step-53h7 | You expose tools over MCP | | 8 | Build an audit trail for AI agent actions https://dev.to/thegdsks/build-an-audit-trail-for-ai-agent-actions-23j7 | Someone will ask what your agents did | Building for people? Start at guide 1. Building for AI agents? Start at guide 5. Every guide links to the docs page for each concept it touches. | Control | What you call | What it does | Who enforces it | |---|---|---|---| | Delegation | theauth.delegate | Grants a subset of permissions with an expiry and a depth cap | theAuth, inside authorize | | Budget policy | theauth.policies.checkBudget | Reports whether a call would cross a limit | Your code | | Approval | theauth.approval.request | Stores a pending human decision | Your code | | Cost attribution | costs.recordCost | Records spend per agent, tool and chain | Your code, via alerts | Notice the last column. Only delegation and permission constraints sit inside authorize . Budgets and approvals are modules you call. I will repeat this, because that wrong assumption causes most of the trouble. You need Node 20 or newer, a package manager, and a TypeScript project. You also need to know what an agent identity is in theAuth. If you do not, read the agent identity page https://docs.theauth.dev/agents first. It takes five minutes. Install the package: pnpm add @glinr/theauth If you want a running app instead of a blank project, the quickstart https://docs.theauth.dev/quickstart has a one-command scaffolder. The examples below use SQLite so you can run them anywhere. One honest scope note before we start. theAuth is a good fit when you want identity, scoped permissions and an audit trail in one place, in your own process and database. theAuth is not a billing system. It will not stop a provider from charging you, and it will not cap spend at the network level. Everything here works because your code asks the library first and obeys the answer. Start with one instance and three agents. A planner holds the broad permissions. A summarizer starts with none and will receive access by delegation. A cleaner holds its own narrow permissions, including a delete that needs approval. js import { createTheAuth } from '@glinr/theauth'; const theauth = await createTheAuth { database: { provider: 'sqlite', url: 'theauth.db' }, approval: { ttl: 600, // seconds onApprovalNeeded: async request = { console.log approval ${request.agentId} wants to ${request.action} ${request.resource} ${request.id} , ; }, }, } ; const planner = await theauth.agent.create { ownerId: 'user-123', name: 'planner', type: 'autonomous', permissions: { resource: 'mcp:github: ', actions: 'read', 'write', 'comment' }, { resource: 'mcp:linear: ', actions: 'read', 'write' }, , } ; const summarizer = await theauth.agent.create { ownerId: 'user-123', name: 'summarizer', type: 'delegated', permissions: , } ; The ownerId must match a real user row, so create or look up that user first. The agent identity docs https://docs.theauth.dev/agents show the full lifecycle, including token rotation. Each agent also gets a bearer token that appears once and sits hashed at rest. Put it in your secret store the moment you see it. Why the delegated type for the summarizer? It signals intent. This agent exists to finish one task with borrowed access, then go away. Now the planner hands the summarizer read access to GitHub issues, for 30 minutes, with no room for further hops. js const chain = await theauth.delegate { fromAgent: planner.id, toAgent: summarizer.id, permissions: { resource: 'mcp:github:issues', actions: 'read' } , expiresAt: new Date Date.now + 30 60 000 , maxDepth: 1, } ; console.log chain.id, chain.depth ; // dlg ..., 1 Three details in the delegation docs https://docs.theauth.dev/delegation save real debugging time. First, the subset rule. The planner must hold every permission it delegates. Asking for a wider resource or an action it lacks throws an error. That is the right failure: a compromised planner cannot mint power it never had. Second, Each call checks maxDepth on its own. Later hops do not inherit it as a ceiling. To stop re-delegation, pass a small maxDepth on every delegate call in your code. If you rely on a default of 3 somewhere, a chain can grow deeper than you planned. Third, the subset check uses the delegating agent's own permissions. An agent that holds only delegated access cannot pass it on. If you want a middle agent to re-delegate, give it its own copy of the permissions. I find this surprising, but it errs on the safe side. Check what the summarizer can actually do: js const allowed = await theauth.authorize summarizer.id, { action: 'read', resource: 'mcp:github:issues', } ; // allowed.allowed === true const denied = await theauth.authorize summarizer.id, { action: 'write', resource: 'mcp:github:issues', } ; // denied.allowed === false authorize checks the agent's own permissions first, then falls back to delegated ones. You can list the delegated set directly: js const effective = await theauth.delegation.getEffectivePermissions summarizer.id ; console.log effective ; When a job goes wrong, you want one call that kills the whole subtree. await theauth.delegation.revoke chain.id ; Revoking a link also revokes active chains that start from the receiving agent. If the summarizer had delegated onward, those links die too. The next authorize call returns allowed: false . One limit to remember: revocation does not interrupt an operation already in flight. A long HTTP request keeps running until it finishes. Delegation controls what an agent may touch. It says nothing about how much it may spend. For that, theAuth has budget policies on theauth.policies . Read the budget policies page https://docs.theauth.dev/budget-policies once, because the model is specific. Here is the part people miss. authorize does not consult budget policies. Nothing happens unless your code calls checkBudget before the LLM call and recordUsage after it. Create two policies for the summarizer, a soft one and a hard one: await theauth.policies.create { agentId: summarizer.id, limits: { maxTokensCostPerDay: 800 }, action: 'warn', } ; await theauth.policies.create { agentId: summarizer.id, limits: { maxTokensCostPerDay: 1000, maxCallsPerDay: 500 }, action: 'block', } ; At 800 units the warn policy fires and checkBudget still returns allowed: true , with the policy attached so you can alert someone. At 1000 the block policy returns allowed: false . Now wrap your model call so the check is impossible to forget: async function guardedCall