For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Extend rpm testing from one sustained schedule into six explicit limiter test modes: sustained, burst, token-bucket, sliding-window, window-boundary, and diagnose.
Architecture: Keep mode-specific scheduling and inference in a focused module, while cli remains responsible for argument parsing and command dispatch. Reports should preserve the current RPM summary shape and add mode-specific detail without storing raw prompts or secrets.
Tech Stack: Rust 2024, existing clap, tokio, futures, serde, chrono, hdrhistogram, anyhow.
src/cli.rs: add --mode, mode-specific flags, dispatch to mode runner, and keep current sustained behavior as default.src/rpm_modes.rs: mode enum, schedules, probe plans, limiter inference, and unit tests.src/report.rs: extend RPM reports with mode, optional burst/probe/window/diagnose details, actual RPM, and inferred limiter.src/main.rs: register rpm_modes module.README.md: document RPM modes and example commands.sustained: current default. Starts one request every 60 / rpm seconds for duration. Requests may overlap.burst: starts burst requests at t=0 and reports immediate success/failure/latency/error behavior.token-bucket: starts burst requests at t=0, then probes refill behavior for probe_seconds. Probe rate defaults to the expected refill rate from rpm, rounded up to at least one probe per second.sliding-window: starts burst requests at t=0, then sends low-rate probes for probe_seconds to observe whether recovery happens near the rolling 60 second boundary.window-boundary: waits until the next minute boundary test point, sends one batch before the boundary and one batch after it. This tests fixed-window reset behavior.diagnose: runs a bounded combined probe using burst, refill probes, and boundary signals, then writes a best-effort inference: token_bucket, fixed_window, sliding_window, or unknown.Keep the old command valid:
cargo run -- rpm --provider anthropic --rpm 120 --duration 60s --prompt "hello"
Equivalent explicit command:
cargo run -- rpm --mode sustained --provider anthropic --rpm 120 --duration 60s --prompt "hello"
New commands:
cargo run -- rpm --mode burst --provider anthropic --burst 120 --prompt "hello"
cargo run -- rpm --mode token-bucket --provider anthropic --rpm 120 --burst 120 --probe-seconds 30 --prompt "hello"
cargo run -- rpm --mode sliding-window --provider anthropic --rpm 120 --burst 120 --probe-seconds 90 --prompt "hello"
cargo run -- rpm --mode window-boundary --provider anthropic --rpm 120 --burst 120 --prompt "hello"
cargo run -- rpm --mode diagnose --provider anthropic --rpm 120 --burst 120 --probe-seconds 90 --prompt "hello"
Argument defaults:
--mode sustained--duration 60s, required only by sustained--burst <rpm> default for burst-style modes when omitted--probe-seconds 90 for sliding-window and diagnose--probe-seconds 30 for token-bucket--window-offset-ms 500 for window-boundary, meaning send before/after batches around the next minute boundary--concurrency should cap in-flight requests for all modes; default can be burst or a safe high value, but must never be zeroExtend RpmReport with:
{
"mode": "token-bucket",
"provider": "anthropic",
"model": "claude-test",
"run": {
"started_at": "2026-05-06T15:30:12Z",
"duration_ms": 60000,
"target_rpm": 120,
"actual_rpm": 118.5,
"temperature": 0.0,
"max_tokens": 1024
},
"summary": {
"actual_requests": 120,
"success": 118,
"failure": 2,
"latency_ms": {
"p50": 800,
"p95": 1500,
"p99": 2200
}
},
"mode_detail": {
"burst": {
"sent": 120,
"success": 118,
"failure": 2
},
"refill_probe": [
{
"second": 1,
"sent": 2,
"success": 2,
"failure": 0
}
],
"inference": {
"likely_limiter": "token_bucket",
"confidence": "medium",
"signals": [
"burst accepted most initial requests",
"probe success approximated 2.00 req/s refill"
]
}
},
"errors": []
}
Rules:
mode at top level.actual_rpm for every mode where duration is meaningful.Files:
Create: src/rpm_modes.rs
Modify: src/main.rs
Implement RpmMode with clap::ValueEnum:
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum RpmMode {
Sustained,
Burst,
TokenBucket,
SlidingWindow,
WindowBoundary,
Diagnose,
}
Implement:
pub fn sustained_schedule(duration: Duration, rpm: u32) -> Vec<Duration>;
pub fn burst_schedule(burst: u32) -> Vec<Duration>;
pub fn token_bucket_schedule(rpm: u32, burst: u32, probe_seconds: u64) -> Vec<ScheduledProbe>;
pub fn sliding_window_schedule(burst: u32, probe_seconds: u64) -> Vec<ScheduledProbe>;
pub fn window_boundary_plan(now: DateTime<Utc>, burst: u32, offset_ms: u64) -> WindowBoundaryPlan;
Use explicit structs for ScheduledProbe and WindowBoundaryPlan; include phase labels such as burst, refill_probe, sliding_probe, before_boundary, and after_boundary.
Test:
120 RPM sustained creates starts at 0ms, 500ms, 1000ms for a short duration.
Burst 5 creates five 0ms starts.
Token bucket 120 RPM with 120 burst and 2 probe seconds has 120 burst starts plus about 4 refill probes.
Sliding window probe lasts through the requested probe seconds.
Window boundary plan places before and after batches around a minute boundary.
Add mod rpm_modes; to src/main.rs.
Run:
cargo fmt
cargo test rpm_modes
Commit:
git add src/rpm_modes.rs src/main.rs
git commit -m "feat: add rpm mode schedules"
Files:
Modify: src/cli.rs
Modify: src/rpm_modes.rs
Add to Command::Rpm:
#[arg(long, value_enum, default_value_t = RpmMode::Sustained)]
mode: RpmMode,
#[arg(long)]
burst: Option<u32>,
#[arg(long)]
probe_seconds: Option<u64>,
#[arg(long, default_value_t = 500)]
window_offset_ms: u64,
#[arg(long)]
concurrency: Option<usize>,
Keep existing rpm, duration, and prompt arguments compatible. duration may remain a string with default 60s; non-sustained modes can ignore it unless needed.
Refactor RPM execution so all modes share:
async fn run_scheduled_requests(
protocol: ProtocolKind,
request: ModelRequest,
starts: Vec<ScheduledRequest>,
max_in_flight: usize,
) -> Vec<ScheduledResult>;
Each result should retain phase/second metadata for mode-detail reporting.
Mode behavior:
sustained: use current behavior with sustained_schedule.
burst: use burst_schedule.
token-bucket: burst at 0, then refill probes based on rpm.
sliding-window: burst at 0, then one probe per second until probe_seconds.
window-boundary: sleep until planned before-boundary start, send before batch, then after batch.
diagnose: run bounded burst/refill/boundary probes and produce inference.
Add tests for:
Old RPM command defaults to sustained.
--mode token-bucket parses.
--mode window-boundary --window-offset-ms 250 parses.
Invalid zero --burst and zero --rpm are rejected by execution validation.
Run:
cargo fmt
cargo test cli::tests rpm_modes
cargo test
Commit:
git add src/cli.rs src/rpm_modes.rs
git commit -m "feat: run rpm limiter modes"
Files:
Modify: src/report.rs
Modify: src/cli.rs
Modify: src/rpm_modes.rs
Add:
pub enum LimiterInferenceKind {
TokenBucket,
FixedWindow,
SlidingWindow,
Unknown,
}
pub struct LimiterInferenceReport {
pub likely_limiter: LimiterInferenceKind,
pub confidence: String,
pub signals: Vec<String>,
}
pub struct RpmModeDetailReport {
pub burst: Option<PhaseSummaryReport>,
pub refill_probe: Vec<ProbeSecondReport>,
pub sliding_probe: Vec<ProbeSecondReport>,
pub window_boundary: Option<WindowBoundaryReport>,
pub inference: Option<LimiterInferenceReport>,
}
Use serde rename attributes so JSON uses snake_case values like token_bucket.
Add helpers to group scheduled results by phase and second:
burst sent/success/failure
refill probe per second
sliding probe per second
boundary before/after sent/success/failure
Best-effort rules:
fixed_window with medium confidence.rpm / 60 requests per second after burst, infer token_bucket with medium confidence.sliding_window with medium confidence.unknown with low confidence.Do not overclaim; include signals in report.
Test:
RPM report serializes mode.
RPM report does not serialize raw prompt.
Token bucket report includes burst and refill probe fields.
Diagnose inference serializes expected snake_case limiter value.
Run:
cargo fmt
cargo test report rpm_modes
cargo test
Commit:
git add src/report.rs src/cli.rs src/rpm_modes.rs
git commit -m "feat: report rpm limiter mode details"
Files:
Modify: README.md
Add a section for:
sustained: stable RPM.
burst: instant burst capacity.
token-bucket: burst plus refill probe.
sliding-window: rolling 60 second recovery probe.
window-boundary: fixed minute boundary probe.
diagnose: combined unknown-mode diagnosis.
Mention that real LLM backends often combine RPM, TPM, concurrency, account-level, model-level, and region-level limits. Reports are strong signals, not perfect proof.
Run:
cargo fmt --check
cargo test
cargo clippy --all-targets -- -D warnings
cargo run -- rpm --help
Do not run live network tests unless the user explicitly asks.
git add README.md
git commit -m "docs: document rpm limiter modes"