Repository navigation
Closed
5 / 55 of 5 issues completed
Copy link
Labels
enhancementNew feature or requestNew feature or request
Description
Settled in a grill-with-docs session: #2178 (Q1.1–Q3.4, ADR 0031).
Problem Statement
When a coding subscription hits a Plan window limit or spend cap, unattended thirdshift work fails even when another paid-for subscription Harness on the same machine still has allowance available:
- Long Spec runs can land every Ticket on one Harness and then fail on the final Spec review or Delivery when a weekly window or monthly spend cap runs out, branding the issue
agent-failedand counting toward the repository's Failure limit even though nothing is wrong with the issue. - Scheduled Passes keep firing against an exhausted subscription, burning startup attempts or failing Ready issues in a loop.
- Conversely, running reactive-only without an optional reserve ceiling drains 100% of the primary subscription overnight and leaves nothing for interactive Day shift work in the morning, while provider credit balances (such as Codex credits or Antigravity personal Google AI credits) can be spent silently before switching to another subscription.
- Meanwhile, the Day shift has no single place in the Console or CLI to see current Plan usage across the Fleet's shared subscription logins, which Plan windows are near or at their ceiling, when they reset, and which configured route each machine will use next.
Solution
Introduce Plan usage (composed of Plan windows and any spend cap) across the five subscription Harnesses—claude, codex, muse, agy and grok—leaving API-key Harnesses (opencode and kimi) out of subscription monitoring:
- Passive + official non-billed observation: Every running session passively updates its machine's local Plan usage state as structured Harness wire-frame events arrive (including Claude
rate_limit_eventframes at all utilization levels, Codex sessiontoken_countevents, and terminalUsageLimitrejections across all five subscription Harnesses). On demand (with short TTL caches, never on an idle Console timer), thirdshift enriches local state with official zero-token CLI/RPC reads where supported (codexapp-serveraccount/rateLimits/read,muse serveusage/readandaccount/read, andclaude auth status --jsonfor plan/account identity), never scraping undocumented web/OAuth endpoints or touching Harnesses' private auth tokens. - CLI and Console visibility (
thirdshift plan-usageand/plan-usage): A dedicated CLI command (--fleet,--machine <name>,--json,--clear [<harness>]) and Console page (/plan-usage,/api/v1/plan-usage, defaulting to Fleet when peers exist) show configured routes and observed subscription Harnesses, grouping shared logins across Fleet machines by account identity when known, caching offline peer snapshots, piggybacking local Plan usage pushes onto post-Commandconsole report, and surfacing a Console header warning pill whenever a configured route is exhausted or at/above its reserve ceiling. - Proactive + reactive per-Step routing: Under
[harness](routes,max_utilization, per-routeallow_credits) and[security](routes), factory Commands using User config defaults select a route per session Step from machine-local Plan usage—routing proactively around cooldowns andmax_utilizationceilings (treating included Plan windows as the boundary ahead of credit pools by default, including--settings '{"useG1Credits":false}'on headlessagylaunches), and routing reactively within a Step when a session stops on structured provider Plan-usage evidence. Mid-Step fallback keeps local commits, discards uncommitted worktree edits cleanly, and starts a fresh session on the next route with zero added quota-anxiety prompt text. Explicit CLIharness/modelflags, Stepharnesspins, Experiments and Scoring runs stay pinned, and cybersecurity safeguard refusals still never route and still hold Self-merge. - Clean exhaustion without failing issues: When every allowed route is unavailable, scheduled Passes skip quietly (one Activity log line per kind on reason change, plus a status banner on
1 Inboxandthirdshift status), and live Commands end as Paused Commands (outcome = "paused"), stopping queued sibling Spec Tickets while active ones finish or salvage, and releasing the issue's Claim withoutagent-failed(restoringready-for-agentsubject to the Trusted account edit check, except Security fixes). A Paused Command that restoredready-for-agentsits in Done rather than Needs you and saves its Run notification without emailing underonly_unmerged, because Pickup continues it automatically once a Plan window resets.
User Stories
- As the Day shift, I want
thirdshift plan-usageand/plan-usageto show each configured route and observed subscription Harness with its Plan windows, utilization, reset times and cooldown status, so that I can see available subscription headroom at a glance. - As the Day shift, I want
thirdshift plan-usageand/plan-usageto default to--fleetwhen Fleet peers exist and group shared subscriptions across machines by account identity (claude,codex,muse), showing the freshest observation and naming the machines sharing that login, so that one shared subscription is not repeated six times or shown with stale per-host numbers. - As the Day shift, I want distinct subscription logins or unknown-identity Harnesses (
agy,grok) kept as separate rows per machine/account, so that employer or secondary accounts are never conflated with my personal login. - As the Day shift, I want NATted machines that push via
console reportto include their local Plan usage snapshot after each Command, and reachable Consoles to cache last-seen peer snapshots when a Fleet host is offline, so that viewing from my phone reflects laptop VM usage too. - As the Day shift, I want a warning pill in the Console header whenever any configured route is exhausted or at/above
max_utilization, linking to/plan-usage, while keeping9 Resourcesstrictly for per-host RAM, CPU and disk. - As the Day shift, I want
/plan-usageandthirdshift plan-usageto omit subscription Harnesses that are neither configured in routes, previously observed, nor signed in on the queried machine/Fleet, while visibly surfacing any configured route whose CLI is missing or signed out, so that headless servers without unused CLIs stay uncluttered without hiding misconfiguration. - As the Day shift, I want Claude
rate_limit_eventstream frames recorded passively at all utilization levels (not only>= 85%) alongside Codextoken_countframes and terminalUsageLimitrejections acrossclaude,codex,muse,agyandgrok, so that every session keeps local Plan usage fresh for free. - As the Day shift, I want active non-billed reads (
codexapp-server,muse serve,claude auth status) to run on demand with short TTL caches (~60s when viewing, ~5m or pastresets_atbefore routing) and never on an idle Console background timer, so that an idle Console stays near-zero CPU and network. - As the Day shift, I want
opencodeandkimileft out of subscription Plan-usage monitoring, and thirdshift never to scrape undocumented web/OAuth endpoints or touch Harnesses' private auth tokens, so that API-billed Harnesses and private credentials are untouched. - As the Day shift, I want to configure an ordered
harness.routeslist (supporting both"claude"strings and inline tables withharness,model,effort,max_utilizationandallow_credits) and an optionalharness.max_utilizationceiling in User config, editable throughthirdshift configand Console/config, so that I can hold back a Day shift reserve (such as 85%) and route across Harnesses or same-Harness model pools. - As the Day shift, I want
harness.routes = [](the default) to preserve single-Harness behavior onharness.defaultwithmax_utilization = 100, so that reserves and fallbacks are strictly opt-in and never route to an unlisted Harness or paid API credential. - As the Day shift, I want included Plan windows treated as the routing boundary ahead of Codex credit balances and Antigravity personal Google AI credits (passing
--settings '{"useG1Credits":false}'on headlessagylaunches) unless a route explicitly setsallow_credits = true, so that unattended factory runs use my other paid subscriptions before burning extra credits. - As the Day shift, I want
[security]to support an optionalsecurity.routeslist and otherwise walkharness.routeswhensecurity.harnessis plan-limited or fails before first turn, while never routing away from a cybersecurity safeguard refusal (which still holds Self-merge), so that Security reviews and Security runs keep working under plan limits without weakening ADR 0021's safety hold. - As the Day shift, I want factory Commands using User config defaults to route per session Step—leaving completed Steps and landed Spec Tickets untouched and starting a fresh session on the new Harness (never a cross-Harness Resume)—so that a Spec run whose 17 Tickets landed on Claude can finish its Spec review and Delivery on Codex.
- As the Day shift, I want explicit CLI
harness/modelflags, explicit Custom-process Stepharnesspins, Experiments and Scoring runs to stay pinned without silent cross-Harness routing, so that targeted manual runs and Evals remain faithful to what was asked. - As the Day shift, I want reactive mid-Step fallback (
before_first_turn == false) to keep local commits, discard uncommitted worktree edits cleanly (switchingImplementfrom fresh to continuation once commits or an open PR exist, and aborting plus re-applying any pinned conflict merge on conflict Repair), link every configured route's Factory skills, and add zero quota-warning text to the Session prompt, so that the next Harness starts from clean git state without rushing over quota anxiety. - As the Day shift, I want routing decisions to stay strictly machine-local (no Fleet HTTP calls during Pass or Step admission), learning shared-login exhaustion on passive-only Harnesses via ~1s
before_first_turnrejections and revalidating expired or timestamp-less cooldowns with exponential backoff (1h → 2h → 4h → 6h cap) plus manualthirdshift plan-usage --clear [<harness>], so that overlapping weekly windows and monthly spend caps recover safely without hammering depleted accounts. - As the Day shift, I want scheduled Passes to skip quietly when every allowed route on the machine is unavailable (logging once on reason change and showing a status banner on
1 Inboxandthirdshift status), so that cron never loops or fails Ready issues while subscriptions are exhausted. - As the Day shift, I want a live factory Command whose allowed routes are all exhausted to end immediately as a Paused Command (
outcome = "paused") without sleeping in-process—stopping queued sibling Spec Tickets while active ones finish or salvage, and releasing its Claim withoutagent-failed(puttingready-for-agentback unless an untrusted edit requiresneeds-triageor a Security fix requiresagent-failed)—so that account exhaustion never tripspickup.failure_limitand Pickup self-heals after reset. - As the Day shift, I want a Paused Command that restored
ready-for-agentfor automatic Pickup continuation to sit in Done rather than Needs you (and save its Run notification without emailing whenonly_unmergedis set), while Paused Commands that Pickup will not retake wait in Needs you and send their notification, so that Needs you and overnight emails stay strictly actionable. - As the Day shift, I want pull-request and notification
Built withlines to keep their single primary tuple for Scoring compatibility while.models.json, the Command record and the Run notification's Session models list record every Step's actual Harness and Model, so that multi-Harness runs are transparently attributed without breaking Scoring.
Implementation Decisions
- Harnesses in scope and data sources:
- Scope covers the five subscription Harnesses:
claude,codex,muse,agyandgrok.opencodeandkimiare excluded as API-key Harnesses. - Passive session-stream capture updates the machine's saved Plan usage state atomically on every running session:
clauderate_limit_eventframes across all statuses (allowed,allowed_warning,rejected) and all utilization percentages plusClaudeApiErrorlimit facts (overageStatus,overageDisabledReason,resetsAt,overageResetsAt),codexsessiontoken_countrate_limits, and terminalUsageLimitprotocol errors across all five subscription Harnesses. - Active zero-token CLI/RPC probes run only on demand:
codex app-serverstdio JSON-RPCaccount/rateLimits/read(accountId,planType,ordinaryUsageAllowed,rateLimits/rateLimitsByLimitId,credits,spendControlReached),muse servestdio JSON-RPCusage/read(tier,observedAtMs,window,weekly) andaccount/read(state,label,credentialRequired), andclaude auth status --json(loggedIn,subscriptionType,planTier,orgId,email).grokandagycurrently expose no working headless non-billed window probe and rely on passive session-stream capture (withgrokready to adopt its CLI JSON output if implemented upstream later). - Never call undocumented web/OAuth endpoints or read Harnesses' private auth tokens.
- Scope covers the five subscription Harnesses:
- Refresh cadence and caching:
- Viewing (
thirdshift plan-usageor/plan-usage//api/v1/plan-usage) refreshes local active probes when the saved snapshot is older than ~60 seconds. - Routing refreshes candidate
codex/museprobes only when a candidate's saved snapshot is missing, pastresets_at, or older than ~5 minutes. thirdshift consoleruns no idle background timer for Plan usage (ADR 0016).
- Viewing (
- User config schema (reusable by add fallback model setting for provider issues, like codex model at capacity #1546):
[harness] default = "claude" routes = [] # ordered entries: "claude" or { harness = "agy", model = "...", effort = "...", max_utilization = 90, allow_credits = false } max_utilization = 100 # 1..=100; 100 switches proactively only on confirmed exhaustion [security] harness = "" routes = [] # optional ordered entries in the same format; when empty, plan-limited security.harness walks harness.routes
- All new keys appear in
DEFAULTSsothirdshift configand Console/configinventory and validate them automatically. - With
harness.routesempty, thirdshift usesharness.default(or Claude Code) with no cross-Harness fallback. String entries inherit[harness.<name>]'smodelandeffort; inline table entries override optionalmodel,effort,max_utilization(1–100) andallow_credits(defaultfalse).
- All new keys appear in
- Credit and overage boundaries:
- Included Plan windows (
usedPercent/utilization) govern proactive routing by default (allow_credits = false), routing away atmax_utilizationor100%before spending Codex credit balances (credits.balance). - Headless
agyinvocations pass--settings '{"useG1Credits":false}'unless the chosen route setsallow_credits = true.
- Included Plan windows (
- Per-Step routing, pinning, and mid-Step clean reset:
- Routing is evaluated per session Step on factory Commands whose Harness comes from User config defaults, using strictly machine-local state (zero Fleet HTTP calls during Pass or Step admission).
- Explicit CLI
harness/modelflags, explicit Custom-process Stepharnesssettings, Experiments and Scoring runs stay pinned. CybersecuritySafeguardRefusalnever routes to another Harness and still holds Self-merge (ADR 0021). - Only structured Harness wire-frame events and official non-billed CLI/RPC facts count as Plan-usage exhaustion—never bare HTTP 429 transient throttling and never agent-writable assistant/tool text (ADR 0030, ADR 0031).
- Each configured route is tried at most once per Step. A cross-Harness switch always starts a fresh session (never a cross-Harness Resume), and every configured route's Factory skills are linked into the worktree.
- When a session stops on a confirmed Plan-usage limit mid-Step after doing work (
before_first_turn == false), local commits are kept, uncommitted worktree edits are discarded (git reset --hard HEADandgit clean -fd, preserving.git/info/excludehandover files; for merge-conflict Repair,git merge --abortfollowed by re-merging the pinned conflict commit),Implementswitches from fresh to the existing continuation prompt when commits or an open PR now exist on the branch, and zero new quota-warning text is added to any Session prompt.
- Cooldown clearance and revalidation:
- Exhaustion cooldowns hold until
resets_atwhen reported, or for an initial 1-hour revalidation backoff (doubling on repeatedbefore_first_turnlimit rejections: 1h → 2h → 4h → 6h cap) whenresets_atis absent. - Past
resets_ator backoff expiry, a route enters revalidating state:codexandmusere-probe before selection (requiringordinaryUsageAllowed != falseandspendControlReached == falseon Codex), while passive-only Harnesses (claude,agy,grok) try once on the next Step and immediately re-arm the doubled backoff and fall back if rejectedbefore_first_turn(handling monthly spend caps that outlive weekly resets). thirdshift plan-usage --clear [<harness>](and its authenticated Console action) clears recorded cooldown/backoff state manually.
- Exhaustion cooldowns hold until
- CLI, Console, Fleet grouping, and
console report:thirdshift plan-usage [--fleet | --machine <name>] [--json] [--clear [<harness>]]and Console/plan-usage(Plan usagein navigation with jump keyp,/api/v1/plan-usagein OpenAPI with full CLI/API parity) default to Fleet when Fleet peers/reports exist.- Fleet view groups shared subscriptions across machines by account identity when known (
claudeorgId/email,codexaccountId,museaccountlabel), displaying the freshest observation and the list of machines using that login, while keeping distinct logins and unknown-identity Harnesses (agy,grok) separate per machine. - Rows include every Harness named in
harness.default,harness.routes,security.harnessorsecurity.routes(in route order, marking the active primary route andmax_utilization) plus any subscription Harness with a saved observation or signed-in CLI, omitting unused/uninstalled ones. - Reachable peer reads cache last-seen per-machine snapshots for offline fallback, and post-Command
console reportpushes include the reporting host's local Plan usage snapshot under thereportscope. - Console header shows a warning pill beside host resources when any configured route is exhausted or at/above
max_utilization.
- All-routes-exhausted Pass skips,
Paused Command,Claim,Inbox, andBuilt with:- When every configured default route on a machine is unavailable, scheduled Passes skip before acquiring worktrees or GitHub claims, writing one Activity log line per Pass kind on reason change, and
1 Inboxplusthirdshift statusshow a status banner naming the exhausted routes and earliest known reset. - A live factory Command whose allowed routes are all exhausted ends immediately without sleeping in-process as a Paused Command (
outcome = "paused"), recording the stopping Harness, Plan window, utilization, reset time and Continuation/resume command on its Command record. In a Spec run, queued sibling Tickets stop starting while active Tickets finish or salvage. - Ending an issue's Claim for a Paused Command removes
in-progresswithout addingagent-failed, puttingready-for-agentback if the Claim took it off—unless an untrusted account edited the issue since its last Trustedready-for-agentlabelling (which leavesneeds-triage) or the Run is a Security fix (which swaps foragent-failed). - A Paused Command whose Claim put
ready-for-agentback lands in Done/history rather than Needs you, and underonly_unmergedsaves its Run notification without emailing; any Paused Command that Pickup will not retake automatically (Experiment,Scoring run, PRFix/Review, Security fix, untrusted-editneeds-triage, or a hand-started Run that did not restoreready-for-agent) enters Needs you asPausedand emails its notification when configured. Built withkeeps its single parseable tuple (the Harness/Model/Effort of the primary implementation session, or resolved first route if rerouted earlier), while.models.json,CommandRecordand the Run notification's Session models list record every Step's actual Harness and Model.
- When every configured default route on a machine is unavailable, scheduled Passes skip before acquiring worktrees or GitHub claims, writing one Activity log line per Pass kind on reason change, and
Testing Decisions
- Test external user-visible behavior at existing top-level seams—CLI output/JSON, Console HTTP/HTML/SSE responses, git worktree state, GitHub label/PR effects, Command records, Activity logs and Run notifications—never internal helper details or real sleeps.
- Fake Harness executables and session-stream seam: Extend scripted fake Harness CLIs to emit Claude
rate_limit_eventframes across all utilization levels andClaudeApiErrorspend/window rejections,claude auth status --json,codex app-serveraccount/rateLimits/readand sessiontoken_countrate_limits+credits,muse serveusage/read+account/read,agy--settings '{"useG1Credits":false}'verification +UsageLimiterrors, andgrokUsageLimiterrors. Assert TTL cache reuse, proactivemax_utilizationrouting,allow_creditsgating, reactivebefore_first_turnand mid-Step clean worktree resets (includingImplementswitching to continuation once commits/PR exist and conflict-Repair merge reset), exponential cooldown revalidation, and--clear. Prior art: existing Harness choice, interpretation, fresh-run and spec-run integration suites. - Console HTTP, Fleet, parity and browser seam: Test
/plan-usage,/api/v1/plan-usage, CLI/API parity, Fleet shared-login grouping vs distinct/unknown-identity separation, offline peer cache fallback,console reportpush under thereportscope, header warning pill visibility, and Console Playwright interaction. Prior art: Console Metrics, Fleet Metrics report, Resources, and route-table parity suites. - Pass admission,
Claim,Inboxand notification seam: Using fakeghandgit, assert Pass skips +Activity logdedupe +1 Inbox/thirdshift statusbanner when all routes are exhausted,Paused Command(outcome = "paused") across Ticket runs, Spec runs (halting queued Tickets) and Custom processes,Claimrelease withoutagent-failed(including the untrusted-editneeds-triageguard, Security-fixagent-failedguard, and rejection of agent-forged prose), andDonevsNeeds you+only_unmergednotification suppression whenready-for-agentis restored. Prior art: Pickup run, Out-of-disk Claim release, Status/Inbox, and Run notification suites.
Out of Scope
- Bounded same-Harness retries on temporary HTTP 503/529/overload errors (Recover from temporary Harness service failures within a bounded retry budget #584).
- Routing or model fallback for provider model-at-capacity errors, expired OAuth sessions, or cybersecurity safeguard refusals (add fallback model setting for provider issues, like codex model at capacity #1546, A Harness's cybersecurity refusal on a Security finding fails the Triage run and counts toward the Failure limit #1972)—though
[harness] routesand[security] routesare shaped so add fallback model setting for provider issues, like codex model at capacity #1546 reuses the same configuration table. - Subscription Plan usage monitoring for API-billed Harnesses (
opencodeandkimi). - Scraping undocumented web or OAuth usage endpoints or reading Harnesses' private authentication tokens.
- Cross-Harness session Resume (
--resumestays strictly within one Harness) or adding un-evaluated quota-handoff instructions to Session prompts. - Cross-machine Fleet HTTP calls inside Pass or Step routing decisions.
Further Notes
- Domain glossary (Plan usage, Plan window, Paused Command, Claim, Failure limit, Harness, Needs you), grill record and ADR 0031 landed in Record the #583 Plan usage grill, glossary terms and ADR 0031 #2178.
- Closes add coding subscription monitor to thirdshift console web ui #969 into this Spec's Console
/plan-usageTicket.
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Activity
JacobStephens2 commentedon Oct 9, 2026
Triage Notes
Category recommendation: enhancement
State recommendation: needs-triage
What we've established so far:
is_error: trueand HTTP 429. The root API error identifiedusage_limit_reached, aseven_daylimit resetting at2026-10-10 02:00 UTC, and extra usage rejected with reasonorg_level_disabled_until. Provider prose additionally said the monthly spend limit was hit. The weekly reset is not evidence of a monthly spend reset or guaranteed account recovery.Proposed behavior to decide:
What still needs a maintainer decision:
Diagnostic reporting is tracked in #587 and the recovery notification in #588; those smaller improvements should not wait for a full routing policy.
[-]make way to route requests depending on available subscription usage[/-][+]make way to view available usage in console and to route requests depending on available subscription usage[/+]Merge pull request #2178 from JacobStephens2/available-usage-routing
[-]make way to view available usage in console and to route requests depending on available subscription usage[/-][+]Spec: view subscription Plan usage and route Steps across Harnesses[/+]