Skip to content

Orgabot is in private alpha. Request access, or join our Discord.

← All documentation
The organization

Budgets, quota, and analytics

Two different kinds of limit (USD spend budgets over real accrued cost, and quota-denominated tracking for subscription executors), plus the fallback ladder when a pool runs dry.

Orgabot tracks two different things that both feel like "running out" and must never be added together:

KindWhat it measuresWhere it shows up
Spend budgetAccrued real USD cost on API-billed workorgabot budget status, the Budgets tab
QuotaConsumption of a subscription window (a Claude or ChatGPT plan)The same surfaces, denominated in quota, not dollars

Counting subscription usage as cash spend produces false "budget exceeded" alarms. The fix is to split budgets by billing class, not to raise the limit.

Spend budgets

orgabot budget set <org> --level org|role|actor [--role slug] [--actor id]
                      --period daily|weekly|monthly|mission --limit <usd>
                      [--on-exceed warn|approval|block] [--warn-at f]
orgabot budget list <org> | remove <org> <budget-id> | status <org>

Budgets are enforced pre-flight and mid-run, at organization, role, or actor level, over accrued real spend from the analytics journal.

Quota-denominated budgets

Subscription usage is tracked off a calibrated QuotaState rather than USD:

  • a calibrated-exhausted window routes or holds a launch exactly like an exhausted pool;
  • an uncalibrated estimate above the warn threshold only warns, because an estimate must not block real work.

The status-bar quota meter

The dashboard's mission status bar carries a quota meter: a small gauge whose label is the smallest remaining allowance across every configured provider, because that is the one that will stop work first. The label names whose allowance it is (est. 42% left · Claude Max 7d week), so the collapsed bar and the expanded panel can never look like they disagree. A leading est. means the figure is an estimate rather than an observation; it is spelled out because at status-bar size a ~ reads as a minus sign. It stays visible when the bar is collapsed, and it refreshes about once a minute rather than on every dashboard poll.

The gauge itself is a dial: its needle points at that remaining headroom and takes the same colour as the level. When nothing is metered, or the read failed, or no capacity seed can be stood behind, the dial is drawn with no needle at all and the label shows no percentage. An empty dial means "no reading", never "out of quota".

The panel header says how old the reading is (read 2m ago), and says outright when the last refresh failed rather than passing the previous figures off as current. The meter refreshes on its own slow cadence while the dashboard repaints every few seconds, so without that a number that moved between two glances looks like the meter disagreeing with itself.

Clicking it expands a column per configured provider, side by side, each answering the same three questions in as few words as they take:

  • a subscription plan meters its plan windows: est. 85% used, est. 15% remaining, and resets at 3:00 when an observed limit event supplied a reset time (a rolling window says it has no fixed reset instead of showing an invented date);
  • an api key meters the dollar budget that governs it: 45% spent, $90.00 of $200.00, 55% remaining ($110.00), and the date the period rolls over. The figure is the spend Orgabot itself journaled against that provider, so it is a floor: a key used outside Orgabot is invisible here, and the column says so;
  • any individual model pool observed exhausted for that provider, with its reset (window mode) or the fact that it needs credits (entitlement mode).

A provider whose plan has no usage credits at all is called out separately, at the top of its column, as subscription inactive. That is a different condition from being out of quota: a cancelled, lapsed, or unpaid plan is never refilled by a window reset, so the panel tells you to renew it or route the role elsewhere instead of leaving you waiting for a renewal that is not coming.

The budget shown is the tightest active organization budget governing that key. A provider with no plan seed and no budget says it is not metered, and how to meter it, rather than showing an invented figure.

Which usage the meter counts

Orgabot's analytics journal only ever contains Orgabot's own missions. A meter built on it alone measures how much of the plan Orgabot spent, not how much the plan spent, so on a machine where you also use the claude terminal the dashboard and the CLI's own /usage disagree in both directions &mdash; and the capacity a limit event measures drifts by the same gap, which is how a weekly window ends up sitting past a denominator that was never the plan's.

So for Claude Code, Orgabot reads the wider record: Claude Code's own session transcripts, which every session on the machine appends to, including interactive terminal work and IDE sessions. Each window row says which record it counted:

  • counted across every Claude Code session on this machine, not just Orgabot's &mdash; the machine-wide read;
  • counted from Orgabot's own journal only &mdash; the narrower fallback, used for a provider with no such transcripts, when you have opted out, or while the ledger is still catching up.

Two rules keep it honest. The two records are combined by taking the larger, never their sum: Orgabot's own missions run through the same CLI and are already inside the transcripts, so adding them would double-count. And the machine-wide attribution is claimed only on a complete read &mdash; a ledger still catching up raises the floor but is not presented as a measurement.

The ledger is incremental (transcripts are append-only, so only the new tail is ever read), byte-budgeted per refresh, and kept for 15 days. Point it elsewhere with ORGABOT_CLAUDE_SESSION_DIR, or set that variable to the empty string to opt out entirely and keep the journal-only estimate.

When the meter says "unknown"

A percentage is only ever shown when Orgabot has something behind it. Two cases make a plan window read not metered instead of showing a number, and neither is bad news:

  • No capacity known. Claude Max ships a built-in token-capacity seed. Codex and Grok do not, because there is no published token figure for a ChatGPT or xAI plan that Orgabot could stand behind, and no limit event can measure one for them either (see below). Their windows still count usage; set your real ceiling with executorQuotas.
  • Usage past the capacity. If an uncalibrated estimate has already run past the capacity, that disproves the capacity, not your plan. The window reports unknown rather than claiming "0% left".

