Repository navigation
Spec: view subscription Plan usage and route Steps across Harnesses - #2199
Merged
Merged
Conversation
Running sessions save Plan windows and limits from structured wire frames (Claude rate_limit_event at every status and utilization and API-error rate-limit facts, Codex token_count, and terminal usage-limit errors on claude, codex, muse, agy and grok) in ~/.thirdshift/plan-usage.json. thirdshift plan-usage [--json] [--clear [<harness>]] adds non-billed codex app-server, muse serve and claude auth status reads at most once a minute, shows the User config's routes in order with the active one, and forgets recorded cooldowns. The User config gains harness.routes, harness.max_utilization and security.routes, and headless agy keeps to included usage unless its route allows credits. Closes #2181
…route Terminal usage-limit facts now come only from each Harness's own top-level failed-turn frame (never a tool task's failure or an interrupted turn's final message), Claude API errors keep their window, reset and spend cap without a status, the snapshot skips unchanged writes, an installed API-key route ahead of a subscription route leaves none marked active, Muse's account state can say signed out, and plan-usage gets interim ADR 0026 exclusions until #2182 serves its Console API.
The every-setting fixtures and default-key inventory name harness.routes, harness.max_utilization and security.routes, the agy session argv expects the included-usage guard, and plan-usage shares the resources help line so help stays a compact reference.
Record and inspect local Plan usage across subscription Harnesses and User config routes
Before each session Step, a Command whose Harness comes from the User config's harness.routes starts on the first route machine-local Plan usage says can take it, refreshing stale Codex and Muse snapshots with their non-billed reads. A session that stops on structured Plan-usage evidence starts its Step again as a fresh session on the next untried route, after discarding uncommitted changes (keeping commits and re-merging a pinned conflict merge); an implement session that made commits continues with the existing Continuation prompt. Security reviews and Security runs walk security.routes, else harness.routes, after security.harness. Explicit Harness flags, Step Harnesses, Evals and safeguard refusals stay pinned. Every route's Factory skills are linked, and the Command record names each stage's actual Harness. Refs #2183
- Keep the prompt's launch policy for routed implement sessions: the review-report contract still selects the code review session. - Name the branch's open pull request in the Continuation prompt a rerouted implement session starts again with. - Fall back on a startup failure only from security.harness. - Record each stage's Harness, Model and Effort as separate fields. - Share the Security purpose test, the current Choice, the read freshness rule and the windowed-read list; build the implement Opening in one place.
thirdshift plan-usage and the Console's /plan-usage (with /api/v1/plan-usage) default to the Fleet once a machine is paired or saved, and take --fleet, --machine <name> and --local. Each Fleet machine is asked for its own view alone, saved for when it goes offline, and grouped into one row per subscription login by the identity its Harness reports (Claude organization and email, Codex account id, Muse label); agy and grok stay per machine. Clearing cooldowns becomes an act-scoped Console action, so the two interim parity exclusions go. The push after each Command, and plan-usage --push, send this machine's saved view under the report scope. Every Console header warns while a configured route is in cooldown or at its ceiling, linking to Plan usage. Closes #2182
Route factory session Steps across configured Plan usage routes
Scheduled Passes skip before any GitHub work when every route they may take is held by machine-local Plan usage, logging each reason once and showing a banner on the Inbox and in status. A live factory Command whose routes are all held ends as a Paused Command (exit 75), recording the stopping limit and Continuation, releasing its Claim without agent-failed and putting ready-for-agent back for Pickup unless an untrusted edit or a Security fix says otherwise. A Spec run starts no queued Ticket once one pauses. Pauses Pickup retakes sit in Done and are not emailed under email.unless_merged. Closes #2184
…pers Plan usage takes the Fleet's saved views and pairing once per operation, so the default view, machine choices and the view shown agree; load takes a Reach instead of two Options that had to agree. Outgoing reports are a route and a body, Login.login is identity, the page reuses the CLI's empty-view text, and the tests reuse the Metrics report helpers.
…elpers A Review run records its pause, and a Run whose Base fix pauses pauses with it. Every Paused Command keeps a resume command, a Spec run paused by a Ticket records what holds the routes, and a named Custom process's run clears its last skip reason. Child Runs end as Ended::Paused, the pause wording, Limit construction and allowed routes each live in one place, the Run notification reads Pickup's retake from the Command record, and the Plan usage banner reads nothing in unit tests.
A paired peer's refusal reason reached `thirdshift plan-usage` text output with its control characters intact, so a peer could set the terminal title, clear the screen or rewrite lines. Print it through the same report_line the cronjobs Fleet view uses; JSON keeps the text as sent.
Only a Command no route could take exits 75; an Experiment or Scoring run paused at a plan-limit warning keeps exiting 1. A Spec run's notification still names Tickets whose child Runs paused for Plan usage. Runs stopped at a structured usage limit now end paused, as their tests now expect.
# Conflicts: # src/plan_usage.rs
Show Plan usage across the Fleet in the CLI and Console
…rchitecture page Clippy's result_large_err rejected Failure::Rejected once it carried a Limit inline; the site test rejects a bare "Run" in hand-written prose.
# Conflicts: # src/plan_usage.rs
Skip Passes and end exhausted factory Commands as Paused Commands without agent-failed
…e manual, site pages and code index The manual gains the five subscription Harnesses and why opencode and kimi are left out, passive and non-billed sources, cooldowns and revalidation, [harness] routes / max_utilization / allow_credits and [security] routes with Setup's defaults, per-Step proactive and reactive routing with mid-Step resets, and the Paused Command's Claim release and notification. The home, how-it-works and architecture pages and the domain-to-code index follow.
…refreshes, Paused Claim endings on the site Document that a Security session with no routes still falls back to harness.default, that Pass admission refreshes a stale Codex or Muse snapshot, and the Paused Command's Claim release beside the full-disk one on the home, how-it-works and architecture pages; point the route-selection row at src/asks.rs's inline tests and name constants rather than copying their values.
Document Plan usage viewing and routing across the user manual, site pages and code index
…nour a lone route's ceiling, name the routed implement session in Built with Spec findings: - Custom processes (and PR Reviews) with no Harness flags take harness.routes, through one asks::choice shared with Flags::harness; a machine-reading Triage settles its route before preparing its sandbox. - A single configured route keeps its own max_utilization and allow_credits for Pass skips and pauses, matching the status banner. - Built with, in the pull request and Run notification, names the Harness, Model and Effort the implement session ran on. - Agent-writable Codex session files report Plan windows only, never exhaustion, and a Trial's native records never move machine Plan usage. - A per-minute or per-second quota throttle is not a Plan usage limit. - status --json and the Inbox API carry plan_usage_banner. Standards findings: - Drop the unit test that wrote to the real ~/.thirdshift. - Pin with_open_pr against continuation; share PlanUsagePaused::recorded and an optional continuation in implement::continue_with. - Read a cooldown's utilization only from a window still in force. - Rename retaken_by_pickup to handed_back_for_pickup. - Share the Plan usage snapshot test helpers on Scenario. - Correct the plan-usage help synopsis and the how-it-works Pass skip wording.
…Built with names the implement session's route
JacobStephens2
marked this pull request as ready for review
November 14, 2026 11:44
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Spec #583 adds subscription Plan usage. Each machine watches its five subscription Harnesses, shows usage in the CLI and the Console, routes session Steps across
[harness] routes, and ends a Command as a Paused Command instead of failing its issue. The five Tickets (#2181 to #2185) land together here, with the Spec review's fixes on top.Closes #583
Summary
flowchart LR W["Harness wire frames<br/>(rate_limit_event, token_count,<br/>terminal UsageLimit)"] --> S[("~/.thirdshift/plan-usage.json")] R["non-billed reads<br/>(codex app-server, muse serve,<br/>claude auth status)"] --> S S --> V["thirdshift plan-usage / Console /plan-usage<br/>Fleet grouping, header pill"] S --> P{"Pass admission"} P -- every route held --> K["skip quietly<br/>Activity log + status banner"] P -- a route free --> St["per-Step routing<br/>before the Step, then on a structured stop"] St -- no route left --> PC["Paused Command (exit 75)<br/>Claim released without agent-failed"]How the Tickets fit together:
The Spec review changed these:
Evidence
Execution evidence
For each Spec finding, the reviewer's own test was added and run against the Spec branch before the fix, then again after it:
cargo test --test plan_usage_pause a_custom_process_with_no_harness_flags_routes_its_steps→ FAILED:thirdshift: no allowed route has available Plan usage: claude in cooldown (five_hour) until 2096-10-02T07:06:40ZAfter:
test result: ok. 1 passed(the step runs on codex:process-step-1: on codex · default model · default effort, routed by Plan usage: claude is in cooldown)cargo test --test plan_usage_pause a_machine_reading_triage_starts_on_the_route_plan_usage_allows(with the Triage change removed) → FAILED:triage: not started: no allowed route has available Plan usage: claude in cooldown (five_hour) …After:
test result: ok. 1 passedcargo test --test plan_usage_routing built_with_names_the_route_the_implement_session_ran_on→ FAILED:Built with claude unknown · default model · default effort <!-- thirdshift:built-with -->, although no claude session ranAfter:
test result: ok. 1 passedcargo test --test plan_usage_pause a_single_route_reserve_ceiling_skips_passes_as_the_banner_says→ FAILED:left: Some(1) right: Some(0). The banner said Passes skip, but Pickup ran a Claude session.After:
test result: ok. 1 passedcargo test --bin thirdshift a_forged_native_codex_limit_never_marks→ FAILED:assertion failed: !failure.plan_usage_exhaustedAfter:
test result: okcargo test --test plan_usage_pause status_json_carries_the_plan_usage_banner→ FAILED: the JSON had noplan_usage_bannerAfter:
test result: ok. 1 passedcargo test --bin thirdshift plan_usage::observation::tests→ FAILED:a_per_minute_quota_throttle_is_not_plan_usageAfter:
test result: ok. 5 passedscripts/affected-tests refs/remotes/origin/mainselected the site checks, the Console browser tests and the full Rust suite. This host has no C toolchain, so cargo ran in a Rocky Linux 9 container.npm test --prefix site: PASS.npm test --prefix console: PASS.cargo test --no-fail-fast: every test passed except six, which all depend on the container. Each of them passed when re-run in an environment that matches CI:process::tests(30 passed),--test setup(38 passed) and--test setup_offer(8 passed). Without one, orphaned children are never reaped, so these report "owned check process … survived".--test bug_origins(18 passed). The container'spython3is 3.9.--test trial(32 passed). Itsa_live_plan_limit_cuts_short_a_trial_and_keeps_its_evidencegives a session only 0.5 s to be stopped after a limit, and it overran that once while the suite ran under load.cargo fmt --checkandcargo clippy --all-targets -- -D warnings: clean.npm run docs:check --prefix site: clean.Visual evidence
Fixture Plan usage only (hostname
laptop-fixture): routes["claude", "codex"]at an 85% ceiling, with claude in cooldown after its seven-day window. On the Base branch the Console has no Plan usage page, so it serves an empty 404, and there is no header pill.Console header and Inbox, 1440×900
Console header and Inbox, 390×844
/plan-usage, 1440×900/plan-usage, 390×844Merge Danger
Door: two-way
Routing is opt-in:
harness.routes = []andmax_utilization = 100are the defaults, and they keep single-Harness behaviour. Reverting removes the new keys; a User config that sets them would then fail validation until they are removed. Savedplan-usage.jsonsnapshots and the new Command-record fields (limit,resume_command,handed_back_for_pickup, stageharness/model/effort) are additive.Blast Radius: wide
pausedinstead of failing.ready-for-agentback withoutagent-failed.harness.routeswhen it is set.Unaddressed findings
Standards
cfg!(test)bypasses in Plan usage I/O (8 sites). Declined. The guards keep unit tests away from the developer's real~/.thirdshift/plan-usage.jsonand User config. Injecting a snapshot source and the config would mean threading them throughRouter,OnMachine(src/session.rsrouter),harness/agy.rscredits_allowed,session/recording.rsrecord,command_limit.rsandstatus. That redesigns seams beyond this review's scope. The pure parts already take their inputs (router.rsselect,exhaustion::judge) and carry unit tests, and the I/O paths are covered by the integration suites (tests/plan_usage*.rs). The one harmful consequence, a unit test writing the real~/.thirdshift(S2), is fixed.Refusaland handler. Declined. Lifting them intosrc/console/fleet/snapshot.rswould change the shipped Metrics report path (src/logs/metrics/fleet.rs,src/console/metrics.rs) as well. The two keep different snapshot types and read policies; Metrics'Capturereads once through itsSavedFilesseam. A shared abstraction belongs in its own change with Metrics' tests.router.rsselect,exhaustion.rsjudgeandview.rsView::of. Declined. The three answer different questions: which route to take, whether every route is held, and what to display. They already shareState::availability. The concrete divergence the reviewer found,latest_limitreading a window that had reset, is fixed with the sharedState::used_percent_in.Spec
plan-usage --push [<destination>]is an extra hand-run CLI. Declined. It mirrors the existingmetrics --push [<destination>](docs/guide.md:473) and sends the same payload as the post-Command push the Spec asks for ("post-Commandconsole reportpushes include the reporting host's local Plan usage snapshot"). That lets a NATted machine report without running a Command, and it sharessrc/console/report.rswith the metrics push.Security review outcome
No unaddressed introduced findings.
Review coverage
Both reviewers read every changed file at least at the hunk level.
site/docs/index.htmlis generated fromdocs/guide.mdand was intentionally not read;npm run docs:check --prefix sitepasses. No changed file was left unread.Tickets