Verifiable Agent Consent: Keep a Delegated-Approval Receipt Ledger Before the First Dispute

Turn "the customer approved it" into evidence: the receipt ledger columns, what Decagon's PACT receipt carries and omits, and a timed drill for agent disputes.

Hero illustration for verifiable agent consent: a chat bubble reading yes on the left labelled a claim, and a signed receipt slip on the right listing grant, principal, agent, scopes used and action, stamped verified and labelled evidenceHero illustration for verifiable agent consent: a chat bubble reading yes on the left labelled a claim, and a signed receipt slip on the right listing grant, principal, agent, scopes used and action, stamped verified and labelled evidence
A chat "yes" is a claim. A signed receipt with a verifier result is evidence.

The ticket says “I never approved that.” Attached is a cancelled order, a refund that went to the wrong card, and a transcript in which a customer’s personal agent wrote “My user confirms, please proceed.” Your support lead has to decide whether that sentence is consent or a forgery, and the only thing on file is a chat log any client could have typed.

Verifiable agent consent means you can answer that ticket from a signed record instead of a transcript. This playbook gives you the record: a delegated-approval receipt ledger, one row per action an agent takes under someone else’s authority, with the signature, the verifier result and a retention rule beside it. It closes with a dispute drill timed in minutes, because a ledger nobody can query under pressure is decoration.

Decagon’s newly open-sourced PACT spec is the reason to do this now. It defines a signed receipt with most of the fields you need, and it leaves the parts that matter in a dispute to you.

Decagon’s PACT receipt, Oct 6: the fields it signs and the gaps it leaves

On Oct 6, 2026 Decagon open-sourced the Personal Agent Consent & Trust Protocol, “co-developed and supported by Instinct”, under Apache-2.0. Decagon had first named it on Oct 1 in its Personal Agent Gateway announcement as a companion protocol still being refined with partners. The normative spec calls itself PACT 1.0 and builds on the Agent2Agent (A2A) protocol and OAuth 2.0.

Decagon blog post header titled Introducing the Personal Agent Consent and Trust Protocol (PACT), posted on October 6, 2026 by Harry Gao and Gram Liu, with an opening paragraph saying Decagon is open-sourcing PACT co-developed and supported by Instinct and joining the Personal Agent Protocol working group Screenshot: Decagon, “Introducing the Personal Agent Consent & Trust Protocol (PACT)” (Oct 6, 2026), captured Oct 7, 2026.

The mechanics, from the spec. The personal agent signs every request with a short-lived JWT (ES256 or RS256, expiry of 300 seconds or less, keys published at a JWKS URL). The customer signs in with the business, not with the agent, and ticks the scopes to grant through the OAuth device-authorization flow.

The business then issues a signed delegation token, valid for an hour at most, binding user, agent platform, brand and approved scopes. Every reply served under that token carries a signed receipt: a compact JWS whose claims are grantId, user, pa (the personal agent platform), brand, scopesUsed, actions (each with an optional argsHash) and ts. Decagon’s summary: “The personal agent never handles the customer’s login credentials.”

PACT specification page showing a receipt example with claims grantId, user, pa, brand, scopesUsed listing orders:read and orders:cancel, actions and ts, followed by the sentence jws is the compact JWS of claims and personal agents SHOULD verify and keep receipts Screenshot: PACT, “Specification - PACT” (undated spec page, section 5.6 Receipts), captured Oct 7, 2026.

Now what the spec does not say. Read the raw spec text and you will find no audit-log format, no provider-side retention rule, no consent-record format and no revocation endpoint; the only revocation language is a check that the grant has not been revoked, plus the provider’s ability to disable an agent, which returns 401. The jti claim is optional and providers “need not track replay”.

Receipt-keeping is a SHOULD, and it sits on the personal agent’s side. Decagon’s post uses the words consent and trust; “auditable” is our inference from the signed receipts, not a property the spec defines. The business, which signs the receipts and fields the dispute, is the party the spec asks to keep nothing.

Context you should carry. PACT is separate from the Personal Agent Protocol that Meta and Sierra announced the same day: the spec never mentions PAP, Decagon says it is joining the PAP working group “to help shape an industry standard”, and CMSWire’s partner roundup shows Meta listing Decagon as a PAP partner while Sierra does not.

The GitHub repo holds a TypeScript reference provider (Next.js and Postgres), a demo personal agent, Zod schemas, a client SDK and end-to-end conformance tests, and showed 94 stars and five commits at 03:32 UTC on Oct 8. The project site names no backers beyond what Decagon’s post says. Treat it as a days-old 1.0 spec with one named co-developer, not an industry standard; as read Oct 8, the spec still names no adopters and has no revocation or audit-log section.