The window Orgabot sums over

A plan like Claude Max does not meter a rolling window: it meters a fixed block that empties at a wall-clock time. Summing the last five hours instead is right in the middle of a block and badly wrong just after one resets, because the rolling window still holds the whole previous block. Measured on a live install: 53% of the session used against the CLI's own /usage saying 2%, with the real block only minutes old. Same arithmetic, different hours.

So when Orgabot has ever seen the plan state a reset time, it puts the window on that cadence: the observed reset, stepped forward by the window length, gives the block you are in now. The row then has a real reset to show &mdash; resets at 6:50pm &middot; projected from an observed reset &mdash; instead of rolling, no fixed reset.

The anchor must be a reset the plan stated, which Orgabot recognises by it landing on a whole minute. The reset times Orgabot derives itself (the observation's own timestamp plus a countdown, or plus the window length) carry seconds and milliseconds; anchoring on one of those would manufacture a boundary out of arithmetic on Orgabot's clock. A window with no such observation stays rolling rather than guessing a phase.

It is still an estimate, and it is marked projected for a reason: it shifts which hours are summed, it never pins a percentage, and an idle stretch longer than the window moves the plan onto a new cadence (the next block starts at your next request). Anchors older than 30 days are dropped for that reason. That horizon is longer than the 14 days a capacity floor gets, because the two observations answer different questions: a reset is a claim about the plan's phase, which a downgrade does not touch, while a floor is a claim about its size, which a downgrade invalidates.

Capacity measured from an observed limit

The seeds are guesses, and a busy operator sits permanently past them: every window then reads "not metered" and the meter shows no figure at all even with plenty of quota left. So a limit event also measures the plan.

When a provider reports a limit, Orgabot sums what had been spent into that window before the banner landed &mdash; from the same record the live window uses, so the numerator and the denominator are never measured over different usage. The plan held at least that much, so a seed below that proven floor is a disproved seed and the floor becomes the capacity. The row says capacity measured at an observed limit when that is what you are reading, and the Budgets &amp; Usage tab badges it measured capacity.

Three properties keep it honest:

  • it can only ever raise a capacity, never lower one. Even the machine-wide read is a floor rather than a ceiling &mdash; usage on the same plan from another machine is not in it &mdash; so treating it as exact would meter pessimistically;
  • only the machine-wide record may measure a floor. Orgabot's own journal is blind to every token you spend driving the same plan outside Orgabot, so the floor it reports at a limit banner is Orgabot's share of the window, not the plan's capacity &mdash; and that share can be arbitrarily small. On a live install it measured a Codex capacity of eight tokens, and dividing by it rendered an exhausted ChatGPT plan as est. 0% used &middot; 100% remaining &middot; estimated healthy. So a provider with no machine-wide record (today: everything but Claude Code) stays not metered however many limits it hits, rather than reporting a percentage of a number nobody measured. Its limit events still pin the window while they are live;
  • observations older than 14 days are ignored, because a floor proved on a plan you have since downgraded is no evidence about the plan you have now.

Two more things the meter deliberately does not do. It never reports a plan as out on an estimate alone: seed capacities are operator configuration and any usage record Orgabot holds is a floor, so only an observed limit event may say "out of quota". And it meters plans, not models: a subscription window belongs to the plan, and a provider's models draw on the one window; the per-model dimension that genuinely exists is the exhausted-pool row.

Quota windows and capacities are operator configuration (executorQuotas in ~/.orgabot/config.json); the full picture, including dollar budgets, lives in the Budgets &amp; Usage tab.

When a pool runs dry

orgabot organization role-fallback set <org> <role> "claude:opus,codex"
orgabot organization quota-policy set <org> [<role>] wait-then-escalate|escalate
orgabot pools list | clear <provider> [model]
orgabot pools set <provider> [model] (--reset-at <ISO> | --entitlement)
orgabot pools holds | resume-due | resume

The fallback chain is an ordered list of engine[:model] alternatives a role uses past an exhausted pool. A mission walks the chain mid-run, so an exhaustion part-way through does not lose the round. When the chain is fully spent, the mission holds and waits for the window to reset, then escalates per the quota policy.

Two rules keep the chain honest:

  • The chain is for unavailability only. A healthy executor is never abandoned because a round was slow or a review found something.
  • A belief must never shorten the chain. A stale "this pool is exhausted" record must reorder the chain, never delete an available executor from it. A skip at round start is a reordering, not a removal.

orgabot pools clear deletes a belief; orgabot pools set corrects one. When a provider states a reset further out than the stored record (or the record has no horizon at all), write the horizon you know to be true with --reset-at <ISO>; the record counts as observed, so the rung is genuinely skipped until then. --entitlement records a non-resetting credit exhaustion instead (re-checked after 24h). Note that only an observed horizon (an explicit reset time, or an entitlement) skips a rung at round start; an inferred default window is believed but the rung is still tried, and orgabot pools list / orgabot doctor group the two states separately.

Analytics

orgabot analytics backfill      # one-time, idempotent projection of existing stores

A metrics journal with provider cost accounting feeds the dashboard's Analytics and Budgets tabs. Backfill projects pre-existing stores into the journal without double-counting.