Budgets: the allocation model, omitted cost, required, and billing accounts - #102
Conversation
…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
|
The open question at the bottom of the description is closed — no base protocol change needed. The four-party paragraph in Also added: because |
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
|
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 The tokenpony sketch, one correction: the Ed25519 key material, published JWKS, The explicit TPX statement isn't on the branch yet. Suggested for Small: 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
|
All four addressed, pushed.
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 TPX standalone — added to
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.
The 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. |
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
|
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.
New: a 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. |
…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
|
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 The request rule is now: carry a scope key or Still a PR, still nothing on the datatracker. |
…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
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:
#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-scopewas 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=clarificationis 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.
costhad no carrier on some streamed responses. The member could go in the header or a trailer, andMUST 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-omittedpermits omitting it, makesreservedREQUIRED in that case, and gives the recovery:cost= previousremaining+reserved− currentremaining. The subtraction works becauseremainingis 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-failureclaiming 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.
requiredmember onAAuth-Budget. A resource refusing underreason=insufficient-budgethas computed the request's maximum cost —#overshootrequires it — but could report it only inside the resource token, whoseaudis the PS. The agent knew what it had and not what the request needed, which makes the shrink-and-retry that#exhaustionoffers 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-memberalso states why this is not the resource token'sbudgetclaim: 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.subis 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, theaccountparameter 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
accountrather 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.txtpasses. All internal cross-references resolve.