Skip to content

docs(proposal): add implementation to token rate-limiting - #658

Open
shaneutt wants to merge 1 commit into
praxis-proxy:mainfrom
shaneutt:shaneutt/tokenomics-implementation-iteration-1
Open

docs(proposal): add implementation to token rate-limiting#658
shaneutt wants to merge 1 commit into
praxis-proxy:mainfrom
shaneutt:shaneutt/tokenomics-implementation-iteration-1

Conversation

@shaneutt

@shaneutt shaneutt commented Aug 6, 2026

Copy link
Copy Markdown
Member

This adds a high-level overview of the strategy for implementing Token Rate Limiting in Praxis.

@shaneutt shaneutt added this to the v0.2.0 milestone Aug 6, 2026
@shaneutt
shaneutt requested a review from jland-redhat August 6, 2026 18:09
@shaneutt
shaneutt force-pushed the shaneutt/tokenomics-implementation-iteration-1 branch from bef5abd to 12b0800 Compare August 6, 2026 18:11
@shaneutt shaneutt changed the title docs(proposal): add implementation strat to tokenomics proposal docs(proposal): add high-level implementation to token rate-limiting Aug 6, 2026
@shaneutt shaneutt moved this from Next to Epics in AI Gateway - Model Serving Aug 10, 2026
@shaneutt shaneutt moved this from Epics to Review in AI Gateway - Model Serving Aug 10, 2026
@shaneutt
shaneutt marked this pull request as ready for review August 10, 2026 16:56
@shaneutt
shaneutt requested a review from a team August 10, 2026 16:56
@shaneutt shaneutt changed the title docs(proposal): add high-level implementation to token rate-limiting docs(proposal): add implementation to token rate-limiting Aug 10, 2026
@shaneutt
shaneutt force-pushed the shaneutt/tokenomics-implementation-iteration-1 branch from c84806d to 7115e45 Compare August 10, 2026 18:05
@shaneutt
shaneutt requested review from asaadbalum and usize August 10, 2026 18:05
@shaneutt shaneutt assigned asaadbalum and alexsnaps and unassigned jland-redhat Aug 10, 2026
@shaneutt
shaneutt force-pushed the shaneutt/tokenomics-implementation-iteration-1 branch from 7115e45 to 253b698 Compare August 11, 2026 12:05
Co-authored-by: Jamie Land <hokie10@gmail.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
@shaneutt
shaneutt force-pushed the shaneutt/tokenomics-implementation-iteration-1 branch from 1e26820 to 394e469 Compare August 11, 2026 12:07
@praxis-proxy praxis-proxy deleted a comment from praxis-bot-app Bot Aug 11, 2026
Comment on lines +53 to +54
by default, treats all requests under a `default`
rule).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest avoiding having "a default". Most generally buckets "stack", e.g. with hourly & daily counters when any hits the threshold, the call is considered "above budget". While it seems easy enough to say "if there are no bucket rule matching for this request, then delegate to this other set of buckets", it actually comes at a very high cost in terms of complexity. otoh, if the user is to specify "how to qualify" the default, that complexity, while being pushed on the user, is gone and makes for a system that much easier to reason about and less prone to bugs & weird corner cases.

- **[M5]** Hard deny with 429 when a budget is
exhausted, with standard rate limit response
headers (`Retry-After`, `X-RateLimit-*`).
- **[M3]** Configurable estimation: allow opeartor to

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

operator (not opeartor)

tracked separately with configurable weights so
quotas reflect real cost differences.
- **[M5]** Flexible bucket keys: quotas keyed by
request infromation. Headers, model identity, or

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

information (not infromation)

- **[M7]** Soft limits: usage tiers that modify request
including estimated cost, actual cost, and remaining
budget. The ability to log tokenomic results
distinctly for accounting.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest not to compound "accounting" & TRLP. This makes for very brittle systems, mostly in the face of failure. It'd be preferable to have that (configurable?) auditing trail happen "outside this system", while it probably could be reusing some of the information from TRLP (e.g. the "bucket keying logic").

Now it can be part of this problem definition, but it would be desirable to model that separately in the implementation.

