ProductDesktopUsage provider APIs

07 — Run-out projection semantics

Documents projection vocabulary, available quota fields, and why jackin❯ usage surfaces remain limited to provider limits and resets.

Summary

Provider APIs do not supply run-out forecasts, and client-derived depletion estimates are outside the jackin limits-only usage contract.

Question and scope

(1) What pace/reset math does jackin-usage already compute? (2) Which run-out phrases and formulas appear in competitor or compatibility evidence? (3) Which API fields would such derivations require, and where are they absent? (4) Why do these findings not expand the jackin limits-only contract?

This chapter records competitor and API evidence. It does not recommend a projection surface for jackin.

Method

Method: codebase read + web (READMEs only) Verification cutoff: 2026-07-24

Findings

Q1 — What jackin-usage already computes

  • quota_pace_label(remaining_percent: Option<u8>, reset_at: Option<i64>, window_seconds: Option<i64>, now: i64) -> Option<String> is the sole pace producer — crates/jackin-usage/src/usage/format.rs:164-191 (confidence: HIGH)
  • Exact math: reset_in = max(reset_at − now, 0) (format.rs:171); window_seconds = max(window_seconds, 1) (format.rs:172); if reset_in > window_secondsNone, pace suppressed entirely (format.rs:173-175); time_left_percent = reset_in / window_seconds × 100 (format.rs:176); delta = remaining_percent − time_left_percent (format.rs:183) (confidence: HIGH)
  • Label mapping: |delta| ≤ 2.0"On pace"; delta > 0"{round(delta)}% in reserve"; delta < 0"{round(−delta)}% in deficit" — so deficit N = round(time_left_percent − remaining_percent) when positive, reserve is the mirror — format.rs:184-190 (confidence: HIGH)
  • In-code comment attributes this to the "CodexBar pace model": more quota than time = reserve, burning faster than the clock = deficit — format.rs:177-182 (confidence: HIGH that the comment says so; attribution itself unverified, see Q3)
  • Unit tests pin the formula: 60% left with 90% of window remaining → "30% in deficit"; 90% left with 60% remaining → "30% in reserve"; 50/50 → "On pace"crates/jackin-usage/src/usage/tests.rs:2821-2835 (test quota_pace_label_uses_codexbar_reserve_deficit_onpace) (confidence: HIGH)
  • Integration coverage: Codex Session bucket "15% in reserve" (tests.rs:2255); Codex Weekly bucket falls back to the window label "1 week window" when pace is None (tests.rs:2259; fallback at crates/jackin-usage/src/usage/codex.rs:696-698); Kimi Rate Limit "30% in reserve", Kimi Weekly pace None (tests.rs:3318, 3324) (confidence: HIGH)
  • reset_label(reset_at, now): "Resets now" when reset_at ≤ now; default Countdown style "Resets in {compact_duration} ({local clock})"; ExactClock alternative "Resets {local clock}" — format.rs:111-128. Duration formatting "{d}d {h}h" / "{h}h {m}m" / "{m}m" via compact_duration_label — format.rs:193-208 (confidence: HIGH). So "Resets in <duration>" is already shipped.
  • remaining_from_fraction: remaining = 100 − min(used, 100) as u8, where used resolves a fraction-vs-percent ambiguity (value ≤ 1.0 → ×100) and non-finite/negative inputs (provider "unknown" sentinels) yield None — format.rs:39-47, 62-74 (confidence: HIGH). Remaining is quantized to whole percent (u8).
  • window_seconds is supplied per provider at probe time, not by the protocol: Claude constants CLAUDE_SESSION_WINDOW_SECONDS = 5h, CLAUDE_WEEKLY_WINDOW_SECONDS = 7d (crates/jackin-usage/src/usage/claude.rs:447, 450), mapped by limit kind (claude.rs:555-576), pace computed in into_bucket (claude.rs:505-506); Codex uses API limit_window_seconds or window_duration_mins × 60 (crates/jackin-usage/src/usage/codex.rs:389-392); Kimi supplies window seconds only for the "Rate Limit" bucket (crates/jackin-usage/src/usage/kimi.rs:252-256) (confidence: HIGH)
  • The capsule TUI already renders "Lasts until reset" today, derived from the pace token, not from a burn-rate projection: in usage_stacked_bucket_detail_rows, a detail equal to "On pace" or ending " in reserve" sets a flag (crates/jackin-capsule/src/tui/components/dialog_widgets/usage.rs:754-755); if the flag is set, a "Resets…" detail exists, and no "Runs out…" detail is present, "Lasts until reset" is pushed into the right column (usage.rs:760-764) (confidence: HIGH). Note: "On pace" covers delta down to −2, so a bucket slightly behind pace still shows "Lasts until reset".
  • "Runs out in <duration>" is consumed but never produced: the dialog routes any detail starting "Runs out" to the right column (dialog_widgets/usage.rs:751) and suppresses the synthesized "Lasts until reset" when one is present (usage.rs:762), but no code in the repo generates the string. It appears only in test fixtures as an injected composite pace_label "31% in deficit · Runs out in 21h 45m"crates/jackin-capsule/src/tui/components/dialog/tests.rs:1587, 1643, asserted rendered at tests.rs:1624-1625 — showing pace_label with " · " separators is the intended carrier (splitter usage_quota_bucket_detail_parts, dialog_widgets/usage.rs:846) (confidence: HIGH)
  • "Projected empty" appears nowhere in the repository (repo-wide grep, zero hits) (confidence: HIGH)
  • pace_label is persisted as an opaque string column in the snapshot store — crates/jackin-usage/src/usage_snapshot_store.rs:213 (schema), :450 (bucket→row) (confidence: HIGH)