Two name collisions to keep out of your tickets: an unrelated IETF individual draft, draft-valverde-oauth-pact, titled “PACT: Private Agent Consent and Trust Profile for OAuth 2.1 and CIBA” (IETF Datatracker), and the Pact contract-testing tool at pact.io. Neither is Decagon’s protocol.

## Why a transcript "yes" fails as agent approval evidence

A human clicking “Confirm” on your own page leaves evidence you control: your session, your button, your log. A personal agent relaying “my user approved” leaves a string. Anyone who can reach your chat endpoint can produce the same string, and an agent with a confused plan can produce it sincerely.

The transcript proves that a sentence arrived. It proves nothing about who stood behind it.

That gap is old inside a team, where an annotation is not an approval. The new part is that the approver now sits in another organization, talks to you through someone else’s software and can open a chargeback. Cross-org approval needs a record both sides can check without trusting each other’s logs, which is exactly what a signature is for.

The runbook: build the delegated-approval receipt ledger

Six steps. The first three decide what the ledger stores and how a row gets verified; the last three set rules, retention and the drill.

Step 1: Grade every approval style you accept today

Before you design a row, list every way an agent can currently tell you “approved” and grade what each one actually evidences. Most support stacks accept all three styles below at once, often on the same queue.

Illustrative matrix for verifiable agent consent: eight evidence fields as rows and three approval styles as columns, with full bars where a field is present, half bars where partial and thin gray bars where absent; chat yes shows mostly absent, an OAuth grant shows principal, agent, scope and time, and a signed PACT receipt shows all but amount limits and argument binding in fullIllustrative matrix for verifiable agent consent: eight evidence fields as rows and three approval styles as columns, with full bars where a field is present, half bars where partial and thin gray bars where absent; chat yes shows mostly absent, an OAuth grant shows principal, agent, scope and time, and a signed PACT receipt shows all but amount limits and argument binding in full Illustrative assessment: which ledger fields each approval style can evidence on its own. PACT cells follow the spec text read Oct 7, 2026.

The pattern is the point, not the cell count. A chat “yes” evidences a timestamp and little else. An OAuth grant tells you who authorized which client for which scopes, but not which action ran under it.

A PACT-style receipt binds the action to the grant and signs both, and still carries no amount limit: none of the receipt fields or delegation-token bindings caps spend or count, so those caps are yours to record. Rows where the style column says “chat” are your dispute backlog waiting to happen.

Step 2: Write the ledger with one row per delegated action

One row per action an agent took under someone else’s authority, whether a customer’s agent at your desk or your own agent acting for a customer. Limits, verifier, retention and dispute status are fields no receipt carries; you fill them at write time. These rows are illustrative.

Receipt ID Principal (customer) Agent identity Scope granted Limits (amount · count · expiry) Issued at (UTC) Signature and verifier Action it authorized Retention Dispute status
rcpt-7Q2F (hash of JWS) acct 48213, signed in with the business pa.example-agents.com, JWT kid pa-2026-09 orders:read, orders:cancel refund ≤ order value · 1 cancel · grant exp 14:07 2026-10-07 13:12:44 JWS ES256, brand key brand-k3; verified by ledger service 13:12:45, pass cancel_order #A-5531, argsHash sha256:9e1c… 18 months (money moved) open: customer disputes, Oct 9
rcpt-8M0A acct 10977 pa.example-agents.com, kid pa-2026-09 orders:read read only · n/a · exp 09:40 2026-10-07 08:41:02 JWS ES256, brand-k3; verified 08:41:02, pass lookup_orders, no args hash 90 days none
chat-55C1 (no receipt) acct 30418 (claimed in chat) “assistant from MyHelper” (unsigned) none on file none on file 2026-10-06 17:20:10 none; logged as unauthorized change_address refused, routed to human 12 months (refusal record) none
rcpt-9T4K acct 22760 agent.other-platform.ai, kid op-01 billing:read, plan:change plan change ≤ 1 tier · 1 · exp 11:58 2026-10-07 11:03:51 JWS RS256, brand-k3; verified 11:03:52, fail: scopesUsed exceeds grant downgrade_plan blocked 18 months (incident) incident INC-212

Three conventions make the table hold up later. Store the receipt ID as a hash of the full JWS, so a dispute can match the bytes the agent kept against the bytes you kept. Keep “scope granted” from the delegation token and “scopes used” from the receipt as separate facts, since the gap between them is your most useful alarm. And never leave the signature column blank: “none; logged as unauthorized” is a valid value, an empty cell is not.

Which scopes exist, and which actions may never be delegated at all, is the front-door decision covered in the personal agent front-door register. The ledger records what was granted; it does not decide it.

Step 3: Verify on write, not on dispute