independent budgets. (TBD - this one may need to be
teased out more - possibly using CEL note there is an
upstream effor around this found
[here](https://github.com/kubernetes-sigs/wg-ai-gateway/pull/57))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This problem is interesting. In my experience, a key is "calculated" (vs. being a compound key, where all individual bits remains usable by themselves) - which is another reason to not compound the rate limiting with the auditing (see below).

Comment on lines +228 to +229
daily budget). Unmatched requests fall to a `default`
rule.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeating myself, but I'd suggest leaving that "fall to a default rule" out, unless there is an actual real use case for it.

Comment on lines +284 to +288
inject:
headers:
X-Token-Day-Tier: warning
- capacity: 1_000_000
action: deny

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is inject, not an action here? nitpick, but from a modeling/normalization perspective I think we can do better here by having a "tighter API surface", which then would probably also be more open to extensions in the future, wdyt?

Comment on lines +334 to +345
3. **Reconciliation** - Actual provider-reported token
usage replaces the estimate. The difference between
estimated and actual cost is logged, and usage past
tier capacities is still tracked for observability
when no `deny` tier applies. (In future iterations,
we may refund the estimate-vs-actual difference back
to the budget; for MVP, budgets consume at the
estimated rate.)

4. **Cleanup** - If a request is lost (timeout,
connection reset, upstream failure), release the
reservation after a configurable hold period.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want, which I think we do and certainly recommend so, to be able to deal with all failure scenarios, this flow will probably need to be somewhat different:

  • Reservations need to be first class citizens in the admission phase and have their own timeout
  • Reconciliation needs to deal with "applying the actual diff" using the reservation, where the "refund" is actually the easiest problem, overshoots are more annoying - as users may want different outcomes.
  • There is a note to be added here possibly, about how a reservation behaves when it "overlaps two (or more buckets)". Not a huge problem, but probably clearly explaining what happens would probably be good.
  • Cleanup here I think is what the proxy does... but the proxy can only do it if it's still around (see first bullet point above)

Comment on lines +368 to +370
estimation:
strategy: input_plus_max_tokens
multiplier: 1.2

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless there is a reason not to support an expression here, I'd stay away from these "explicit YAML" strategies. Now, if on the other hand, the goal is to actually not use an expression then... maybe ¯_(ツ)_/¯

@praxis-bot praxis-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 comments: 2 Large, 4 Medium.

The rule-based budgeting model and tier system are well-structured. The estimation strategy table and token-type capture/weighting design address real operator needs. The main concerns are: a contradiction in the reconciliation lifecycle, an MVP item (M8) with no corresponding design, unclear window semantics, and scope ambiguity around soft limits vs. the tier model.

response-only accounting.

2. **Inference** - The request is forwarded upstream.
The provider performs inference and returns token

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Large] The reconciliation phase says "actual provider-reported token usage replaces the estimate" but then concludes "for MVP, budgets consume at the estimated rate." These contradict each other. If actual usage replaces the estimate, the budget reflects actual cost. If budgets consume at the estimated rate, actual usage does not replace anything -- it is only logged.

Clarify which behavior MVP implements: (a) replace the estimate with actual cost in the budget (true reconciliation), or (b) keep the budget at the estimated rate and only log the actual-vs-estimated delta. Then reword this paragraph to be internally consistent.

including estimated cost, actual cost, and remaining
budget. The ability to log tokenomic results
distinctly for accounting.
- **[M8]** Multi-environment observability: aggregate

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Large] M8 (multi-environment observability) is listed as MVP must-have, but the Design section has no subsection addressing it. Cross-environment aggregation is architecturally significant -- it requires either shared state (e.g. an external store that all proxy instances read/write budgets against) or a centralized aggregation service. Without at least a sketch of the approach, this goal is unimplementable.

Either add a design subsection for multi-environment aggregation, or move M8 to "Should have" and note that MVP budgets are per-instance.

rejected with 429. Otherwise the request continues and
inject headers from all currently exceeded soft tiers
are unioned onto the request. If two budgets inject the
same header name, the last budget in config order wins;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Medium] The tier model described here -- multiple capacity thresholds with inject.headers for graduated signals before hard deny -- is exactly what S1 (Soft limits) describes: "usage tiers that modify request headers instead of rejecting." But S1 is "Should have," not MVP. This creates ambiguity: is the multi-tier injection model part of MVP, or is the MVP tier design just a single capacity with hard deny?