Q2 — Projection formulas found in the evidence (analysis only)

  • Input availability in the bucket model: QuotaBucketView carries remaining_percent: Option<u8> (crates/jackin-protocol/src/control.rs:600-601), resets_at: Option<i64> (control.rs:604-608), and preformatted reset_label/pace_label strings (control.rs:602-603, 612-613) — but no window_seconds field. Window duration exists only provider-side at probe time (Q1) and is not in the protocol bucket nor in the store schema (column list, usage_snapshot_store.rs:190-218). Any projection computed downstream of bucket construction lacks the window duration unless the contract changes; computed at probe time (where quota_pace_label runs today) all three inputs are present (confidence: HIGH)
  • Sampling timestamps exist: FocusedUsageView.fetched_at_epoch (control.rs:407); store column fetched_at (usage_snapshot_store.rs:203) (confidence: HIGH)

(a) "Runs out in" / "Projected empty in" — burn-rate variants derivable from the available inputs:

  • Variant A — linear-from-window-start (no history needed). Inputs: the exact trio quota_pace_label already takes (format.rs:164-169). Define elapsed = window_seconds − reset_in, used = 100 − remaining. Burn rate r = used / elapsed (%/s); runs_out_in = remaining / r = remaining × elapsed / used. Failure modes: used = 0 → division by zero (never runs out); elapsed = 0 at window start → undefined; early-window jumpiness — small elapsed amplifies the whole-percent u8 quantization of remaining (format.rs:45-47, control.rs:601); assumes the window started at resets_at − window_seconds, which is wrong for rolling windows; reset_in > window_seconds already suppressed by the existing guard (format.rs:173-175) (confidence: HIGH that inputs/derivation follow from cited code; the variant itself is a derivation, not shipped code)
  • Algebraic identity linking Variant A to the shipped pace math: runs_out_in ≥ reset_inremaining × (window − reset_in) ≥ (100 − remaining) × reset_inremaining × window ≥ 100 × reset_inremaining_percent ≥ time_left_percentdelta ≥ 0. So under Variant A, the "Lasts until reset" boolean is exactly the sign of the existing pace delta (format.rs:176-183) — the current TUI heuristic (Q1, dialog_widgets/usage.rs:754-764) is that model's boolean, off by the ±2-point "On pace" band (confidence: HIGH, pure algebra over cited formulas)
  • Variant B — nominal-pace projection (degenerate; no burn estimate). Assume plan-nominal burn r = 100 / window_seconds; runs_out_in = remaining × window_seconds / 100. Inputs: remaining + window_seconds only (no resets_at needed for the duration). Yields the same "lasts until reset" boolean as Variant A (same inequality), but the duration reflects nominal, not actual, consumption — it contradicts a "current burn rate" reading of the phrase. Failure modes: none numerically (no division by observed data), but semantically misleading for idle or bursty accounts (confidence: HIGH as derivation)
  • Variant C — sampled burn from consecutive snapshots. r = (remaining_prev − remaining_now) / (fetched_now − fetched_prev); runs_out_in = remaining_now / r. Data requirement: ≥ 2 timestamped snapshots per bucket. Availability: the refresh loop holds exactly one prior in-memory view per cache key and reads it before overwriting (self.snapshots: HashMap<String, CachedUsage>crates/jackin-usage/src/usage.rs:192; read-then-insert at usage.rs:521-526), so one delta sample is obtainable in-memory; the durable store retains no history (Q4). Failure modes: reset crossing between samples → remaining rises → negative r; slow burn + 1% quantization + refresh cadence → measured delta 0 → r = 0 → projection flaps between "never" and finite; single-delta estimates are jumpy and smoothing (e.g. EWMA) needs more than one prior sample; division by zero when fetched_now = fetched_prev (confidence: HIGH for the cited data availability; the variant is a derivation)

(b) "Lasts until reset":

  • Definition derivable from any variant: projected run-out instant now + runs_out_in ≥ resets_at. Under Variants A and B this reduces to remaining_percent ≥ time_left_percent (identity above); under Variant C it is an independent comparison using the sampled r (confidence: HIGH as derivation)
  • jackin already ships the phrase as a pace-token heuristic (Q1): "On pace"-or-"in reserve" + a "Resets" detail + no "Runs out" detail → "Lasts until reset" — dialog_widgets/usage.rs:746-764 (confidence: HIGH)

