# 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.