Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Usage Forecast (predicted rate-limit exhaustion) (part 2)

Part 2 of the Usage Forecast page: how to read each per-account forecast state, the threshold that fires an early warning, the data-freshness line, the four-card Usage Stats page, the optional header widget and the architecture behind it all.

What it is

This is part 2 of the Usage Forecast (predicted rate-limit exhaustion) page. It covers how to read each per-account forecast state, the threshold that fires an early warning and when it does and does not fire, the data-freshness line, the four-card Usage Stats page, the optional header widget, and the architecture behind the per-account slope, the pool rollup, the warning dispatcher and the renderer hosts.

Where to find it

The same surface as the full page: the Accounts popover off the utilization chip in the top-right of the main window, the per-account row in Settings → Accounts, the two controls in Settings → Notifications, the Usage tab under the Statistics sidebar group, and the optional header Usage widget you add from the header overflow menu or Settings → Widgets.

How it behaves

How to use it

  1. Where the per-account forecast row appears. The same projection row is rendered in two places so you can read it without leaving your current view:

    • Top-bar Accounts popover — click the utilization percentage chip in the top-right of the main window (e.g. 91% v) to open the floating Accounts panel. The global banner sits at the very top; below it, each OAuth account row shows the 5h/7d/Extra mini-bars, and the forecast row sits directly under those bars in the same row card.
    • Settings → Accounts — open Settings, scroll to the Accounts section. Same row of text appears just below the colored 5h usage bar on each OAuth account card. (The global banner is popover-only — it does NOT appear in Settings → Accounts.)

    API-key accounts do not show a forecast row in either location — API keys don't have a 5-hour rate-limit window to project against.

  2. What each per-account forecast state means.

    • Limit reached (red) — you are already at or above 100% utilization in this 5-hour bucket. Nothing left until the reset.
    • Calculating… (muted grey) — Omniscio has fewer than ~10 minutes of signal inside the current window. Slope estimates from short samples are noise, so the forecast holds off.
    • won't reach limit before reset (muted grey) — at the current burn rate, the bucket will reset (refill) before it ever reaches 100%. You're safe to keep working at the current pace.
    • ~X.Xh until exhausted, resets in Yh (amber) — your standard projection. Burn-rate math says you'll hit 100% at the displayed ETA, and the bucket will reset at the displayed time. Both are rough — they assume your current rate continues. (When the ETA is between 30 minutes and 1 hour, the unit switches to minutes — e.g. ≈45 min until exhausted, resets in 4h. The "Almost out" prefix only kicks in below 30 minutes.)
    • Almost out — ≈X min until exhausted, resets in Yh (red) — projected exhaustion is 30 minutes or less away. The "Almost out" prefix is intentional so urgency isn't communicated by color alone (matters for color-blind users and screen-readers).
    • · data N min stale (suffix) — your last successful usage poll is more than 30 minutes old, so the projection is using older numbers than usual. Refresh the account or wait for the next poll cycle.
    • · unusual rate (suffix) — the burn-rate exceeds 80%/hour, which is suspiciously fast. Could be a runaway agent, another machine logged into the same account, or a real intense session. Worth a glance.
  3. Early-warning setting (threshold only; no UI alert today). Open Settings → Notifications and scroll to the bottom:

    • The Usage forecast master toggle (default ON) controls whether the inline row AND the global banner appear at all and whether the warning dispatcher runs. Turn it off and you go back to plain percentage bars with no projection text and no global banner.
    • Under the toggle, the 5-hour exhaustion warning number input (range 0–5 hours, step 0.5, default 0) sets your alert threshold. 0 disables the dispatcher entirely but the inline forecast row still displays. Set it to (for example) 1 and the dispatcher activates when the projected exhaustion drops below 1 hour inside any 5-hour window.
    • The threshold input is nested under the master toggle and is hidden when the toggle is off (because the dispatcher can't run if the feature is off).
  4. When the warning dispatcher fires (and when it doesn't). Omniscio's main process emits a usage-forecast:warning push event (channel IPC.USAGE_FORECAST_WARNING) when all of the following are true at once:

    • The forecast state is projected (we have enough signal to compute slope).
    • The forecast says willExhaustBeforeReset = true (you're on track to hit 100% before the window resets).
    • The projected hoursUntilExhaustion is less than your configured threshold.
    • The forecast is NOT flagged as unusualRate (we suppress alerts when burn looks anomalous to avoid noisy alarms — the inline row still shows the "unusual rate" suffix though).
    • You haven't already been warned for this exact 5-hour window. Dedup is per-account, per-bucket, keyed on the window's resetsAt timestamp — a new window unlocks a fresh warning automatically.
    • The Usage forecast master toggle is on AND the threshold is above 0.

    Important — no renderer consumer today. The main process fires the push event but the renderer currently has no listener for usage-forecast:warning. The event is dispatched into the void — no toast, no sound, no badge is shown when the threshold is crossed. The inline row and global banner are the only user-visible early-warning surfaces for now. A renderer consumer (OS notification or toast) is the planned next step for this feature.

    (Warnings fire from per-account state only. The global banner is informational; it does NOT have its own warning dispatcher.)

  5. How to disable. Two levels:

    • Hide everything (rows + banner): Settings → Notifications → Usage forecast toggle → off. The global banner, the inline rows under each account, and the warning dispatcher all go silent.
    • Silence the dispatcher (rows and banner stay): Settings → Notifications → 5-hour exhaustion warning → set to 0. Rows and banner still display so you can read projections; the dispatcher just doesn't fire.
  6. The "What this banner means" explainer. A small "i" sits beside the banner's verdict line and reveals a plain-English legend that decodes the whole banner: the health check + color, the rolling 5-hour limit (a window that refills every 5 hours, so hitting it is a brief pause on one account — not running out of Claude), the weekly limit (the one that can pause you for days), what coverage % means (over 100% is comfortable, under 100% runs tight before reset), and what "sessions left" estimates. It exists because the green "healthy" glyph next to a ~2.5h until your 5-hour limit headline read as a contradiction. It adapts to the device: on a hover-capable device the "i" reveals the legend as a tooltip (hover or keyboard-focus); on a touch device it is a tap-toggle that expands the legend inline (a hover bubble can't open on a tap). It appears in every state where a verdict renders, and the legend copy is deliberately conceptual (no exact numbers) so it stays accurate even if the tier thresholds change.

Data freshness ("Updated Xm ago")

The bars and the forecast are only as current as the last successful poll, so the Accounts surface tells you when it was last refreshed — you can trust (or distrust) the numbers at a glance instead of guessing whether a static-looking bar is fresh or frozen:

  • The top-bar popover footer and Settings → Accounts (on the "Login Accounts" header) both render a small Updated Xm ago line. It's driven by the oldest login account's fetch time — the honest worst case for the whole pool, not just the active account — and re-renders every ~30 seconds so it visibly ticks up while the panel sits open (a label that never moves itself reads as "frozen").
  • It stays calm grey at normal cache age and turns amber only once that oldest account is more than 30 minutes stale — the same 30-minute gate that drives the · data N min stale banner suffix. Before the first successful fetch it reads Not checked.
  • The Refresh button beside it forces a fresh fetch of every account and spins while the fetch is in flight.

Expect a healthy pool to usually read Updated ~10–14m ago and tick upward: Omniscio samples each account every ~15 minutes and the renderer re-polls every ~5 minutes behind a ~10-minute cache, so a 10–14-minute age is the normal cadence, not staleness. The amber threshold (30 min) sits deliberately well past that, so amber means something genuinely stalled.

Usage Stats page

The Accounts popover gives you a one-line forecast, but for the full picture there's a dedicated Usage Stats sub-page reached from the Statistics virtual project in the Omniscio sidebar group — once open, the project's middle column shows two tabs at the top (Overview and Usage); click Usage to land on the page. The page is a 2×2 grid of four cards, each a self-contained chart with its own data fetch. Together they answer four different questions about your pool: where is the burn heading, when does it usually peak, which account is closest to capping, and how does the next week look at a glance.

  1. Pool Trajectory (Last 7 days → Next 7 days) — stacked line chart with one row for the 5-hour bucket and one for the weekly bucket. A solid line traces the past 7 days of actual pool burn, a dashed line continues into the next 7 days as the HOD-walk projection, and a translucent band around the dashed segment shows the high/low standard-deviation envelope. A single vertical "now" marker spans both rows. Per-row green-dashed verticals mark account resets; a red-dashed horizontal sits at 100% on each row. A corner toggle highlights one bucket at a time, but both rows always render.
  2. Burn Rate Heat-Map (HOD × DOW) — two 24×7 grids stacked top-to-bottom (5h on top, weekly below). Each cell's opacity maps to that hour-of-day × day-of-week's mean burn delta. Outlined-amber cells flag low-confidence cells with only one sample; dashed-empty cells mean no samples at all. Hover any cell for a tooltip with mean ± std and n samples.
  3. Per-Account Pool Contribution — one horizontal bar per OAuth login account. Each bar shows the account's current utilization as a solid fill, the projected addition through the bucket horizon as a striped overlay, and the 100% headroom as a thin outline. Color tiers: green below 70%, amber 70–95%, red above 95%. Reset times sit as text beside each account label. A toggle picks 5h or 7d — one bucket at a time.
  4. Daily Pool Projection (Next 7 days) — one vertical bar per day, height = projected weekly-bucket utilization for that day, color-tiered identically to the per-account bars. A green dot sits below any day where one or more accounts reset.

All four cards share a single pool walk (walkPoolNativeWithTrace) so the chart you read against the heat-map matches the trajectory line above it — no source-of-truth drift between views. Each card fetches independently, so a single failed fetch leaves the other three intact.

Audit callouts. Each card renders an amber inline notice below its chart when its detector fires. Trajectory fires when the projected slope would breach 100% inside the bucket's planning horizon AND the recent-window mean burn is more than 20% above the historical rate — signal: "you're burning hotter than usual right now". Heat-Map fires when the cell containing "now" shows a ≥30% mismatch between projected and historical burn — signal: "this hour is busier than your usual pattern". Per-Account fires when any one account is at ≥90% utilization in either bucket — signal: "one account is about to cap out". Daily fires when more than half of the projected 7-day bars exceed 90% AND the headline coverage is below 100% — signal: "the next week looks tight". (Daily currently receives null for headline coverage in v1, so it stays dormant pending a v2 follow-up.)

Cold-start gating. When fewer than 7 distinct calendar days of usage samples exist inside the 14-day window (distinctDays < 7), all four cards collapse simultaneously to a Learning your patterns — N more days needed hint. The gate is centralized in the tab component off the heat-map payload's distinctDays, so the four cards never go out of sync — either they all show their chart or they all show the cold-start hint.

Where to find it. Two entry points open the page: the AccountIndicator popover has a View usage stats → link row in its footer, directly above the Updated-timestamp + Manage Accounts row; and Settings → Accounts has an Open Usage Stats → link button inside the Accounts heading block, after the description text. Both navigate to the Statistics virtual project and switch the Statistics tab to Usage.

Where the code lives.

  • The tab host: /src/renderer/src/features/statistics/usage/UsageTab.tsx.
  • The four chart components: /src/renderer/src/features/statistics/usage/UsageTrajectoryChart.tsx, /src/renderer/src/features/statistics/usage/UsageHeatMap.tsx, /src/renderer/src/features/statistics/usage/UsagePerAccountBars.tsx, /src/renderer/src/features/statistics/usage/UsageDailyProjectionChart.tsx.
  • The pure audit detectors: /src/renderer/src/features/statistics/usage/usage-audit.ts.
  • The four IPC channels — IPC.USAGE_FORECAST_GET_TRAJECTORY, IPC.USAGE_FORECAST_GET_HOD_CELLS, IPC.USAGE_FORECAST_GET_PER_ACCOUNT, IPC.USAGE_FORECAST_GET_DAILY_PROJECTION — and their handlers in /src/main/ipc/usage-forecast-handlers.ts.

Usage widget (optional header chip)

A third place usage can appear — a compact, always-visible chip in the top header row, for people who want a running read on capacity without opening the Accounts popover or the Stats page. It is opt-in and off by default: you add it from the header's ⋯ overflow menu or Settings → Widgets (it is a normal header widget, so it shows / hides / reorders exactly like the account pill and the Hardcore indicator — see the header-widget contract for the mechanics).

  • What it shows. For the pool's most-constrained account per window: the 5-hour usage % and the weekly usage %, each with a reset countdown, colored by the same threshold gradient used everywhere else (green < 60 · yellow 60–69 · orange 70–89 · red 90+). "Most-constrained" = the account nearest its limit for that window, so the chip answers "do I have room to start another big task?" honestly for a multi-account pool.
  • Click / hover. Click opens the full Stats → Usage page; the chip's popover shows the full 5-hour + weekly bars, reusing the same MiniUsageBar as the Accounts popover.
  • Field options (which fields it shows). Three toggles in Settings → Widgets → Usage widget — Show 5-hour usage, Show weekly usage, Show reset countdowns (all on by default; settings usageWidgetShowFiveHour / usageWidgetShowWeekly / usageWidgetShowResets). Turn any off to slim the chip down to just the fields you care about.
  • No new data. It reads the same per-account allUsage the app already polls (the ACCOUNT_USAGE_UPDATED push) — no new IPC, DB, or background work. It is hidden on-camera under the same presentation-mode setting as the account widget (usage numbers are sensitive on a screen share).
  • Discovery (one-time tip). Because the widget is opt-in and off by default, a one-time, dismissible tip surfaces on a later launch to point new users to it: a single informational toast ("add a Usage widget to your header…") with a one-click Add it that pins it. It fires at most once per install (localStorage amc-usage-widget-discovery-tip-shown), desktop only, waits ~30s after the dashboard mounts, never fires over onboarding / a sign-in gate / an open modal (it retries a few minutes later), and never fires if you already pinned the widget yourself. It is the direct sibling of the "press ? to see every shortcut" tip (useShortcutDiscoveryTip).
  • Where the code lives. /src/renderer/src/components/ui/UsageHeaderWidget.tsx (the widget), /src/renderer/src/lib/pool-usage-summary.ts (selectPoolUsageSummary — the most-constrained-per-window reducer), the usage catalog entry + HEADER_WIDGET_BLOCK_IDS in /src/renderer/src/features/toolbar/toolbar-catalog.ts, the three toggles in /src/renderer/src/features/toolbar/ToolbarSettings.tsx, and the discovery tip in /src/renderer/src/hooks/useUsageWidgetDiscoveryTip.ts (wired in /src/renderer/src/App.tsx).

For agents

How it works

The per-account forecast math is a pure function in /src/shared/usage-forecast.ts — forecastFiveHour(util, resetsAt, now, lastPolledAt, recentSamples?) returns a discriminated union with one of five states: 'no-data' | 'no-usage-yet' | 'calculating' | 'projected' | 'exhausted'. The function uses a recency-biased slope: it computes both the whole-window slope (utilization% / hoursElapsed from the start of the current 5-hour window to now) AND a trailing-window slope from the last 90 minutes of samples (via recentSlopePctPerHour in /src/shared/usage-forecast.ts), then takes the maximum of the two. This catches the idle-then-burst pattern that flat-extrapolation misses — if you spent the first 3 hours of the window doing almost nothing then ramped to 50%/hour for the last 30 minutes, the whole-window slope underestimates and the headline says "you have 4 hours" when you actually have 30 minutes. Max-of-two means the forecast is never less safe than the legacy whole-window math but is more responsive when burn accelerates. Projected total at reset is slope × 5h; exhaustion ETA is (100 − util) / slope. The function tags the result with unusualRate: true when the slope exceeds 80%/hour and with staleMinutes when the most recent poll is older than 30 minutes. Sample windows shorter than 10 minutes return 'calculating' to avoid noisy projections from too few data points.

The trailing-window slope helper (recentSlopePctPerHour) takes the last 90 minutes of samples and applies three gates before reporting a slope: (1) window clamp — samples older than the current 5-hour window's start are excluded, so a sample from the previous (already-reset) cycle never poisons the slope; (2) span gate — the oldest and newest samples in the window must be at least 30 minutes apart, otherwise the slope is too noisy to use; (3) freshness gate — the newest sample must be no older than 30 minutes, otherwise we're computing on stale data; (4) reset detection — if fiveHourResetsAt advanced between the oldest and newest sample, the bucket reset mid-window and the delta is meaningless. When any gate trips, the helper returns null and forecastFiveHour falls back to whole-window math alone (which is the pre-2026-05 behavior).

The global rollup is a separate pure function in /src/shared/usage-forecast.ts — forecastGlobalExhaustion(accounts, now, samplesByAccountId?) returns a GlobalBucketForecast discriminated union with five states: 'no-data' | 'no-usage-yet' | 'calculating' | 'all-exhausted' | 'projected'. It filters to OAuth-only accounts, bails to 'no-data' if any are missing usage, runs forecastFiveHour for each one (passing the per-account samples from samplesByAccountId.get(accountId) so each account gets its own recency-biased slope — mixing accounts would smear the slope and is intentionally avoided), then walks the per-account forecasts: any 'calculating' propagates to a global 'calculating'; all 'exhausted' propagates to 'all-exhausted' with the soonest reset; otherwise it computes T = max(perAccount.tExhaust) (the moment the slowest account hits 100%) and verifies every earlier-exhausted account's resetsAt is at or after T. If yes, willAllExhaustBeforeAnyReset = false (the muted-green "Won't run out before reset" branch). If no, the result includes hoursUntilAllExhausted, the soonest-reset hour count, and propagated staleMinutes/unusualRate flags.

The forecast for the active 5-hour bucket is computed once per cache write inside auth-service.cacheAndReturn (see /src/main/services/auth/auth-service.ts — search for forecastFiveHour) and is included in the ACCOUNT_USAGE_UPDATED push payload. The cache-write site loads the last 6 hours of samples per-account (covers the 90-minute trailing window with comfortable margin) via getUsageSamplesForAccountSince(accountId, cutoff) from /src/main/db/queries-usage-samples.ts and passes them as the 5th argument to forecastFiveHour. The DB query is account-scoped on purpose — pulling all-account samples on every cache write would scale linearly with N accounts; the scoped query stays flat. The DB load is wrapped in a try/catch so the boot-race case (auth service initializes before the SQLite connection is ready) degrades gracefully to the legacy whole-window slope rather than throwing. This means the renderer never has to make a separate IPC call — it gets the projection alongside the raw utilization on every poll. There is also a pull channel IPC.USAGE_FORECAST_GET ('usage-forecast:get' — see /src/shared/ipc-channels/) that returns { fiveHour: BucketForecast } per account if the renderer needs to fetch on demand. The pull-channel handler at /src/main/ipc/usage-forecast-handlers.ts loads all-account samples once per call (the 14-day cutoff is shared with the HOD coverage line below the headline so we don't pay two SQLite scans per forecast tick) and groups them via a Map<accountId, RawSample[]> before dispatching forecastFiveHour per account.

The warning dispatcher lives in /src/main/services/usage/usage-forecast-notifier.ts — createUsageForecastNotifier({ config, emitPush }) returns an object with an evaluate(accountId, bucket, forecast, resetsAt) method. It bails out for any of the suppression conditions in the bullet list above, then writes {accountId}:{bucket} -> resetsAt into settings.lastWarnedForecast after firing. Because the dedup key is the window's resetsAt, the next 5-hour window will have a different resetsAt value and naturally re-arm without any timer logic. AppSettings fields usageForecastEnabled (default true) and usageForecastWarnHoursFiveHour (default 0, max 5, step 0.5) live in /src/shared/types.ts and are validated in /src/shared/ipc-schemas.ts.

On the renderer, the standalone per-account inline row was removed — its rendering folded into the global banner /src/renderer/src/features/settings/GlobalUsageForecastRow.tsx. That component renders null for the 'no-data' and 'no-usage-yet' states (no signal worth showing), a muted neutral row for 'exhausted' and 'calculating', and the headline-plus-reset-clause format for 'projected'. Color is determined by pickColorClass() — red when hoursUntilExhaustion <= 0.5, amber otherwise (and only when willExhaustBeforeReset is true). The settings UI lives in /src/renderer/src/features/settings/sections/notifications/NotificationSettings.tsx (master toggle plus nested threshold input), and both controls have entries in /src/renderer/src/features/settings/settings-search-index.ts (usage-forecast-enabled and usage-forecast-warn-hours-five-hour) so they're discoverable via the global Ctrl+K settings search.

The global banner component is /src/renderer/src/features/settings/GlobalUsageForecastRow.tsx. It accepts a GlobalBucketForecast prop, returns null for 'no-data' and 'no-usage-yet', renders a muted-grey "Calculating…" for 'calculating', a red "All accounts exhausted — resets in …" for 'all-exhausted', a muted-green "Won't run out before reset" when willAllExhaustBeforeAnyReset is false, and the standard <duration> until you run out line otherwise. The 3-tier color rule (URGENT_HOURS = 0.5, WARN_HOURS = 2) plus the Almost out — / Running low — text prefixes mirror the per-account row's spec so stacked global+account rows feel consistent. The component also fires a one-shot feature_events row keyed on the displayed state (telemetry only re-fires on state transitions, not on every numeric tick).

The banner's verdict line carries an on-demand explainer affordance (the "What this banner means" legend). ForecastVerdict is the shared verdict-row shell all four states render through — it wraps the changing verdict text in the role="status" live region and appends <UsageExplainer> (the "i") beside it. UsageExplainer branches on isTouchOnlyDevice(): a hover-capable device renders <UsageExplainerHoverTip> (the shared <InfoTooltip>, hover / keyboard-focus), a touch device renders <UsageExplainerTapPanel> (a shared <IconButton> toggling a basis-full inline panel). The split keeps each variant's hooks unconditional (Rules of Hooks), and both render one shared <UsageExplainerContent> legend whose copy lives under the settings.usageForecast.explainer.* keys in en/ (the catalog is sharded: common.00.json, common.01.json, …). The touch toggle carries data-ui-anchor="usage-forecast-explainer-toggle" (co-located in GlobalUsageForecastRow.ui-anchors.ts). Locked by the "Explainer affordance" cases in /tests/unit/components/global-usage-forecast-row.test.tsx.

Three host components mount the forecast UI, all gated on forecastEnabled (the AppSettings flag):

  • Settings → Accounts — /src/renderer/src/features/settings/sections/accounts/AccountSettings.tsx renders the per-account row directly under each OAuth account's mini-bars block. (Settings does NOT show the global banner.)
  • Top-bar Accounts popover — /src/renderer/src/components/ui/AccountIndicator.tsx is the host for both the global banner and the per-account rows. It computes the GlobalBucketForecast via useMemo keyed on allUsage (so it doesn't re-aggregate on unrelated re-renders) and renders <GlobalUsageForecastRow> above the per-account list. Each per-account row is rendered by /src/renderer/src/components/ui/account/LoginAccountRow.tsx, which reuses the same <UsageForecastRow> as Settings.

Related

This is part 2 of the Usage Forecast (predicted rate-limit exhaustion) page — what the feature is, where to read it, and the verdicts and algorithms behind the headline live there.

Last verified 2026-10-02