Repository navigation
feat(gateway): default-on rate limits and tenant concurrency cap - #45
Merged
Merged
Conversation
Reject exhausted keys before delegation verification. Hold tenant slots until responses and SSE streams finish. Keep admission denials inside the authenticated receipt path. Signed-off-by: Shreyansh Sancheti <43677304+shreyanshjain7174@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Authorize principal and service scopes before tenant charges to prevent cross-tenant quota exhaustion. Bound tenant identities and scan past pinned cache entries. Refund concurrency rejections without rewinding bucket clocks. Release acquired slots during panic unwinding. Signed-off-by: Shreyansh Sancheti <43677304+shreyanshjain7174@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Plan
Phase 0, item 6 of the AgentGate scale-out prompt: real rate limiting.
cmd/agentgwcalledratelimit.New(nil), soAllow()passed everything through. The limiter also ran after delegation verification, its map was unbounded, and it had no tenant or concurrency limits.What changed
Default-on token buckets.
rate_limit:in registry YAML.Per-tenant in-flight cap (default 32). A slot is held until the request ends, including SSE streams.
Order. Key bucket, then the scope check, then tenant and service buckets, then delegation verification, vault, URL building and upstream. A key that is out of scope for a user can never spend that user's tenant tokens. Such requests keep their
scope_deniedreceipt.Tenant identity.
key:<id>.human:<principal>, capped at 64 principals per key; extra principals share a key-scoped fallback.Rotating
on_behalf_ofcannot create new entries.Responses. Over the limit, the agent gets 429
rate_limitedorconcurrency_limited, aRetry-Afterof at least 1s, and a receipt, as before.Refunds. A
concurrency_limitedrejection refunds the tokens it took, so a tenant waiting at its cap does not drain its own buckets.Memory bound. Idle entries are evicted after 10 minutes, and the map is LRU-capped. When it is full, eviction scans past pinned entries rather than failing closed.
Panic safety. The slot release is registered before dispatch.
Config.
AGENTGATE_RATE_KEY_RPS,AGENTGATE_RATE_KEY_BURST,AGENTGATE_RATE_TENANT_RPS,AGENTGATE_RATE_TENANT_BURST,AGENTGATE_TENANT_MAX_INFLIGHT.0disables a limit. Startup refuses a disabled key limit while tenant limits are on.Validation
go build ./...: passedgo vet ./...: passedgo test -race ./... -count=2: passedsemgrep --config auto --error --quiet internal/ratelimit internal/gateway cmd/agentgw: passed, no findingsTests use a fake clock. They cover:
Review
Two rounds by a second model (Claude Opus 5.5). Round 1 found that the new ordering let out-of-scope keys drain other tenants' buckets, and that a pinned LRU tail failed closed. Both are fixed with tests.
Known limits
Quotas are per process. Replicas do not share buckets.