Q3 — CodexBar / OpenUsage README cross-check (READMEs only, clean-room)

  • CodexBar README (https://github.com/steipete/CodexBar) describes usage display only as meters + reset countdowns: "Provider-specific usage meters with reset countdowns."; "Per-provider session, weekly, and monthly windows with countdowns to the next reset"; "The menu bar icon is a tiny usage meter." It contains no statements about pace, projection, burn rate, run-out, deficit, reserve, time-to-empty, or "lasts until" semantics — checked twice (repo page and raw README), consistent results (confidence: MED — read via WebFetch summarizer, quotes consistent across two independent fetches but not byte-verified)
  • OpenUsage README (https://github.com/robinebers/openusage) names the feature without defining it: "Provider-grouped meters with live reset countdowns and pace indicators." No definition of the pace computation and no run-out/projection/deficit/reserve/time-to-empty language anywhere in the README — checked twice (repo page and raw README) (confidence: MED — same WebFetch caveat)
  • Consequence: the jackin comment "CodexBar pace model" (format.rs:177) and the target phrases "Runs out in" / "Projected empty in" / "Lasts until reset" cannot be corroborated or refuted from the allowed public sources; the reserve/deficit/on-pace semantics are documented only by jackin code and tests (confidence: HIGH that the READMEs are silent, per the MED-confidence fetches above)

Q4 — Snapshot store history retention

  • The store retains only the latest row per bucket, not history: UNIQUE(provider, account_key_hash, source, window_kind) — usage_snapshot_store.rs:217 — with INSERT … ON CONFLICT(provider, account_key_hash, source, window_kind) DO UPDATE SET … overwriting every field including remaining_percent, resets_at, fetched_at, pace_label — usage_snapshot_store.rs:347-368 (confidence: HIGH)
  • Test proof: two sequential store_usage_snapshot calls on a 2-bucket view still yield exactly 2 rows, with the Session row's remaining_percent replaced by the second write's value (25) and fetched_at advanced — crates/jackin-usage/src/usage_snapshot_store/tests.rs:110-139 (test account_snapshot_rows_are_persisted_and_upserted) (confidence: HIGH)
  • Therefore a sampled burn rate (Q2 Variant C) cannot be computed from the durable store as-is; the only prior sample currently available is the single in-memory CachedUsage view per cache key (usage.rs:192, 521-526), which supports one delta and no smoothing window (confidence: HIGH)

Dead ends and contradictions

  • No producer of "Runs out in" exists anywhere in the repository — repo-wide grep across crates/ and src/ finds only the TUI consumer (dialog_widgets/usage.rs:751, 762) and injected test fixtures (dialog/tests.rs:1587, 1643). Ruled out that the projection is already computed somewhere upstream.
  • No occurrence of "Projected empty" or any burn-rate identifier (burn_rate, "time to empty") in product code — the phrase family is entirely unimplemented.
  • window_seconds checked for persistence: absent from both the protocol QuotaBucketView (control.rs:593-628) and the store schema (usage_snapshot_store.rs:190-218) — ruled out as a downstream-available input.
  • Mild contradiction: the TUI's shipped "Lasts until reset" heuristic fires for "On pace" (delta as low as −2), i.e. a bucket marginally behind pace is still labeled as lasting until reset — dialog_widgets/usage.rs:754-755 vs. format.rs:184-185. Consistent with rounding tolerance, inconsistent with a strict delta ≥ 0 reading.
  • CodexBar README fetched twice specifically hunting projection language — none present; the in-code "CodexBar pace model" attribution (format.rs:177) cannot be verified from the README, and source reading is forbidden (clean-room).
  • No embedded instructions were encountered in any fetched or read content (code, tests, READMEs) — nothing to flag.

Implications for jackin

jackin should not produce or display client-derived run-out projections. Usage surfaces remain limited to provider-authoritative used or remaining percentages, reset windows, plan status, freshness, and provenance. Existing projection consumers should not be treated as a product requirement.

Limitations and unknowns

  • CodexBar displays "Runs out in 4d 21h" and "Lasts until reset" on its homepage; release notes confirm the bare "Projected empty" label. OpenUsage uses "~N% left at reset" instead. The backing math remains unpublished — chapter 10.
  • Claude weekly and Grok weekly use fixed slots; Claude's five-hour session is anchored on server resets_at; Codex exposes a discrete window with window_start = reset_at − limit_window_seconds, though usage may not start at zero. Variant A is therefore well-founded when anchored on resets_atchapters 09 and 10.
  • The intended producer of the composite fixture "31% in deficit · Runs out in 21h 45m" (dialog/tests.rs:1587) — the TUI contract anticipates a producer that does not exist; whether it was planned for jackin-usage probe-time or a Desktop-side computation is unrecorded.
  • README quotes carry a WebFetch-summarizer intermediary; consistent across two fetches each but not byte-verified against raw file bytes.

Sources

On this page