# ADR 061: Agent Auth for Delegated Recipe Access

- HTML version: https://robbiepalmer.me/projects/recipe-site/adrs/061-agent-auth
- Project: Recipe Site (https://robbiepalmer.me/projects/recipe-site.md)
- Status: Proposed
- Date: 2026-07-25

# Summary

Add the [Better Auth Agent Auth plugin](https://better-auth.com/docs/plugins/agent-auth) as an
**experimental, delegated machine-access layer** for the recipe site.

An approved agent gets its own keypair, identity, narrowly named capabilities, short-lived
audience-bound JWTs, audit history, and independent revocation. It never receives the user's browser
session or a general-purpose API key. The existing application authorization model remains
authoritative: an agent grant can reduce a user's permissions, never expand them.

Recipe, pantry, cook-log, and insight reads are low-risk and should use lightweight, durable grants.
Pantry writes may include bulk reconciliation once every agent change is grouped, attributable, and
undoable. Recipe imports retain stronger controls because they ingest untrusted content and consume
paid processing. MCP may expose the same capabilities as tools, but is a complementary transport
rather than the identity or authorization boundary.

# Context

The site's machine-readable pages, recipe JSON twins, and API already let agents inspect public
content. The missing case is a user deliberately allowing an agent to work with private, mutable
state:

* answer "what can I cook?" from the user's recipes, pantry, dietary settings, and recent cook log
* add, remove, or reconcile pantry items after shopping or cooking
* log meals, correct log entries, and explain rotation, variety, ingredient, or cuisine patterns
* query the recipe corpus by ingredient, provenance, parse quality, or coverage without direct
  database access
* submit a newly discovered URL, text, or image to the recipe-ingestion workflow, monitor it, and
  prepare the resulting draft for review

Giving every agent the user's session or one broad bearer token makes requests attributable only to
the user or host application. It also makes per-agent scoping and revocation difficult. The
[Agent Auth Protocol](https://agentauthprotocol.com/docs/introduction) instead models each runtime
agent as a principal with its own grants and lifecycle.

The protocol and plugin are explicitly draft/unstable. This ADR therefore chooses a bounded
integration and an escape hatch, not an irreversible public contract.

# Decision

Use `@better-auth/agent-auth` beside the Better Auth deployment selected in
[ADR 032](/projects/recipe-site/adrs/032-better-auth), initially in **delegated mode only**.
Autonomous registration is out of scope for private recipe-site data.

Expose `/.well-known/agent-configuration`, the registration and approval endpoints, capability
listing, execution, revocation, and a user-facing approval/revocation screen. Agent hosts must be
allowlisted during the preview; dynamic host registration is disabled by default.

Define capabilities by hand. Do not turn the complete OpenAPI surface into agent capabilities: the
plugin's OpenAPI adapter is useful implementation machinery, but automatic exposure would make a
routine API addition an authorization change.

```mermaid
sequenceDiagram
participant A as Agent + keypair
participant Auth as Better Auth Agent Auth
participant U as User
participant API as recipe-api
participant D as Domain authz + data

A->>Auth: Discover and request named capabilities
Auth->>U: Show agent, host, scope, constraints and expiry
U->>Auth: Approve or deny
Auth-->>A: Per-agent grant (no user session/API key)
A->>API: Capability call + short-lived signed JWT
API->>Auth: Verify signature, audience, expiry, replay and grant
Auth-->>API: Agent identity + delegated user + active grants
API->>D: Intersect grant with user/household policy
D-->>API: Result + domain audit event
API-->>A: Least-privilege response
```

## Initial Capability Surface

Capabilities are task-oriented rather than table- or CRUD-oriented. Names below are the intended
contract; exact schemas belong in the API specification.

| Capability                        | Initial policy                | Purpose and constraints                                                                                                                                                                                              |
| --------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `recipes.search` / `recipes.read` | Read; low risk                | Search and inspect recipes visible to the delegated user, including provenance and normalized ingredients. Public-only use remains unauthenticated.                                                                  |
| `recipes.dataset.inspect`         | Read; low risk                | Return aggregate coverage, provenance, parse-quality, and ingredient/cuisine statistics through a bounded query API. Never expose Postgres, DVC/R2 objects, prompts, raw user uploads, or other users' private rows. |
| `pantry.read`                     | Read; low sensitivity         | Read the delegated user's or explicitly selected household's pantry. Household membership is checked on every call, but the contents do not warrant per-call approval.                                               |
| `pantry.reconcile`                | Reversible write              | Apply one or many additions, removals, or quantity corrections as one idempotent change set. Return the change-set ID and an undo preview.                                                                           |
| `cook_log.read`                   | Read; low sensitivity         | Read a bounded date range and only fields needed for the task.                                                                                                                                                       |
| `cook_log.append`                 | Reversible write              | Record one or more cook events with recipe, date, servings, diners, and idempotency key. Agent-authored changes use the same undo model as pantry changes.                                                           |
| `cooking_insights.read`           | Derived read; low sensitivity | Return server-computed rotation, recency, variety, pantry-fit, and ingredient/cuisine trends. Do not give the agent unrestricted SQL.                                                                                |
| `recipe_import.create`            | Costed write; explicit grant  | Submit a URL, text, or previously uploaded image with source/provenance metadata. Enforce quotas, URL/SSRF controls, file limits, and import idempotency.                                                            |
| `recipe_import.status`            | Read; private                 | Read progress, diagnostics, and the generated draft for jobs owned by the delegated user.                                                                                                                            |

An agent may discover, queue, monitor, and suggest corrections to an imported recipe. Publishing the
draft remains an explicit user action, preserving the review boundary in
[ADR 049](/projects/recipe-site/adrs/049-cloudflare-workflows-recipe-ingestion).

Bulk export, recipe deletion, household membership, sharing/visibility changes, quota changes,
ground-truth promotion, raw ML-corpus access, and secret/admin operations are not agent capabilities.

## Reversible Writes

Limiting every pantry call to one item reduces throughput, not blast radius: a faulty or hostile
agent can still make many bad calls. The real control is fast, reliable recovery.

Before enabling `pantry.reconcile`, store each agent mutation as an immutable change set:

* actor user, agent, and host; target household; capability; reason; timestamp; and idempotency key
* one or more item changes with stable item IDs, before/after values, and row versions; a removal
  retains a tombstone or equivalent absence-revision record carrying the removal's `after` version
* a transaction that updates the current pantry projection and appends the immutable change set
  atomically
* an undo operation that appends a compensating change set rather than deleting history

Undo must use compare-and-swap against the original change's `after` version. For a present item, the
compensating change applies only when the current row version matches. For a removed item, restoration
applies only when its current tombstone or absence revision matches the removal's `after` version;
the restore clears that absence marker and writes a new revision. A missing marker, a recreated item,
or any different revision means the state changed after removal, so the UI must show the existing
three-way conflict preview instead of restoring automatically. A batch reconciliation can be undone
as one unit or item by item.

An audit log alone is insufficient: it explains what happened but may not contain enough structured
state to reverse it safely. The domain change set is the recovery mechanism; the security audit
event records access and outcome. This does not require making the whole application event-sourced.
Postgres can keep normal current-state tables plus an append-only mutation ledger.

## Enforcement and Operations

The capability grant is only one input to authorization:

```text
allowed = valid agent proof
       ∩ active capability grant and constraints
       ∩ delegated user's current permissions
       ∩ resource ownership / household membership
       ∩ product quota and safety policy
```

* Use short expiries, audience binding, `jti` replay protection, and request binding supported by
  the plugin; do not issue static personal API keys.
* Store the agent's public key and grant metadata server-side, not the agent's private key.
* Show the agent and host identity, target account/household, exact capabilities, constraints, and
  expiry at grant time. Low-sensitivity reads may receive longer-lived grants with no per-call
  prompt; writes and cost-incurring imports are separate capability grants.
* Re-check household membership and resource visibility at execution time. Revoking sharing or
  removing a member must take effect even while the agent grant is still active.
* Require idempotency keys and reversible change sets for state mutations. Preserve the ingestion
  quota/reservation rules.
* Emit immutable events for registration, approval, denial, execution, failure, expiry, and
  revocation. Domain audit records must identify the user, agent, host, capability, target resource,
  outcome, and correlation/idempotency key without logging sensitive payloads.
* Provide a settings page to inspect and revoke individual agents. Default grants expire; revocation
  must not invalidate the user's other agents or human sessions.
* Rate-limit by user, agent, host, capability, and source IP. Treat recipe URLs and agent-supplied
  text as untrusted input, including prompt-injection content.

# Standards Landscape: Complementary Layers

As of July 2026, no single agent-authentication proposal is both stable and broadly interoperable.
Most of the technologies below are **not direct competitors**:

* MCP standardizes agent-to-tool interaction and OAuth authorization for the client/host.
* ID-JAG/XAA carries user delegation across identity and application trust domains.
* OAuth, RFC 9728, device authorization, CIBA, and DPoP-style proofs are foundation primitives.
* Agent Auth adds per-runtime-agent identity, capabilities, request proofs, and lifecycle.
* WorkOS Agent Registration is the closest overlapping alternative because it also registers,
  scopes, attributes, and revokes agent identities.

These layers can converge or compose. The decision is which agent-specific control plane to
implement now, not which surrounding standard should "win."

| Approach                                           | What it standardizes                                                                                                                                                                                                                                                                                                      | Fit here                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | Decision                                                                                                                                  |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| **Better Auth Agent Auth Protocol (draft)**        | Per-agent key identity, discovery, delegated/autonomous registration, named capability grants and constraints, approval, signed request proofs, audit, and independent lifecycle                                                                                                                                          | Best semantic fit and smallest integration because Better Auth is already the identity layer. Official OpenAPI/MCP adapters preserve transport choice. Main risk is a young, Better Auth-led draft with limited independent adoption.                                                                                                                                                                                                                                       | **Adopt experimentally**, behind an allowlist and with explicit capabilities.                                                             |
| **WorkOS `auth.md` / Agent Registration**          | Agent-readable onboarding via the open [`auth.md` proposal](https://workos.com/blog/agent-registration-with-auth-md) and RFC 9728 discovery; anonymous or claimed/verified agent registrations are exchanged for scoped OAuth tokens or API keys. The verified flow can use ID-JAG.                                       | This is the closest alternative agent control plane: strong OAuth reuse, per-registration identity/revocation, and useful "an unknown agent can register" semantics. However, the [AuthKit implementation currently requires WorkOS enablement](https://workos.com/docs/authkit/agent-auth), moves policy into a hosted dashboard, and maps trust levels to environment-wide OAuth scopes rather than Agent Auth's per-capability schemas, constraints, and request proofs. | Do not add a second control plane. Track `auth.md` and consider a compatibility discovery/registration adapter if adoption emerges.       |
| **MCP authorization**                              | OAuth-based authorization between an MCP client/host and an HTTP MCP server, including protected-resource discovery and client registration                                                                                                                                                                               | MCP is useful for tool discovery and invocation. Its [authorization specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) identifies the client and protects the server, but does not standardize a distinct identity, grant, and lifecycle for each runtime agent behind that client. Three agents using one host token remain one OAuth client from the server's perspective.                                                      | Compose it with Agent Auth when MCP clients are needed: MCP is the tool transport; Agent Auth is the per-agent identity/capability layer. |
| **OAuth 2.x, API keys, or service accounts alone** | Application/user delegation or machine identity with scopes                                                                                                                                                                                                                                                               | These are mature foundation primitives, but a shared token collapses host, user, and runtime-agent attribution. Custom per-agent records would recreate much of the chosen protocol.                                                                                                                                                                                                                                                                                        | Keep OAuth for human/app flows and as a protocol building block; do not hand agents user sessions or broad shared API keys.               |
| **ID-JAG / Cross App Access (emerging)**           | The IETF [Identity Assertion JWT Authorization Grant](https://www.ietf.org/archive/id/draft-ietf-oauth-identity-assertion-authz-grant-04.html) carries user delegation across trust domains. [Okta/Auth0 XAA](https://auth0.com/docs/secure/call-apis-on-users-behalf/xaa) adds enterprise-IdP policy and token exchange. | Promising for enterprise agents calling independent SaaS products, and it underpins WorkOS's provider-verified path. It assumes cross-domain IdP/resource trust and identifies a requesting client acting for a user; it does not replace per-agent capabilities and lifecycle.                                                                                                                                                                                             | Treat as a future cross-domain delegation bridge that could feed an Agent Auth registration or grant.                                     |

The architecture should therefore stay layered: shared OAuth/RFC 9728 discovery and delegation
primitives, agent-specific identity/capability semantics, and REST/OpenAPI or MCP execution. Keep
domain capability handlers protocol-neutral so a future adapter does not rewrite pantry, cook-log,
recipe, or import authorization.

# Rollout

1. **Useful reads:** allowlisted hosts; discovery, approval/revocation UI, audit events, and durable
   grants for recipe, pantry, cook-log, and insight reads.
2. **Reversible mutation foundation:** add atomic change sets, the append-only mutation ledger,
   concurrency-aware undo, history/undo UI, idempotency, and attribution.
3. **Full pantry management:** enable batch `pantry.reconcile`, then reversible cook-log writes.
4. **Import management:** add costed import creation/status; keep final draft publication human-only.
5. **Transport adapters:** add MCP when a target client requires it; the REST/OpenAPI
   capability path remains canonical.
6. **Public availability:** follow interoperability testing and usable undo/revocation controls.

# Consequences

## Positive

* Agents can perform useful personal workflows without borrowing the user's session.
* Every action is attributable to one user, host, and runtime agent; one agent can be revoked alone.
* Named, constrained capabilities are smaller and more reviewable than broad API scopes.
* The same domain handlers can serve direct HTTP/OpenAPI callers and optional MCP tools.
* The decision reuses Better Auth and the existing recipe API instead of adding a hosted identity
  control plane.

## Negative

* The protocol and plugin are unstable, with migration and interoperability risk.
* Approval, revocation, capability UX, audits, abuse controls, and incident response become product
  responsibilities; the plugin does not render the approval UI.
* Pantry and cook-log reads are low-sensitivity personal state, but still require ordinary ownership
  enforcement. Imports carry the materially higher SSRF, prompt-injection, provenance, and
  cost-abuse risks.
* Reversible writes add mutation-ledger storage, transaction, conflict-resolution, retention, and
  undo-UX complexity.
* Two authorization layers must remain correct: agent grants and application/household policy.
* MCP clients will not automatically understand Agent Auth unless they adopt the protocol or use an
  adapter.

# When To Revisit

* Agent Auth changes incompatibly, stalls, or fails to gain support from the agent hosts users want.
* WorkOS `auth.md`, ID-JAG/XAA, an IETF standard, or MCP gains interoperable per-agent identity and
  lifecycle semantics.
* The project needs third-party/enterprise agents across separate identity domains.
* Auditing or policy complexity justifies a dedicated authorization service.
* A security review finds that safe write access cannot be made proportional to this project's
  operating capacity.

---

Markdown index of this site: https://robbiepalmer.me/llms.txt
