Skip to content

ambient: adaptive scheduler never receives rate-limit data, so calculate_interval always falls back to max_interval_minutes #1330

Description

@unclesamwk

docs/AMBIENT_MODE.md documents an adaptive wake interval driven by provider rate-limit headers. The algorithm exists in scheduler.rs and is unit-tested, but nothing ever supplies it with data: both call sites pass None, and RateLimitInfo has no producer anywhere in the workspace. The documented behavior is therefore unreachable.

Checked against master at be248c6 (v0.85.0), which is current as of writing.

Expected

docs/AMBIENT_MODE.md#L344-L356:

Rate Limit Discovery

Rate limits are learned from provider response headers:

x-ratelimit-limit-tokens: 100000
x-ratelimit-remaining-tokens: 85000
x-ratelimit-reset-requests: 2026-02-08T15:00:00Z

The interval is then derived from the remaining window and budget.

Actual

ambient/scheduler.rs#L187 implements exactly that algorithm and returns early when it gets nothing:

let info = match rate_limit_info {
    Some(i) => i,
    None => return self.apply_backoff(max),
};

Both callers pass None, and they are the only callers in the workspace:

$ rg -n 'calculate_interval' --type rust | rg -v 'scheduler.rs'
crates/jcode-app-core/src/ambient/runner.rs:681:  .calculate_interval(None)
crates/jcode-app-core/src/ambient/runner.rs:814:  let interval = scheduler.calculate_interval(None);

RateLimitInfo is declared and consumed by the scheduler, but never constructed with real values anywhere.

No provider runtime reads x-ratelimit-*. The two rate-limit modules that do exist serve other purposes:

  • crates/jcode-app-core/src/update_rate_limit.rs — GitHub API limits for update checks.
  • crates/jcode-provider-core/src/retry_after.rs — per-request Retry-After handling, carried through anyhow::Error, not surfaced to ambient scheduling.

Consequence

With rate_limit_info = None, calculate_interval returns apply_backoff(max), and apply_backoff clamps to max_interval_minutes. So:

  • Ambient wakes on a fixed cadence of max_interval_minutes (default 120) regardless of quota state, rather than adapting to the window.
  • min_interval_minutes never takes effect.
  • The exponential backoff in on_rate_limit_hit is inert, because the interval is already clamped at max.
  • user_budget_reserve never takes effect, so there is no reservation of headroom for the interactive user.

Separately, runner.rs#L800-L802 calls on_rate_limit_hit() in the generic cycle-error branch, so any failure is treated as a rate-limit hit.

Question before sending a patch

The existing usage plumbing does not fit RateLimitInfo as-is: ProviderUsage/UsageLimit carry usage_percent: f32 and resets_at: String behind a 120s cache, not token counts. Capturing x-ratelimit-* per provider runtime touches a dozen crates, so I would rather ask first than guess:

  1. Should the capture live in each provider runtime, or behind one shared response hook?
  2. Is reusing the existing ProviderUsage snapshot acceptable instead, which would mean widening RateLimitInfo (or the algorithm) to accept a percentage plus reset time?

Happy to send a PR once the direction is clear.

Related

Environment

v0.85.0, be248c6, macOS 26.6.2 (arm64).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    autonomous: noNeeds your brain: a product/design decision is required before anyone acts.bugSomething isn't workingtriage: needs-decisionNeeds maintainer decision/design thought

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions