Skip to content

Spec: view subscription Plan usage and route Steps across Harnesses - #2199

Merged
JacobStephens2 merged 29 commits into
mainfrom
issue-583
Nov 14, 2026
Merged

JacobStephens2 merged 29 commits into
mainfrom
issue-583

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Nov 14, 2026 •

Copy link
Copy Markdown
Owner

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"]
Loading

How the Tickets fit together:

src/plan_usage/            # #2181 observation, store, reads, view; #2183 Fleet
├── observation.rs         #   structured frames only → Observation
├── store.rs / read.rs     #   machine snapshot, cooldown backoff 1h→2h→4h→6h, TTL reads
├── route.rs / router.rs   # #2182 [harness]/[security] routes, max_utilization, allow_credits
├── exhaustion.rs          # #2184 "every route held" for Pass skips, pauses, banner
└── fleet.rs / view.rs     # #2183 Fleet grouping by account, offline cache, report push
src/session.rs             # #2182 per-Step routing, mid-Step clean reset, continuation
src/claim.rs, run_ending.rs, spec_run/   # #2184 Paused Command, Claim release, queued Tickets stop
src/console/plan_usage.rs  # #2183 /plan-usage, /api/v1/plan-usage, header pill
docs/guide.md, site/       # #2185 manual, how-it-works, architecture, code index

The Spec review changed these:

 factory Command with no Harness flags
-  Custom process / PR Review: harness.default only
+  Custom process / PR Review: first of harness.routes, routed per Step   (asks::choice)
+  Triage --read-machine: route settled before the sandbox starts         (plan_usage::settled_route)
 Pass skip / pause with routes = ["claude"]
-  lone route pinned: held only at 100% or a cooldown
+  lone route keeps its max_utilization and allow_credits, as the banner says
 Built with (PR and Run notification)
-  Command's first route
+  the route the implement session ran on                                  (logs::implemented_on)
 evidence of exhaustion
-  ~/.codex/sessions token_count with rate_limit_reached_type → cooldown
+  native session files report windows only; Trial native records never recorded
-  any "quota" 429 → usage limit
+  "requests per minute/second" throttles are not Plan usage
 status --json / Inbox API
+  plan_usage_banner

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:

  • Before: 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:40Z
    After: 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)
  • Before: 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 passed
  • Before: cargo 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 ran
    After: test result: ok. 1 passed
  • Before: cargo 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 passed
  • Before: cargo test --bin thirdshift a_forged_native_codex_limit_never_marks → FAILED: assertion failed: !failure.plan_usage_exhausted
    After: test result: ok
  • Before: cargo test --test plan_usage_pause status_json_carries_the_plan_usage_banner → FAILED: the JSON had no plan_usage_banner
    After: test result: ok. 1 passed
  • Before: cargo test --bin thirdshift plan_usage::observation::tests → FAILED: a_per_minute_quota_throttle_is_not_plan_usage
    After: test result: ok. 5 passed

scripts/affected-tests refs/remotes/origin/main selected 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.
  • The full Rust suite, 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:
    • Run with an init process: 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".
    • Run with Python 3.12: --test bug_origins (18 passed). The container's python3 is 3.9.
    • Re-run alone, twice: --test trial (32 passed). Its a_live_plan_limit_cuts_short_a_trial_and_keeps_its_evidence gives 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 --check and cargo 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

Before
After

Console header and Inbox, 390×844

Before
After

/plan-usage, 1440×900

Before
After

/plan-usage, 390×844

Before
After

Merge Danger

Door: two-way

Routing is opt-in: harness.routes = [] and max_utilization = 100 are 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. Saved plan-usage.json snapshots and the new Command-record fields (limit, resume_command, handed_back_for_pickup, stage harness/model/effort) are additive.

Blast Radius: wide

  • Every factory Command now reads machine Plan usage before each Step.
  • Every Pass reads it after the GitHub quota check.
  • A Command whose routes are all held exits 75 as paused instead of failing.
  • The Claim release on a pause puts ready-for-agent back without agent-failed.
  • Custom processes and PR Reviews with no Harness flags now take harness.routes when it is set.

Unaddressed findings

Standards

  • S1: 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.json and User config. Injecting a snapshot source and the config would mean threading them through Router, OnMachine (src/session.rs router), harness/agy.rs credits_allowed, session/recording.rs record, command_limit.rs and status. That redesigns seams beyond this review's scope. The pure parts already take their inputs (router.rs select, 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.
  • S3: Fleet Plan usage repeats Fleet metrics' remote fallback, report receive guard, Refusal and handler. Declined. Lifting them into src/console/fleet/snapshot.rs would 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' Capture reads once through its SavedFiles seam. A shared abstraction belongs in its own change with Metrics' tests.
  • S8 (part): the "unwatched Harness is usable when installed" rule appears in router.rs select, exhaustion.rs judge and view.rs View::of. Declined. The three answer different questions: which route to take, whether every route is held, and what to display. They already share State::availability. The concrete divergence the reviewer found, latest_limit reading a window that had reset, is fixed with the shared State::used_percent_in.

Spec

  • P7: plan-usage --push [<destination>] is an extra hand-run CLI. Declined. It mirrors the existing metrics --push [<destination>] (docs/guide.md:473) and sends the same payload as the post-Command push the Spec asks for ("post-Command console report pushes include the reporting host's local Plan usage snapshot"). That lets a NATted machine report without running a Command, and it shares src/console/report.rs with 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.html is generated from docs/guide.md and was intentionally not read; npm run docs:check --prefix site passes. No changed file was left unread.

Tickets

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.
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.
… test

Ticket #9's 429 records muse's cooldown, so a sibling Ticket whose route
check came after it was not started and ended paused, making the Spec run
exit 75 instead of failing with #11's stream error. Hold #9's session
until #11's has started.
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
JacobStephens2 marked this pull request as ready for review November 14, 2026 11:44
@JacobStephens2
JacobStephens2 merged commit f00bb10 into main Nov 14, 2026
24 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-583 branch November 14, 2026 11:54
@JacobStephens2 JacobStephens2 mentioned this pull request Nov 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spec: view subscription Plan usage and route Steps across Harnesses

1 participant