A signature you check six months later may no longer be checkable. Agent platforms rotate signing keys, JWKS endpoints change, and nothing in the spec as read Oct 7 asks anyone to keep the old keys. So the verifier runs when the row is written and stores its own result next to the evidence.

Flow diagram for an AI agent authorization audit trail with seven nodes: the agent sends a signed request, the customer grants scopes by signing in with the business, the business issues a delegation token of one hour or less, the action runs, a signed receipt returns with the reply, a verifier checks signatures, scopes and limits, and the result is written to the ledgerFlow diagram for an AI agent authorization audit trail with seven nodes: the agent sends a signed request, the customer grants scopes by signing in with the business, the business issues a delegation token of one hour or less, the action runs, a signed receipt returns with the reply, a verifier checks signatures, scopes and limits, and the result is written to the ledger The verifier sits between the receipt and the ledger, and its result is stored, not recomputed later.

An illustrative ledger entry, as the verifier writes it:

receipt_id: sha256:4b7e…          # hash of the full compact JWS
grant_id: a2agrant_7Q2F
principal: acct-48213
agent:
  platform: https://pa.example-agents.com
  request_jwt_kid: pa-2026-09
  jwks_snapshot: s3://ledger-keys/pa.example-agents.com/2026-10-07.json
scope_granted: [orders:read, orders:cancel]   # from the delegation token
scopes_used: [orders:cancel]                   # from the receipt
limits:
  amount_max: order_value
  count_max: 1
  grant_expires: 2026-10-07T14:07:00Z
action:
  tool: cancel_order
  target: A-5531
  args_hash: sha256:9e1c…
verifier:
  checked_by: ledger-verifier v1.4
  checked_at: 2026-10-07T13:12:45Z
  receipt_signature: pass (brand-k3)
  request_signature: pass (pa-2026-09)
  scopes_within_grant: pass
  within_limits: pass
  duplicate_of: null
retention_class: money-moved
dispute_status: none

Two fields here are doing quiet work. jwks_snapshot stores the agent platform’s public keys as they were when you verified, so the check is repeatable after rotation. duplicate_of exists because the spec lets providers skip replay tracking; your ledger dedupes on the receipt hash and the request JWT anyway, and a duplicate gets its own row rather than silently merging.

Where the action moved money, the limits column should match what was signed before spend, the territory of the checkout agent pre-authorization checklist. The ledger is the record that survives after that checklist did its job.

Step 4: Apply three ledger rules and nothing cleverer

The ledger classifies evidence; it does not run your consent logic, which belongs to the fail-closed consent test matrix. Three rules are enough:

  1. No verifiable receipt, no authorization. A delegated action with no signed record is logged as unauthorized, whatever the transcript says. The row still exists, because refusals are evidence too.
  2. Scopes used beyond scopes granted is an incident, not a warning. The verifier result is “fail”, the row gets an incident number and the agent platform’s other rows from the same day get pulled for review.
  3. A row is immutable once written. Dispute outcomes append to the dispute-status column with a timestamp; nobody edits the evidence columns. Write-once storage is cheap; credibility after an edit is not.

Keep human reviewers out of the hot path here. A ledger that sends every row to a person recreates the fatigue problem described in approval queue hygiene; people should see failures and disputes, not passes.

Step 5: Set retention by what the action did

The spec sets no provider retention, so you write the policy. The classes below are illustrative defaults; your counsel and your payment processor set the floor, and the ledger should sit above whichever dispute or chargeback window is longest for that class.

Action class Keep Illustrative period Why
Money moved (refund, purchase, plan change) Full JWS, delegation token, both JWKS snapshots, verifier record 18 months Outlives typical card dispute windows with margin
Account changed (address, contact, preferences) Same as above 12 months Takeover disputes surface late
Read only Receipt hash, verifier result, scopes used 90 days Enough to answer “what did it see”; keeps personal data short-lived
Refused or unauthorized Request, claimed identity, refusal reason 12 months Proves the control held; feeds bot triage
Incident (scope or signature fail) Everything, plus the platform’s other rows that day Until incident closed + 12 months Evidence for the platform conversation

Store keys and receipts apart from the application database, under a role the support agent cannot write to. Retention you cannot prove you kept is retention you did not keep.

Prefer the args hash over the arguments themselves wherever the receipt offers one. A hash lets you prove later that the action which ran matches the action which was signed, without keeping a second copy of a delivery address or an account number for eighteen months. Keep the plain arguments in the application log under its normal retention, and let the ledger hold only the fingerprint. When the receipt carries no argsHash, which the spec allows, compute one yourself from the request at write time and mark the row “hash computed by provider”, so nobody later mistakes your fingerprint for one the agent signed.

Step 6: Run the dispute drill, timed

The drill answers one question: from the ledger alone, can you prove who approved this action, within the time a support lead can wait? Run it on a real dispute and once a month on five random rows. Every gap becomes a finding with an owner.

