Skip to content

Budgets: the allocation model, omitted cost, required, and billing accounts - #102

Merged
dickhardt merged 13 commits into
mainfrom
budgets-allocation
Aug 20, 2026
Merged

Budgets: the allocation model, omitted cost, required, and billing accounts#102
dickhardt merged 13 commits into
mainfrom
budgets-allocation

Conversation

@dickhardt

@dickhardt dickhardt commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Review of -01 from @polvi-ish, who runs AuthGravity as a PS, tokenpony as the resource, and Harness News as an agent against an earlier budget draft. One framing gap and three concrete defects.

The framing gap

The granted budget is an allocation, not the person's ceiling. The PS holds a ceiling the agent is never told, and each auth token carries a slice of it. When the slice runs out — or the hour expires, whichever comes first — the agent returns, and the PS decides again with consumption records in hand.

That was the design throughout and it was nowhere in the document. The reviewer read it end to end and concluded a durable grant was missing, then proposed adding one. That is the correct reading of what was on the page.

Now stated in three places:

  • Introduction — one paragraph, because that is where the misreading happened.
  • PS Token Endpoint — expanded from "no new request parameter is defined" into what the PS is deciding (#ps-decision), the four inputs it reads (#ps-inputs), and its six responses (#ps-responses).
  • #why-not-the-ceiling — the termination argument. Nobody knows at mission approval what the work will cost; a figure fixed up front is either too small to finish or too large to be a control.

#token-scope was rewritten from a disclaimer about the absent grant identifier into a statement of the mechanism, including the property that expiry is proportional to time and exhaustion to spend, so the check-in interval tracks whichever moves faster. A cheap mission reports hourly; an expensive one reports in minutes; nobody configures it.

requirement=clarification is named as a PS response. The PS previously had three moves on a budget escalation: grant less, decline, or terminate the mission. Granting less is silent — the agent cannot distinguish a PS applying pressure from a resource lowering its own offer. Clarification is the base protocol channel that lets the PS ask the agent to account for the spend before deciding. Nothing new to specify.

Three defects

1. cost had no carrier on some streamed responses. The member could go in the header or a trailer, and MUST NOT omit from both. A resource metering a streamed response on a runtime with no trailer support can use neither: cost is unknown when headers are written, and the trailer never happens. Every such response was non-conformant, and the runtimes in question are Workers, Deno, Bun, and undici.

New #cost-omitted permits omitting it, makes reserved REQUIRED in that case, and gives the recovery: cost = previous remaining + reserved − current remaining. The subtraction works because remaining is already net of reservations. Until the next response arrives the agent applies #ambiguous-failure, which already handles this exact shape.

Also removed the sentence in #ambiguous-failure claiming a resource emitting usage in its stream "is not excused from the trailer" — it contradicted the trailer's SHOULD and was read by the reviewer as the requirement.

2. required member on AAuth-Budget. A resource refusing under reason=insufficient-budget has computed the request's maximum cost — #overshoot requires it — but could report it only inside the resource token, whose aud is the PS. The agent knew what it had and not what the request needed, which makes the shrink-and-retry that #exhaustion offers a binary search rather than a calculation. It cannot derive the figure itself; the bound is the resource's own calculation and no resource is required to publish operation pricing.

#required-member also states why this is not the resource token's budget claim: that figure is a re-authorization offer addressed to the PS and should be larger, since sizing it to the refused request hands back a grant good for one call.

3. #billing-account. A metered resource charges an account, and nothing in a budget names one. sub is directed per PS. The section covers the three base-protocol answers — (iss, sub) or (iss, tenant, sub) lookup where the resource holds one account per person, the account parameter and claim where it holds several, and resource-initiated interaction where the person has no funded account yet. No new mechanism; the document was simply silent on a question every metered resource meets on its first request.

Resolved during review

The open question was whether the AS should be able to originate account rather than copy it from the resource token. Answered no by the implementer: one balance per person, bound on (ps, sub), and pooled or sponsored funding is resource-side state. The text says the AS copies it, matching the base protocol, and no base protocol change is needed.

Build

make draft-hardt-aauth-budgets.txt passes. All internal cross-references resolve.

dickhardt and others added 4 commits August 19, 2026 17:04
…counts

Review of -01 by a implementer building on this family surfaced one
framing gap and three concrete defects.

The framing gap: the granted budget is an allocation drawn against a
ceiling the PS holds and the agent is never told, not the person's whole
authorization. That was the design throughout and appeared nowhere. A
reviewer read the document end to end and concluded a durable grant was
missing, which is the correct reading of what was on the page. Stated in
the Introduction, in an expanded PS Token Endpoint, and in a rationale
section.

The PS Token Endpoint said "no new request parameter is defined" and
nothing about the decision. It now covers what the PS is deciding, the
four inputs it reads, and its six responses -- including
requirement=clarification, the channel that lets a PS tell an agent it is
overspending. Narrowing an amount cannot do that: a smaller figure is
silent, and the agent cannot distinguish a PS applying pressure from a
resource lowering its offer.

cost had two carriers, header and trailer. A resource metering a streamed
response on a runtime with no trailer support could use neither, making
every such response non-conformant. It may now omit cost, with reserved
becoming REQUIRED and the exact figure recoverable from the next
response's remaining.

A resource refusing under insufficient-budget computed the request's
maximum cost but could report it only in the resource token, whose aud is
the PS. The agent was left unable to size the shrink-and-retry the
exhaustion section offers it. Added the required member.

Added The Billing Account: which account a metered resource charges, and
the three base-protocol mechanisms that answer it. No new mechanism, but
every metered resource meets the question on its first request and the
document was silent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
Aggregation said the resource MUST aggregate against (iss, sub, aud) and
Concurrency said it MUST do so atomically across all live auth tokens,
which reads as a cross-token cap. It is not one. The enforced cap is the
presented token's own budget; the aggregate is a ledger for the records
and counters, and the resource must not refuse against it. Holding the
cross-token total is the PS's job, since the PS issues the tokens and is
the only party that knows the ceiling.

Also states why the ledger keys on the person rather than the mission:
mission_s256 is optional, and the standing inference pattern requires
mission-less tokens, so a mission-keyed ledger has no bucket for them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
The ceiling may not be shared with the agent, rather than never being
told to it -- what the document constrains is the wire, not what a PS
chooses to disclose out of band.

Terminating a mission was cited as an operation at the
mission_control_endpoint. The metadata field exists but the operation
does not: the base protocol leaves reading status, terminating, and
querying delegation to a companion specification. Names the response
without naming a mechanism.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
The four-party paragraph said the AS may know which account is billed and
left open which party sets the account claim. No claim is needed. The AS
returns 202 requirement=interaction on first sight of a sub, the person
authenticates and binds an account, and the AS holds the mapping -- the
same one-time binding it already performs to establish trust with a
person server. Also notes that sub is directed per PS, so the binding is
what attaches two person servers' identifiers to one account.

Closes the open question raised in the PR: no base protocol change is
needed for the AS to originate account information.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
@dickhardt

Copy link
Copy Markdown
Owner Author

The open question at the bottom of the description is closed — no base protocol change needed.

The four-party paragraph in #billing-account had the AS possibly setting the account claim, which conflicted with the base protocol having it copy the value from the resource token. Wrong framing: no claim is involved. The AS returns 202 with requirement=interaction on first sight of a sub, the person authenticates at the AS and creates or binds an account, and the AS holds the sub → account mapping from then on. That is the same one-time binding the AS already performs to establish trust with a person server — it just answers two questions at once.

Also added: because sub is directed per PS, a person arriving through two person servers presents two identifiers, and the binding interaction is what attaches both to one account. A resource that skips it bills two ledgers for one person.

The previous structure listed three cases as if they were alternatives.
Two of them are the same question -- which person is this -- asked by the
resource in three-party access and by the AS in four-party. The third is
a different question asked at a different frequency: which of several
accounts this authorization is for.

Restores the account parameter to the four-party path. The AS reads it
from the resource token, as the base protocol already specifies. Binding
tells the AS who the person is; account tells it which of their accounts
to bill. The previous revision said no claim was involved, which was
wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
@polvi-ish

Copy link
Copy Markdown

Read the branch against the thread; this is implementable as written. We're dropping TPX-A and porting tokenpony, AuthGravity, and Harness News to TPX + this draft. Three things:

Your open question: no, I don't need the AS to originate account. One balance per person, bound on (ps, sub). Sponsored or pooled funding is resource-side state keyed by mission_s256, no protocol change. Tighten the sentence to "the AS copies account from the resource token."

The tokenpony sketch, one correction: the Ed25519 key material, published JWKS, aa-resource+jwt minting on the 401, and an AS /token that verifies the PS and mints aa-auth+jwt already exist from TPX-A. What's actually new for us is the -11 surface (person tokens, presented_jti/sub/ps, authorization_endpoint, auth token via Signature-Key, 202 at the AS, usage_endpoint, budget_units, the header, the reason= 401s) and rekeying the ledger from mission to person. Standing mission-less tokens force the (ps, sub) → account binding anyway, so that order is right.

The explicit TPX statement isn't on the branch yet. Suggested for #inference: TPX is a complete deployment on its own and needs no person server; the two share no wire surface, only the meter; neither displaces the other.

Small: #cost-omitted is exact only for serial requests on one token (concurrent requests recover the sum), worth a sentence. The unit/decimals bullet in #aauth-budget-header still has the garbled "10^decimals to exactly the readers self-description serves" sentence.

Let us know once you're happy with the text and we'll port our side against it.

Four items from implementer review of the branch.

Stated in the inference section that TPX and this document do not
displace each other. TPX is a complete deployment on its own -- no person
server, no agent keys, nothing from this document -- and the two share no
wire surface. A provider implementing both has one meter under two
independent front doors, and the envelope follows from whether a person
is driving an app or an agent is acting for them.

Noted that recovering cost by subtraction is exact only for serial
requests on one token. Concurrent requests all move the same remaining,
so the difference between two responses is the net of everything that
settled in between.

Tightened the four-party account sentence to say the AS copies account
from the resource token, matching the base protocol, and added that a
resource holding one balance per person needs none of it.

Rewrote the garbled unit/decimals sentence, which was unreadable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
@dickhardt

Copy link
Copy Markdown
Owner Author

All four addressed, pushed.

account — tightened to "the AS copies account from the resource token," and added that a resource holding one balance per person needs none of it: binding on (iss, sub) is the whole mechanism and account never appears in its tokens. Open question removed from the description.

The sketch — thanks, that correction lands. I read the TPX-A appendix as a description of what the profile does rather than an inventory of what you'd already built, so I put the Ed25519 material, the JWKS, and the AS /token in the new column when they're existing code. Your list is the real one: the -11 surface plus the ledger rekey. And you're right that the standing mission-less token forces the (ps, sub) binding regardless, which fixes the order.

TPX standalone — added to #inference as its own paragraph:

Neither displaces the other. TPX is a complete deployment on its own. It requires no person server, no agent signing keys, and nothing from this document […] The two specifications share no wire surface — TPX carries its budget in an RFC 9396 authorization_details object on a refresh-token grant, this document carries one in a JWT claim and an HTTP response header — and a provider implementing both has one meter and one balance underneath two independent front doors.

It closes on the framing rather than a hierarchy: a person driving an app and an agent acting for that person are different situations, and the envelope follows from which one is happening.

#cost-omitted — caught. The subtraction is exact only for serial requests on one token; concurrent requests all move the same remaining, so the difference between two responses is the net of everything that settled in between. Added, along with the consequence — serialize on that token if you want per-request figures from a resource that omits cost, and don't bother if you only need the balance.

The unit/decimals sentence — it was garbled, now rewritten. The point it was failing to make: the readers those two members exist for are the ones that never parse a JWT, and those are exactly the readers that would misread an amount carrying a unit with no scale.

Also fixed my own error in the description — I had invented a surname for you. Apologies; it's your handle now.

Text is where I want it. Merging unless you spot something else.

dickhardt and others added 3 commits August 20, 2026 11:47
Moved the TPX relationship out of the inference section and into
Implementation Status, where it is a fact about a deployment rather than
a positional claim. The section now records tokenpony, Regent Protocol,
and the editor's services, and notes that an implementation report and
test vectors are expected. The inference section keeps one sentence.

The account paragraph restated base protocol behavior, which made this
document look like it defines account semantics. It now defers to the
base protocol for how account reaches the issuer and says only what is
specific to a metered resource: ask for it where a person may hold more
than one account, and ignore it where they hold one balance.

Split the unit/decimals rationale into its own sentences.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
…sponse

The response repeated unit and decimals in every entry, at every level.
budget_units fixes the scale for a unit and a response reports in one
unit, so both are stated once at the top and usage collapses from an
array to a counter object. The response now also echoes the scope key,
which it previously dropped, and carries aud naming the person server it
was produced for.

Added the jkts query. Allocations are ceilings, not figures, so a PS
supervising several agents for one person had no way to learn how the
spending divided among them except by waiting for consumption records.
The PS names the keys it wants and the resource returns what each
consumed. No calendar periods -- a key's spend is bounded by tokens that
live an hour, and periods answer a question about a person. A resource
SHOULD retain a key's figure for 24 hours after that key's last metered
request; the PS accumulates, since it is the party that knows which keys
belonged to which agent across rotations. An unrecognized or pruned key
is omitted rather than zeroed, because a zero meaning pruned is a wrong
answer to an allocation decision rather than a withheld one.

The response is now signed and bound to the request. The endpoint reports
what a person owes for, and consumption records were already
resource-signed by virtue of riding inside a resource token -- this was
the only PS-facing consumption channel a resource could later disown.
Signing makes the resource committed, not the meter honest, and
counters-trust now says so.

Also: revoking an auth token revokes its budget, per the base protocol's
revocation endpoint; a per-agent ceiling is a PS sizing decision rather
than a resource-side key; and no unit registry does not mean no
constraint, since a monetary unit SHOULD still be an ISO 4217 code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
…ion of labor

Usage example figures were round dollar amounts padded to six decimals --
long and artificial at once. Replaced with figures that do not land on
whole cents, which is what micro-dollar metering actually produces and a
better illustration of why decimals is 6. Counters are monotonic across
periods and the two per-key figures sum to all_time.

The Errors table still described the old usage request rule. It now
matches usage-authorization: more than one scope key, neither a scope key
nor jkts, an undeclared unit, or a malformed value.

Division of Labor did not mention per-agent visibility, which is a new
thing the endpoint answers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
@dickhardt

Copy link
Copy Markdown
Owner Author

Correcting myself: I said "text is where I want it, merging unless you spot something else." That was premature — the branch moved substantially afterward, and the usage endpoint in particular is not what you reviewed. Don't build against what you saw.

#usage-counters is reshaped. unit and decimals are stated once at the top of the response instead of per entry, and usage is consequently a plain counter object rather than an array. The response now echoes the scope key it was asked for, which it previously dropped, and carries aud naming the person server it was produced for. The request takes at most one scope key, and an OPTIONAL unit selects which unit to report in when a resource declares several.

New: a jkts query. The PS sends an array of JWK thumbprints and gets back what each key consumed. This is the per-agent visibility problem — allocations are ceilings, not figures, so a PS supervising several agents for one person could not learn how the spending divided among them. No calendar periods per key, since a key's spend is already bounded by tokens that live an hour. A resource SHOULD retain a key's figure for 24 hours after that key's last metered request; the PS accumulates beyond that, because it is the party that knows which keys belonged to which agent across rotations. An unrecognized or pruned key is omitted rather than reported as zero — a zero meaning "pruned" is a wrong answer to an allocation decision rather than a withheld one.

The usage response is signed, bound to the request, using the key material you already publish for resource tokens. Whether that is a MUST or a SHOULD is still open — see the thread; the current text says MUST and will likely soften to SHOULD with a PS-side MAY-require. Worth your opinion, since you would be implementing it.

Also since your review: revoking an auth token revokes its budget, pointing at the base protocol's revocation endpoint; a per-agent ceiling is stated to be a PS sizing decision rather than a resource-side key; and the no-unit-registry rationale now notes that a monetary unit SHOULD still be an ISO 4217 code.

Nothing is merged and nothing is on the datatracker — this whole family is editor's copy. Track the branch, not a published revision. I will say when it settles, and this time I will mean it.

dickhardt and others added 2 commits August 20, 2026 13:27
…atory changes

The signature was a MUST. Softened to RECOMMENDED, which BCP 14 defines
as synonymous with SHOULD. Three reasons, now stated in place: the
figures are decision context rather than authorization; this would be the
first response-side signature in the family, whose profile is
request-side throughout; and a person server that refuses an unsigned
response is left with no figures rather than unattributable ones, which
is the worse of the two. There is no useful action behind refusing, so no
PS-side requirement is defined.

The document history carried -00, -01, and -02 entries describing
revisions to a document that has never been submitted. Replaced with a
single flat list of exploratory changes and a note that the log resets at
first submission, which becomes -00. The detail behind any entry is in
the repository history and pull requests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
The unit request parameter existed for a resource metering one person in
more than one unit. Non-goals already says a resource that meters several
quantities collapses them to one billing unit before denominating a
budget, so the case is marginal by this document's own account, and the
parameter bought an error condition plus an arbitrary notion of a primary
unit for the case where a resource genuinely has two. The response
reports in the unit the resource meters in and names it.

The request rule read "a scope key, jkts, or both" followed by "none of
the three members", which counts four members as three and is unreadable.
Now: MUST carry a scope key or jkts, MAY carry both, at most one scope
key.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
@dickhardt

Copy link
Copy Markdown
Owner Author

Two follow-ups since my last note.

Signing is now RECOMMENDED, not MUST. You don't need to weigh in — the reasoning went: the figures are decision context rather than authorization, this would be the first response-side signature anywhere in AAuth (the profile is request-side throughout), and there is no useful action behind a PS refusing an unsigned response. Refusing leaves it with no figures rather than unattributable ones, which is worse. Signing still buys the thing worth having — the resource cannot later disown what it reported, or tell you one number and the biller another — so implement it if you're building the endpoint.

The unit request parameter is gone. I had added it so a resource metering one person in several units could be asked which to report. That case is marginal by the document's own non-goals, and it cost an error condition and an arbitrary "primary unit" rule. The response reports in the unit the resource meters in and names it, which for tokenpony is USD and always was.

The request rule is now: carry a scope key or jkts, may carry both, at most one scope key.

Still a PR, still nothing on the datatracker.

dickhardt and others added 2 commits August 20, 2026 13:41
…ress

Thanks Alex Polvi, Karl McGuinness, and Abay Aubakirov for feedback on
early drafts.

Implementation Status listed three efforts under one heading, which read
as though implementations exist. None is complete. Split into
Implementations, which says so plainly, and Implementations in Progress,
which carries the three.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7eRgUMa7W4hGQmURctyDa
@dickhardt
dickhardt merged commit a99d353 into main Aug 20, 2026
2 checks passed
@dickhardt
dickhardt deleted the budgets-allocation branch August 20, 2026 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants