Agent identity

Identity for AI agents

An agent is not a user and not a shared API key. In theAuth it is an identity with an owner, its own kv_ bearer token, scoped permissions, an optional expiry and an audit row for every decision.

agent recordactive
id
agt_…
token
kv_…shown once, SHA-256 hash stored
owner
user-123
type
autonomous
permissions
mcp:github:* [read]file:prod-data/* [delete] requireApproval
authorize(agent.id, read, mcp:github:repos) => allowed, auditId aud_…

Anatomy

What the token is, and is not

Tokens are kv_ plus 32 random bytes. The plaintext is returned once at creation. Only a SHA-256 hash is stored, so a database dump does not reveal a live token. Rotation issues a new token and invalidates the old one.

type: autonomousRuns unattended with the permissions declared at creation.
type: delegatedReceives permissions through a delegation chain. Suited to short-lived sub-agents.
type: serviceLong-lived identity for infrastructure such as an MCP server.
limitsDefault cap of 10 active agents per user, configurable with agents.maxPerUser.
revokePermanent. Later authorize calls return allowed: false.

Permissions

Wildcard matching, then constraints

A permission is a colon-separated resource pattern plus actions. A * segment matches everything from that position on. Constraints must all pass: maxCallsPerHour, allowedArgPatterns, timeWindow (UTC), ipAllowlist (CIDR) and requireApproval.

Matching behavior from the policy source (policy/abac.ts)
PatternResourceMatch
mcp:github:*mcp:github:reposYes
mcp:github:*mcp:github:repos:commentsYes, * covers the remaining segments
mcp:github:*mcp:slack:channelsNo
mcp:github:reposmcp:github:repos:commentsNo, exact patterns must match in length
*anyYes

How a delegation chain is checked

Narrow at every hop, capped by depth

An orchestrator hands a subset of its permissions to a sub-agent. Two checks run when the link is created, and more run on every call.

A human owner creates an agent, and the agent can hand a subset of its permissions to a sub-agent. Each hop adds one to the delegation depth, which is capped by maxDepth (default 3), and revoking a link also revokes every link created downstream of it.
  1. plannerautonomous
    mcp:github:* [read, write, comment]
    delegate(planner, code-reviewer, mcp:github:pulls [read, comment], maxDepth 2)

    Subset. The parent must hold every permission it hands over. Wider resources or new actions are rejected.

  2. code-reviewerdelegated, depth 1
    mcp:github:pulls [read, comment]
    delegate(code-reviewer, diff-fetcher, ..., maxDepth 2)

    Depth. New depth is the deepest incoming link plus one. It must not exceed this call's maxDepth (default 3). Depth 2 passes.

  3. diff-fetcherdelegated, depth 2
    mcp:github:pulls [read]
    delegate(diff-fetcher, another-agent, ..., maxDepth 2)

    Rejected. Depth 3 exceeds the maximum of 2, which prevents unbounded chains.

  4. another-agentno link created

On every authorize() call

  1. AStatus. The agent must exist and be active, not revoked or expired.
  2. BOwn permissions. Pattern, action and constraints are evaluated first.
  3. CDelegated permissions. If B denies, grants from active, unexpired chains are tried.
  4. DAudit. The decision is written and its auditId returned.
Revoking a link also revokes every link created downstream of it. It takes effect on the next authorize call, not on operations already running. Delegated grants carry resource and actions only. See delegation docs.

Sensitive actions

Approval, budgets and audit

  1. Denied until approved

    A permission with requireApproval: true is denied. Your app catches the denial.

  2. Request created

    approval.request() persists it with a default TTL of 5 minutes and fires your webhookUrl or onApprovalNeeded.

  3. Human decides

    Your UI calls approval.approve() or deny(). Delivering the notification is your app's job.

  4. Agent retries

    The retried action passes the permission check once the request is approved.

  • Budget policies cap token cost and call counts per agent, user or tenant. On breach: warn, throttle, block or revoke. Budget docs.
  • Audit trail. Each entry records agent, owner, action, resource, parameters, result (allowed, denied, rate_limited), duration and timestamp. Export as JSON or CSV. Audit docs.
  • Trust score. Each agent gets a 0 to 100 score from its age, call volume and audit history. See trust scoring. Automatic anomaly detection is not shipped: the anomaly.detected event name is reserved, but nothing emits it yet.
Honest limit

Approval requests are stored by theAuth, but it ships no approval UI and does not deliver the notification. You build that step.

Create and authorize

Create an agent, authorize an action

The same calls run on Postgres, SQLite, MySQL or D1. Go has its own agent model on top of OAuth client credentials, documented in the Go package.

agent.ts
import { createTheAuth } from "@glinr/theauth";

const auth = await createTheAuth({
  database: { provider: "postgres", url: process.env.DATABASE_URL },
});

// Create an AI agent with scoped MCP permissions
const agent = await auth.agent.create({
  ownerId: "user-123",
  name: "github-reader",
  type: "autonomous",
  permissions: [{ resource: "mcp:github:*", actions: ["read"] }],
});

const result = await auth.authorize(agent.id, {
  action: "read",
  resource: "mcp:github:repos",
});
// { allowed: true, auditId: "aud_..." }
Your code calls authorize() for an agent: theAuth checks the agent's permissions, writes the decision to the audit log and returns allowed plus an auditId before the action runs on your MCP server or API.

Compared

Agent identity, shared API keys, human sessions

General patterns, not claims about any vendor. A shared key is often the right call for one service talking to one API you control.

QuestionShared API keyHuman sessiontheAuth agent
Who is it?Whoever holds the stringA person at a browserOne named agent with an owner
ScopeWhatever the key allows, usually coarseThe user's full rightsResource and action patterns plus constraints
Handing work to a sub-agentCopy the keyNot designed for itNarrower delegation with a depth cap
Attribution in logsThe key, not the callerThe user, not the agentPer agent and owner on every decision
Stopping one callerRotate for everyoneEnd the sessionRevoke or rotate one agent
Human sign-offBuild it yourselfThe user is already presentrequireApproval constraint

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/theauth
  • go get github.com/glincker/theauth-go
Or skip hosting with theAuth Cloud