Clarify the MVP boundary. Either move S1 into "Must have" (since the design already assumes it), or add a note that the MVP tier model supports only action: deny and the inject.headers / action: continue mechanism is S1 scope.

subscription: team-alpha-key
x-praxis-ai-model: gpt-4o
# Dynamic matchers (post-MVP / CEL) — optional
- cel: >

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Medium] The config uses window: 1h and window: 24h but the proposal never defines the window type. Is this a sliding window (continuously rolling), a tumbling/fixed window (resets on the hour/day), or calendar-aligned? The non-goals list "Token Rate Limiting over a static window" as future work, which implies the MVP uses sliding windows, but this is never stated.

Add a sentence to the Token Budgeting section defining the window semantics (e.g. "Windows are sliding: a 1h window tracks usage in the most recent 60 minutes from the current instant").

length).

Built-in strategies (MVP starting point):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Medium] Three of the four estimation strategies depend on max_tokens, but not all LLM requests include this field (providers apply high defaults when it is omitted). The proposal does not specify what happens when max_tokens is absent.

Add a fallback behavior for each strategy when max_tokens is missing -- for example: reject the request, use a configurable default, or fall back to a different strategy.

- **[M5]** Hard deny with 429 when a budget is
exhausted, with standard rate limit response
headers (`Retry-After`, `X-RateLimit-*`).
- **[M3]** Configurable estimation: allow opeartor to

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Medium] Several typos throughout the proposal:

  • Line 59: "opeartor" -> "operator"
  • Line 68: "infromation" -> "information"
  • Line 72: "effor" -> "effort" (also has a double space before "this")
  • Line 142: "thefore" -> "therefore"
  • Line 146: "requrest" -> "request", "therfore" -> "therefore"

Fix these typos.

@praxis-bot praxis-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 comments: 2 Medium, 2 Small.

Following up on the new commit. The How? section with the full design is a strong addition — the rule/budget/tier structure, estimation strategies table, and token-type capture/accounting separation are well thought out. A few new items below.

forced to report usage in the response body or
headers. By the time actual counts are known, the
tokens have been consumed. Admission decisions
however must happen at requrest time. And therfore

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Medium] Three new typos in the rewritten Motivation paragraph:

  • Line 142: "thefore" → "therefore"
  • Line 146: "requrest" → "request"
  • Line 146: "therfore" → "therefore"

Also, the two sentences starting with "And" (lines 142, 146) read as fragments. Consider joining them to the preceding sentence:

Providers are unaware of output token counts before receiving the output and are therefore forced to report usage in the response body or headers. … Admission decisions however must happen at request time, and therefore we must rely on token count estimates …

rule** (not per `token_budget`). Hourly and daily
budgets on the same rule share one cost model.
Strategies operate on request metadata available before
forwarding (e.g. `max_tokens`, model identity, content

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Medium] Estimation is configured per-rule, but the proposal doesn't specify behavior when a rule omits the estimation key. The default catch-all rule example (line 290) has no estimation config, and neither does the batch example (line 468). Is there a filter-level default strategy? Is omission an error? Does it skip admission estimation entirely (response-only accounting)?

Line 327 hints that estimation at admission "can be optionally disabled in favor of response-only accounting" but the mechanism for that opt-out isn't defined. Clarifying the default/fallback behavior would close this gap.

action: deny
# Optional nested batch rule (same parent identity,
# different budgets). Exact nesting TBD.
batch:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Small] The Batch Workloads section presents a concrete nested batch: config key with a detailed example, but the Open Questions section (line 552–568) says: "Open shape questions remain: separate rules vs nested rules under a parent, both, or something else entirely." Consider marking this example as illustrative/draft (e.g. "one possible shape") so readers don't treat the nested batch: key as settled design.

connection reset, upstream 5xx)? How long should a
reservation be held before it is considered lost?

[#210]: https://github.com/praxis-proxy/ai/issues/210

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Small] [#210] is defined here but never referenced in the document. Either link it where relevant or remove the dead reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Review

Development

Successfully merging this pull request may close these issues.

5 participants