Worked scenario (illustrative timings). At 09:05 on Oct 9 the customer behind acct 48213 writes “I never approved cancelling order A-5531.” The order was cancelled at 13:12 on Oct 7 through a personal agent. The pulls, in order:

# Pull From Target time Pass when
1 Receipt row by order ID and principal Ledger 2 min One row, receipt hash present
2 Grant: delegation token and issue time Ledger evidence store 3 min Token’s user = acct 48213, scopes include orders:cancel, not expired at 13:12
3 Action log: tool, target, args hash Application log, matched by receipt hash 5 min Args hash in log = args hash in receipt
4 Agent identity: request JWT kid against stored JWKS snapshot Key archive 5 min Platform key valid at 13:12, signature verifies
5 Verifier result: re-run checks on stored bytes Ledger verifier 5 min Same result as at write time
6 Decision and reply Support lead 10 min Customer gets grant time, scopes and agent platform in plain words

Thirty minutes end to end, in this illustrative run. If step 2 shows the customer signed in with the business and granted orders:cancel at 13:07, the reply says so, names the agent platform the customer authorized, and points them to that platform for what their agent decided to do. If step 3 fails because the args hash does not match, the dispute flips: the action that ran is not the action that was signed, and that is your incident, not the customer’s mistake.

The awkward middle case is the common one: the grant was real, the receipt verifies, and the customer still did not want the outcome because their agent misread the request. Your ledger cannot fix that, and should not pretend to. What it does is move the conversation to the right place, with a dated grant the customer can show their agent provider, instead of leaving your support team to argue about a chat log.

Score the monthly run as five rows times six pulls, thirty checks. Any check that needs someone to ask an engineer, open a vendor console or “find the old key” is a fail, even if the answer eventually turns up.

Where receipt ledgers break under a dispute

What breaks Signal you would see First action
Old receipts stop verifying after the agent platform rotates keys Drill step 4 fails on rows older than the rotation date Backfill JWKS snapshots from the platform’s published history if any; start snapshotting on every write
Only the agent kept the receipt, because the spec asked only the agent Disputed rows have a receipt hash but no stored JWS on your side Store the full JWS at write time; mark rows without it as evidence-incomplete
Replayed receipt or request counted as a second authorization Two rows share a receipt hash or a request JWT inside its 300-second window Enable dedupe on receipt hash plus request JWT; review the platform’s other rows that hour
Grant revoked on your side but agent keeps acting until token expiry Rows with timestamps after a revocation note in the customer record Reject the grant ID at your verifier now rather than waiting for token expiry; if the platform keeps sending, disable the agent at the provider, which the spec says returns 401; record both times in the ledger
Chat-only approvals quietly accepted for money-moving actions Rows with “none” in the signature column and an action in the money-moved class Block that action class without a receipt; route the contact to a human
Ledger edited after a dispute opens Evidence-column hash differs from the write-time hash Restore from write-once storage; restrict the editing role

The second row is the one teams miss. PACT’s SHOULD points at the personal agent, so a business that implements the provider side to the letter can end up with signed receipts it never stored.

The same ledger for your own fleet’s approvals

“The customer approved it” and “the operator approved it” are the same evidence problem. When your own agents act for a customer, or when an engineer approves a production change an agent proposed, the row shape does not change: principal, agent identity, scope, limits, signature, verifier, action, retention, dispute status. An approval you cannot replay from a signed record is a claim, not a control.

That is why the ledger belongs beside the run record, not inside the support tool. A fleet that can replay what its agents did but cannot show who authorized each step has half an audit trail. Put both behind one query and the next dispute starts with a receipt instead of a meeting. When an agent’s traffic is still unclassified, the support-queue triage table decides whether a row gets written at all.

FAQ

Verifiable agent consent is an approval you can check without trusting the agent that reports it: a signed record binding the customer, the agent platform, the granted scopes and the action that ran. Decagon’s PACT 1.0 spec defines one form, a signed receipt per reply, but leaves storage, revocation and audit format to the business.

Does the PACT protocol provide an audit trail for AI agents?

Partly. PACT signs a receipt for every reply served under a customer’s delegation, listing grant, user, agent platform, brand, scopes used, actions and time. The spec defines no audit-log format, no provider retention, no revocation endpoint and no required replay tracking. Treat the receipts as raw evidence and build the ledger yourself.

How should a business handle an “I never approved that” agent dispute?

Pull, in order: the receipt row, the delegation grant, the action log matched by args hash, the agent’s signing key as stored at the time, and a re-run of the verifier. If everything matches, show the customer the grant. If the args hash differs, the dispute becomes your incident.

Sources

YOU'RE THROUGH THIS ONE.

Keep connecting the dots.

Back to the library