Files
box/docs/RATE-LIMIT-POLICY.md
T

54 lines
2.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Per-Node Rate Limit Policy (2026-10-04)
## Why
All 4 fleet nodes (muse, pip, 646, opm) egress from a single Cloudflare IP
(`104.28.195.181`). Cloudflare therefore sees the whole fleet as one client.
When fleet timers fire simultaneously, synchronized send bursts look like one
aggressive client and invite correlated rate-limiting — the likely shape of
the "all 4 browsers flapping" episodes.
Per-agent rate limiting with deterministic jitter de-correlates the fleet:
each node's send phase drifts apart instead of hitting in lockstep.
## Policy
| Agent | Base interval | Jitter factor | Effective interval | Burst | Max/min |
|---|---|---|---|---|---|
| muse | 3.0s | 1.2917 | 3.875s | 5 | 20 |
| pip | 3.0s | 0.8183 | 2.455s | 5 | 20 |
| 646 | 3.0s | 1.1193 | 3.358s | 5 | 20 |
| opm | 3.0s | 0.8287 | 2.486s | 5 | 20 |
Mean effective interval ≈ 3.04s — the jitter does **not** make limits
stricter on average; it only spreads phases.
- **Jitter:** ±30% deterministic per agent, seeded by
`sha256("netvm-rate-jitter:v1:" + agent)`. Same agent → same factor every
run (reproducible); different agents → different factors (de-correlated).
Uses an isolated `random.Random`, never touches global random state.
- **Scope:** per-agent token buckets. State lives in one shared file
(`/tmp/netvm-rate-limit.json`) but keys are namespaced by agent
(`{agent}`, `{agent}_recent`) — buckets are fully independent.
- **Enforcement points:**
- `job-dispatch.py` — `rate_limit_wait(agent)` / `rate_limit_wait(sender_agent)`
(pre-existing; now jittered, no call-site change needed)
- `dm.py::dm_send` — `rate_limit_wait(agent)` before send, keyed on the
sender (added 2026-10-04). Near no-op at normal pacing since sends
already sleep 2–3s; it only bites on burst loops.
## Inspect
```bash
python3 bin/rate_limiter.py --policy # effective policy per agent
python3 bin/rate_limiter.py --policy opm --no-jitter
```
## Notes
- `/tmp` state does not survive reboots; buckets start empty (permissive).
- To disable jitter for a caller: `rate_limit_wait(agent, jitter=0)`.
- Distinct egress IPs per node (Cloudflare Zero Trust dedicated egress)
remains the structural fix; this policy is the de-correlation layer that
works regardless.