Skip to content

feat(control-plane): add per-key rate limiting on hot endpoints - #952

Merged
santoshkumarradha merged 3 commits into
Agent-Field:mainfrom
7vignesh:feat/429-rate-limiting-hot-endpoints
Aug 24, 2026
Merged

santoshkumarradha merged 3 commits into
Agent-Field:mainfrom
7vignesh:feat/429-rate-limiting-hot-endpoints

Conversation

@7vignesh

Copy link
Copy Markdown
Contributor

Summary

Fixes #429. Adds per-key token-bucket rate limiting to the control plane's hot endpoints (/execute, /discovery, /nodes/status/bulk), preventing a single misbehaving client from exhausting the async worker pool and database connections.

Type of change

  • New feature
  • Bug fix

Implementation

Middleware (middleware/ratelimit.go)

  • RateLimiterStore — per-key golang.org/x/time/rate token-bucket limiters with a sync.Mutex-guarded map and background eviction of idle keys (10 min TTL)
  • RateLimit gin middleware — extracts key from API key identity (set by auth middleware) or falls back to client IP; returns HTTP 429 with Retry-After header and JSON body on limit breach
  • RateLimitConfig / RateLimitsConfig — per-endpoint configuration types with IsEnabled() and DefaultRateLimitConfig()

Config (config/config.go)

  • Added RateLimitConfig to AgentFieldConfig — configurable via agentfield.yaml:
agentfield:
  rate_limit:
    enabled: true
    execute_rps: 50
    execute_burst: 100
    discovery_rps: 20
    discovery_burst: 40
    bulk_status_rps: 30
    bulk_status_burst: 60
    global_rps: 200
    global_burst: 400

Routes (routes_core.go, server.go)

  • initRateLimiters() — creates per-endpoint stores from config at startup
  • Applied to: /api/v1 (global), /execute group, /discovery group, /nodes/status/bulk
  • Graceful shutdown: Stop() halts eviction goroutines
  • Opt-in: disabled by default (rate_limit.enabled: false) to avoid breaking existing deployments

Defaults (when enabled)

Endpoint RPS Burst
Execute 50 100
Discovery 20 40
Bulk Status 30 60
Global (per-IP) 200 400

Test plan

  • cd control-plane && go test ./internal/server/middleware/ -run TestRateLimit -v (9 tests)
  • cd control-plane && go test ./internal/config/ -timeout 30s
  • cd control-plane && go build ./...
  • cd control-plane && go vet ./internal/server/... ./internal/config/...
  • cd control-plane && golangci-lint run ./internal/server/middleware/ (no new warnings)

Test coverage

  • I ran tests for the surface(s) I changed locally.
  • New code paths are covered by tests in this PR (no bare additions).
  • If I removed code, I updated coverage-baseline.json in this PR only if the removal caused a legitimate regression.
  • The coverage gate check is green in CI before requesting review.

Checklist

Related issues / PRs

Fixes #429

Notes

  • TestGinLoggerEmitsStructuredDebugLine is a pre-existing flaky test unrelated to this change.
  • golang.org/x/time was already an indirect dependency (v0.8.0 via otel); upgraded to v0.15.0.

@7vignesh
7vignesh requested review from a team and AbirAbbas as code owners August 23, 2026 18:51
@github-actions

github-actions Bot commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

📊 Coverage gate

Thresholds from .coverage-gate.toml: per-surface ≥ 84%, aggregate ≥ 85%, max per-surface regression ≤ 1.0 pp, max aggregate regression ≤ 0.50 pp.

Surface Current Baseline Δ
control-plane 87.20% 87.40% ↓ -0.20 pp 🟡
sdk-go 92.90% 92.00% ↑ +0.90 pp 🟢
sdk-python 94.20% 93.73% ↑ +0.47 pp 🟢
sdk-typescript 91.39% 90.42% ↑ +0.97 pp 🟢
web-ui 84.76% 84.79% ↓ -0.03 pp 🟡
aggregate 85.66% 85.75% ↓ -0.09 pp 🟡

✅ Gate passed

No surface regressed past the allowed threshold and the aggregate stayed above the floor.

@github-actions

github-actions Bot commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

📐 Patch coverage gate

Threshold: 80% on lines this PR touches vs origin/main (from .coverage-gate.toml:thresholds.min_patch).

Surface Touched lines Patch coverage Status
control-plane 197 90.00% ✅
sdk-go 0 — ➖ no changes
sdk-python 0 — ➖ no changes
sdk-typescript 0 — ➖ no changes
web-ui 0 — ➖ no changes

✅ Patch gate passed

Every surface whose lines were touched by this PR has patch coverage at or above the threshold.

…t-Field#429)

Without rate limiting, a single misbehaving client can exhaust the
async worker pool and database connections on hot endpoints, starving
legitimate traffic.

Implementation:
- Add RateLimiterStore using golang.org/x/time/rate with per-key
  token-bucket limiters and background idle eviction (10 min TTL).
- Add RateLimit gin middleware that keys on API key (from auth
  middleware) or client IP, returns HTTP 429 with Retry-After header.
- Add RateLimitConfig to agentfield.yaml config with per-endpoint
  settings: execute (50 rps/100 burst), discovery (20/40),
  bulk_status (30/60), global (200/400).
- Wire into routes: global limiter on /api/v1, dedicated limiters on
  /execute group, /discovery group, and /nodes/status/bulk.
- Opt-in via config: rate_limit.enabled = true in agentfield.yaml.
  Disabled by default to avoid breaking existing deployments.
- Graceful shutdown: Stop() halts eviction goroutines.

Tests (9 cases):
- Token bucket enforcement and burst
- Idle key eviction
- Active key not evicted
- Normal traffic allowed
- 429 returned with Retry-After header and JSON body
- Per-key isolation (different clients independent)
- API-key-based keying when auth is present
- Config enabled/disabled logic
- Default config values
@7vignesh
7vignesh force-pushed the feat/429-rate-limiting-hot-endpoints branch from ef5429f to 1043654 Compare August 23, 2026 19:11

@santoshkumarradha santoshkumarradha left a comment

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.

Checked the new rate-limit wiring locally. The endpoint-specific tests pass, the feature stays inert unless explicitly enabled, and the config surface is straightforward.

@santoshkumarradha
santoshkumarradha added this pull request to the merge queue Aug 24, 2026
Merged via the queue into Agent-Field:main with commit c78b22c Aug 24, 2026
21 checks passed
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.

[Control Plane] No rate limiting on hot endpoints

2 participants