feat(seats): accounts without a 5-hour limit

Anthropic is removing the 5-hour limit in stages (from 2026-10-31), and
an account without it reports the weekly window alone. Until now the pool
took such an account for an unprobed seat: always eligible, ranked after
every other, never held to the weekly cutoff, with no cap on its passes.

- The burst window is the 5-hour one, else the weekly one
  (Seats::BurstWindow). An account whose burst window is the weekly one is
  ranked by it and admitted under the 95 % cutoff with the passes in
  flight projected on it, at the measured share per pass (0.04 at xhigh,
  0.125 otherwise; REVIEW_GATES_LENS_COST_7D_PCT), and waits for the weekly
  reset when over it.
- Every OAuth account and the local login takes at most 128 passes at once,
  every driver's counted (REVIEW_GATES_OAUTH_CONCURRENCY): every run
  measured with 149 or more in flight on one account drew usage-limit
  rejections, none of the 119 with 139 or fewer.
- A 'concurrents' rejection pauses the seat for 20 s or its retry-after,
  not until a window's reset (a seat sat two hours at 10 % on one).
- Board entries, the folded foreign record and the board's view carry
  cost7d, so the drivers see each other's weekly load.
- `seats` and the log table say `no limit` in such an account's 5-hour
  column; the board's others-line names its weekly projection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 17:07:17 +01:00
commit c28307601b
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
11 changed files with 209 additions and 26 deletions

View file

@ -107,6 +107,12 @@ Conventions:
No file the drivers write holds these in practice, and a file that fails to parse is treated as a damaged one.
- **HTTP.** `FetchHTTP1` ignores `*_proxy` variables. The OpenRouter requests send `User-Agent: crafter-review`; `app_ready` sends `review-gates`. Reason phrases are the standard ones, not the server's.
- **Seat placement** (SeatPool, Seats, Drivers):
- An account whose 5-hour limit Anthropic has removed reports the weekly window alone. Its burst window (`Seats::BurstWindow`) is the weekly one: it is ranked and admitted by the weekly window with the passes in flight projected on it (0.04 points per pass at xhigh, 0.125 otherwise), under the 95 % cutoff.
- Every OAuth account and the local login takes at most 128 passes at once, every driver's counted.
- A 'concurrents' rejection pauses the seat for 20 s or its retry-after, not until a window's reset.
- Board entries and the folded `foreign` record carry `cost7d`, and so does the dump's seat summary.
- The `seats` report and the log table say `no limit` in such an account's 5-hour column.
- **Board identity and liveness, process lifetime, stop:** see "Inside the Flatpak".
- **Edge cases:**
- Usage errors of `run`, `freeze`, `slots`, `config` and the other commands exit 3, the preflight's code, and so does an INVARIANT failure of `run` and `slots`. The helpers exit 64.
@ -174,6 +180,8 @@ What the binary reads at run time, and how. A variable the driver only sets or s
| `REVIEW_GATES_SEAT_POLICY` | `:Seats` ConfigFromEnv | `spread` | Exactly `pack` (case-sensitive, untrimmed) is pack; anything else spread. | pack: the first eligible OAuth seat by id; spread: the lowest projected one |
| `REVIEW_GATES_SEAT_WAIT_MIN` | `:Seats` ConfigFromEnv | `15` | As MAX_UTIL | Shown by `seats --json` as `waitMaxMin`; nothing else reads it |
| `REVIEW_GATES_SEAT_HANDOVER_PCT` | `:Seats` ConfigFromEnv | `90` | As MAX_UTIL | Shown in the `seats` report and `--json` only |
| `REVIEW_GATES_OAUTH_CONCURRENCY` | `:SeatPool` PoolConfig | `128` | As MAX_UTIL, truncated toward zero. | Passes at once on one OAuth account or the local login, every driver's counted |
| `REVIEW_GATES_LENS_COST_7D_PCT` | `:SeatPool` PoolConfig | `0.04` at xhigh, `0.125` otherwise | As MAX_UTIL. | Percentage points of the weekly window a running pass is projected to add: the placement of an account without a 5-hour limit, and `cost7d` on the board |
| `REVIEW_GATES_SEAT_ORDER` | `:SeatPool` ApiConfig | `api-first` | Exactly `oauth-first` is oauth-first; anything else api-first. | Whether the paid seats (API, OpenRouter) or the OAuth/local ones rank first |
| `REVIEW_GATES_API_BUDGET_USD` | `:SeatPool` ApiConfig (run, seats) | `200.0` | MAX_UTIL's syntax, kept if >= 0 (`inf` means no cap); empty, garbage, negative, `nan` give the default. `--api-budget` overrides it in run. | USD one API key may spend per cycle; at it the key is out |
| `REVIEW_GATES_API_LENS_COST_USD` | `:SeatPool` ApiConfig | `1.0` | As API_BUDGET_USD | USD reserved per running process: a pass fits while spent + (running+1) × this + other drivers' cost ≤ budget |

View file

@ -27,7 +27,7 @@ alias crafter-review='flatpak run net.catcrafts.CrafterReview'
What the machine provides:
- **The Claude Code CLI.** It is proprietary and not bundled. crafter-review looks for it in this order: `CLAUDE_BIN` or `REVIEW_GATES_CLAUDE_BIN`, the PATH, the editor extensions' bundled binary (newest first), then `~/.claude/local` and `~/.local/bin`. It runs inside the sandbox, so every pass is measured and every pass's process tree can be killed.
- **Seats.** The lens processes run on the seats in the repository's `.env` (`CLAUDE_CODE_OAUTH_TOKEN1..6`, `ANTHROPIC_API_KEY1..6`), else this machine's own login. `crafter-review seats` probes and reports them. It never prints a token.
- **Seats.** The lens processes run on the seats in the repository's `.env` (`CLAUDE_CODE_OAUTH_TOKEN1..6`, `ANTHROPIC_API_KEY1..6`), else this machine's own login. `crafter-review seats` probes and reports them. It never prints a token. An account whose 5-hour limit Anthropic has removed shows `no limit` in that column and is placed by its weekly window; every account takes at most 128 passes at once.
- **Chromium, for a lens with the browser.** Run `crafter-review browser install` once. It installs the Chromium build the pinned `@playwright/mcp@0.0.83` drives into `~/.cache/ms-playwright`, where the MCP server looks. It runs Playwright's own installer, which also removes the browser builds no installed Playwright on the machine refers to.
- **A local Supabase stack, for a lens with the database** (`supabase start`, or the repository's own bring-up). The `sql` helper speaks the Postgres protocol itself: no `psql` and no driver to install. Every call is one transaction, rolled back.
- **gpg, if your commits are signed.** `freeze` reads each commit's signature through gnupg's keyboxd socket, which is all the sandbox is given. The agent, which signs, stays out of reach.

View file

@ -649,7 +649,7 @@ namespace Crafter::Review::Run {
} catch (const std::runtime_error& e) {
Die(e.what());
}
const Json pc = SeatPool::PoolConfig(processEnv);
const Json pc = SeatPool::PoolConfig(processEnv, a.effort);
board = std::make_unique<SeatPool::Board>(SeatPool::BoardPath(), std::string(), std::nullopt, log);
board->Join({{"run", std::format("commits {} @ {}", Basename(Dyn::RStrip(a.artifact, "/")), h8)},
{"dump", Basename(dumpPath)},
@ -673,7 +673,7 @@ namespace Crafter::Review::Run {
const double now = Platform::Now();
board->Publish(seats, now, !a.noProbe);
board->Fold(seats, now);
for (const std::string& line : board->OthersLines(seats, now, SeatPool::PoolConfig(processEnv), apiCfg, a.concurrency)) log(line);
for (const std::string& line : board->OthersLines(seats, now, SeatPool::PoolConfig(processEnv, a.effort), apiCfg, a.concurrency)) log(line);
}
if (a.harness == "opencode") {
runner->opencodeHome = OpenCode::PrepareHome(scratch, repo, modelIds);
@ -1467,6 +1467,7 @@ namespace Crafter::Review::Run {
.cacheKey = warm ? std::function<std::optional<std::string>(Json&)>([this](Json& job) { return CacheKey(job); }) : nullptr,
.warmProbe = warm ? std::function<bool(const Json&)>([this](const Json& job) { return WarmProbe(job); }) : nullptr,
.seats = &seats,
.seatConfig = SeatPool::PoolConfig(Platform::Environment(), a.effort),
.apiConfig = apiCfg,
.ledger = ledger.get(),
.admission = admission.get(),

View file

@ -69,7 +69,7 @@ namespace Crafter::Review::SeatPool {
}
Json ZeroForeign() {
return {{"count", 0}, {"cost5h", 0.0}, {"costUsd", 0.0}};
return {{"count", 0}, {"cost5h", 0.0}, {"cost7d", 0.0}, {"costUsd", 0.0}};
}
std::string Kind(const Json& seat) {
@ -510,16 +510,50 @@ namespace Crafter::Review::SeatPool {
}
// ---- placement -------------------------------------------------------------------------
Json PoolConfig(const std::map<std::string, std::string>& env) {
namespace {
// A finite number >= 0 in Dyn::ParseFloat's syntax, else the default.
double EnvNumber(const std::map<std::string, std::string>& env, const std::string& name, double fallback) {
const std::string text = EnvOf(env, name);
const std::optional<double> n = Dyn::ParseFloat(text);
return n && !Dyn::Strip(text).empty() && std::isfinite(*n) && *n >= 0 ? *n : fallback;
}
// A pass's share of the weekly window, as measured: 0.04 at xhigh,
// 0.125 at max and for any other effort.
double Cost7d(const Json& config) {
return Dyn::Has(config, "lensCost7dPct") ? AsDouble(config.at("lensCost7dPct")) : 0.125;
}
// The weekly window with the passes in flight and this one projected
// on it stays under the cutoff: the room of an account whose burst
// window is the weekly one.
bool WeeklyRoom(const Json& s, const Json& config) {
const double ahead = static_cast<double>(Running(s) + 1) * Cost7d(config) + Num(Dyn::Get(Foreign(s), "cost7d"));
const Json& windows = Dyn::At(s, "windows");
for (auto it = windows.begin(); it != windows.end(); ++it) {
if (it.key() == "five_hour" || Seats::IsFableWindow(it.key())) continue;
if (Num(Dyn::Get(it.value(), "utilization")) + ahead >= Seats::LongWindowCutoff) return false;
}
return true;
}
}
Json PoolConfig(const std::map<std::string, std::string>& env, std::string_view effort) {
Json config = Seats::ConfigFromEnv(env);
if (Dyn::Strip(EnvOf(env, "REVIEW_GATES_LENS_COST_PCT")).empty()) config["lensCostPct"] = 0.25;
config["lensCost7dPct"] = EnvNumber(env, "REVIEW_GATES_LENS_COST_7D_PCT", effort == "xhigh" ? 0.04 : 0.125);
config["oauthConcurrency"] = static_cast<std::int64_t>(EnvNumber(env, "REVIEW_GATES_OAUTH_CONCURRENCY", static_cast<double>(OauthConcurrency)));
return config;
}
double OauthProjected(const Json& s, const Json& config) {
// Where the seat's 5-hour window will be with its running processes -
// Where the seat's burst window will be with its running processes -
// this driver's at its projection, the other drivers' at theirs (the
// board). Unknown = at the ceiling, ranked last.
// board): the 5-hour window, or the weekly one on an account without
// a 5-hour limit. Unknown = at the ceiling, ranked last.
if (Seats::BurstWindow(Dyn::At(s, "windows")) == "seven_day") {
return AsDouble(s.at("windows").at("seven_day").at("utilization")) + static_cast<double>(Running(s)) * Cost7d(config) + Num(Dyn::Get(Foreign(s), "cost7d"));
}
const Json& five = Dyn::Get(FiveHour(s), "utilization");
const double base = five.is_null() ? AsDouble(config.at("maxUtil")) : AsDouble(five);
return base + static_cast<double>(Running(s)) * AsDouble(config.at("lensCostPct")) + AsDouble(Foreign(s).at("cost5h"));
@ -552,9 +586,17 @@ namespace Crafter::Review::SeatPool {
// Its pause is over (checked above): back in placement, its windows
// decide.
if (s.at("state") == "limited") s["state"] = "ok";
// No 5-hour reading (no probe, or it failed): usable, ranked at the
// ceiling - after every known seat.
if (Dyn::Get(FiveHour(s), "utilization").is_null()) return true;
// So many passes per account at once, every driver's counted,
// whatever its windows (2026-10-31).
const std::int64_t cap = Dyn::Has(config, "oauthConcurrency") ? Int(AsDouble(config.at("oauthConcurrency"))) : OauthConcurrency;
if (Running(s) + Int(AsDouble(Foreign(s).at("count"))) >= cap) return false;
const std::optional<std::string> burst = Seats::BurstWindow(s.at("windows"));
// No reading (no probe, or it failed): usable, ranked at the ceiling -
// after every known seat.
if (!burst) return true;
// No 5-hour limit on the account: the weekly cutoff alone, with the
// passes projected on it.
if (*burst == "seven_day") return WeeklyRoom(s, config);
return OauthProjected(s, config) + AsDouble(config.at("lensCostPct")) <= AsDouble(config.at("maxUtil")) && LongOk(s);
}
@ -628,11 +670,14 @@ namespace Crafter::Review::SeatPool {
std::vector<double> times;
for (const Json& s : seats) {
if (s.at("state") == "out" || s.at("state") == "invalid") continue;
const Json& five = FiveHour(s);
if (AsDouble(s.at("pauseUntil")) > now) {
times.push_back(AsDouble(s.at("pauseUntil")));
} else if (Kind(s) != "api" && Dyn::Truthy(Dyn::Get(five, "resetsAt")) && AsDouble(five.at("resetsAt")) + 30 > now) {
times.push_back(AsDouble(five.at("resetsAt")) + 30);
} else if (Kind(s) != "api") {
// The reset of the seat's burst window: the 5-hour one, the
// weekly one on an account without it.
const std::optional<std::string> burst = Seats::BurstWindow(s.at("windows"));
const Json& window = burst ? Dyn::Get(s.at("windows"), *burst) : Dyn::EmptyDict();
if (Dyn::Truthy(Dyn::Get(window, "resetsAt")) && AsDouble(window.at("resetsAt")) + 30 > now) times.push_back(AsDouble(window.at("resetsAt")) + 30);
}
if (recheck && AsDouble(Foreign(s).at("count")) > 0) times.push_back(now + *recheck);
}
@ -730,6 +775,16 @@ namespace Crafter::Review::SeatPool {
return true;
}
const std::string rateType = Dyn::StrOr(Dyn::Get(record, "rateType"), "");
if (rateType == "concurrents") {
// Not a window: the account had too many requests in flight
// at once (a burst of passes released onto one seat when its
// prompt prefix became cached). Seconds, not the window's
// reset.
s["pauseUntil"] = std::max(AsDouble(s.at("pauseUntil")), now + RetryAfter(reason, ConcurrentBackoffS));
s["state"] = "limited";
log(std::format("seat {}: too many requests at once ({}) - paused {} s, the process is placed again", Id(s), Dyn::Slice(reason, 0, 40), Int(AsDouble(s.at("pauseUntil")) - now)));
return true;
}
const Json& binding = s.at("binding");
if (rateType.starts_with("seven_day") || (Dyn::StrOr(binding, "").starts_with("seven_day") && Num(Dyn::Get(s, "util")) >= 100)) {
s["state"] = "out";
@ -1112,17 +1167,20 @@ namespace Crafter::Review::SeatPool {
const Json& inflight = Dyn::Or(Dyn::Get(b, "inflight"), Dyn::EmptyDict());
std::int64_t count = 0;
double cost5h = 0.0;
double cost7d = 0.0;
double costUsd = 0.0;
for (auto it = inflight.begin(); it != inflight.end(); ++it) {
const std::string owner = Owner(it.key());
if (owner == me) continue;
++count;
cost5h += Num(Dyn::Get(it.value(), "cost5h"));
cost7d += Num(Dyn::Get(it.value(), "cost7d"));
costUsd += Num(Dyn::Get(it.value(), "costUsd"));
f["drivers"][owner] = Dyn::Has(f["drivers"], owner) ? f["drivers"][owner].get<std::int64_t>() + 1 : 1;
}
f["count"] = count;
f["cost5h"] = cost5h;
f["cost7d"] = cost7d;
f["costUsd"] = costUsd;
if (!IsPaid(s) && !MergeWindows(s["windows"], Dyn::Get(b, "windows"), now).empty()) Reutil(s);
const double pause = Num(Dyn::Get(b, "pauseUntil"));
@ -1140,6 +1198,7 @@ namespace Crafter::Review::SeatPool {
}
}
f["cost5h"] = Dyn::Round(AsDouble(f.at("cost5h")), 4);
f["cost7d"] = Dyn::Round(AsDouble(f.at("cost7d")), 4);
f["costUsd"] = Dyn::Round(AsDouble(f.at("costUsd")), 4);
s["foreign"] = std::move(f);
}
@ -1309,6 +1368,7 @@ namespace Crafter::Review::SeatPool {
{"since", now},
{"deadline", now + 60 * (Dyn::Truthy(timeout) ? AsDouble(timeout) : 20.0) * 1.5 + InflightGraceS},
{"cost5h", paid ? Json(0) : config.at("lensCostPct")},
{"cost7d", paid ? Json(0) : Json(Cost7d(config))},
{"costUsd", paid ? LensCostValue(seat, apiConfig) : Json(0)},
};
if (!Dyn::Get(job, "_admitted").is_null()) entry["mb"] = Dyn::Round(AsDouble(job.at("_admitted")), 1);
@ -1362,6 +1422,7 @@ namespace Crafter::Review::SeatPool {
Json by = Json::object();
std::int64_t count = 0;
double cost5h = 0.0;
double cost7d = 0.0;
double costUsd = 0.0;
Json oldest;
const Json& inflight = Dyn::Or(Dyn::Get(s, "inflight"), Dyn::EmptyDict());
@ -1370,6 +1431,7 @@ namespace Crafter::Review::SeatPool {
by[owner] = Dyn::Has(by, owner) ? by[owner].get<std::int64_t>() + 1 : 1;
++count;
cost5h += Num(Dyn::Get(e.value(), "cost5h"));
cost7d += Num(Dyn::Get(e.value(), "cost7d"));
costUsd += Num(Dyn::Get(e.value(), "costUsd"));
const double since = Num(Dyn::Get(e.value(), "since"));
if (oldest.is_null() || since < oldest.get<double>()) oldest = since;
@ -1386,6 +1448,7 @@ namespace Crafter::Review::SeatPool {
{"kind", Dyn::Get(s, "kind")},
{"inflight", count},
{"cost5h", Dyn::Round(cost5h, 4)},
{"cost7d", Dyn::Round(cost7d, 4)},
{"costUsd", Dyn::Round(costUsd, 4)},
{"by", by},
{"oldest", oldest},
@ -1436,6 +1499,8 @@ namespace Crafter::Review::SeatPool {
const std::int64_t c = Concurrency(s, apiConfig);
lines.push_back(std::format(" seat {}: {} in flight ({}) - {} of {} slots free, ${:.2f} reserved", Id(s), count, Dyn::Join(who, ", "), std::max<std::int64_t>(0, c - count - Running(s)), c,
AsDouble(f.at("costUsd"))));
} else if (Seats::BurstWindow(s.at("windows")) == "seven_day") {
lines.push_back(std::format(" seat {}: {} passes in flight ({}) - projected 7d {:.1f} %, no 5-hour limit", Id(s), count, Dyn::Join(who, ", "), OauthProjected(s, config)));
} else {
lines.push_back(std::format(" seat {}: {} passes in flight ({}) - projected 5h {:.1f} %", Id(s), count, Dyn::Join(who, ", "), OauthProjected(s, config)));
}
@ -1475,10 +1540,7 @@ namespace Crafter::Review::SeatPool {
std::vector<std::vector<std::string>> rows = {{"seat", "kind", "state", "5h", "7d", "fable", "running", "foreign", "done", "spent", "note"}};
for (const Json& s : seats) {
const Json& windows = s.at("windows");
auto w = [&](std::string_view k) {
const Json& u = Dyn::Get(Dyn::Or(Dyn::Get(windows, k), Dyn::EmptyDict()), "utilization");
return u.is_null() ? std::string("—") : std::format("{}%", Seats::Fmt(u));
};
auto w = [&](std::string_view k) { return Seats::WindowCell(windows, k); };
std::string fable = "—";
for (auto it = windows.begin(); it != windows.end(); ++it) {
if (Seats::IsFableWindow(it.key()) && !Dyn::Get(it.value(), "utilization").is_null()) {

View file

@ -827,6 +827,19 @@ namespace Crafter::Review::Seats {
return std::format("{:02d}:{:02d}", d.tm_hour, d.tm_min);
}
std::optional<std::string> BurstWindow(const Json& windows) {
for (const std::string_view window : {std::string_view("five_hour"), std::string_view("seven_day")}) {
if (!Dyn::Get(Dyn::Or(Dyn::Get(windows, window), Dyn::EmptyDict()), "utilization").is_null()) return std::string(window);
}
return std::nullopt;
}
std::string WindowCell(const Json& windows, std::string_view window) {
const Json& u = Dyn::Get(Dyn::Or(Dyn::Get(windows, window), Dyn::EmptyDict()), "utilization");
if (!u.is_null()) return std::format("{}%", Fmt(u));
return window == "five_hour" && BurstWindow(windows) == "seven_day" ? std::string("no limit") : std::string("—");
}
std::string FormatWhen(double epoch, double now, const Zone& zone) {
const auto diffMin = static_cast<std::int64_t>(JsRound((epoch - now) / 60));
std::string relative;
@ -876,10 +889,7 @@ namespace Crafter::Review::Seats {
if (anyInflight) header.emplace_back("in flight");
rows.push_back(header);
for (const Json& s : ordered) {
auto w = [&](std::string_view k) {
if (!Dyn::Has(Dyn::Or(Dyn::Get(s, "windows"), Dyn::EmptyDict()), k)) return std::string("—");
return std::format("{}%", Fmt(Dyn::At(Window(s, k), "utilization")));
};
auto w = [&](std::string_view k) { return WindowCell(Dyn::Or(Dyn::Get(s, "windows"), Dyn::EmptyDict()), k); };
auto when = [&](std::string_view k) {
const Json& at = Dyn::Get(Dyn::Or(Window(s, k), Dyn::EmptyDict()), "resetsAt");
return Dyn::Truthy(at) ? FormatWhen(AsDouble(at), now, zone) : std::string("—");

View file

@ -32,6 +32,17 @@ export namespace Crafter::Review::SeatPool {
// An API seat's pause after a 429.
constexpr double ApiBackoffS = 90;
constexpr double OauthFallbackPauseS = 300;
// An OAuth seat's pause after a 'concurrents' rejection: too many requests
// at once on the account, cleared as the ones in flight answer. Not the
// window's reset (2026-10-31: a seat sat two hours at 10 % on one).
constexpr double ConcurrentBackoffS = 20;
// The passes one OAuth account (or the local login) takes at once, every
// driver's counted. Until 2026-10-31 the 5-hour projection was the only
// limit, and an account without a 5-hour limit has none: on the record,
// every run with 149 or more in flight on one account drew usage-limit
// rejections, none of the 119 with 139 or fewer.
// REVIEW_GATES_OAUTH_CONCURRENCY overrides.
constexpr std::int64_t OauthConcurrency = 128;
// The kinds whose processes cost money the ledger counts.
constexpr std::array<std::string_view, 2> Paid = {"api", "openrouter"};
// The kinds whose out/invalid verdict crosses drivers on the board.
@ -90,9 +101,13 @@ export namespace Crafter::Review::SeatPool {
void ApplyProbe(Json& seat, const Json& result);
std::string Describe(const Json& seat);
// The OAuth placement knobs, with the pool's own per-process default (0.25:
// the 2026-10-15 load test moved a 20x seat's window 0.08 points a pass).
Json PoolConfig(const std::map<std::string, std::string>& env);
// The OAuth placement knobs, with the pool's own per-process projections:
// 0.25 of the 5-hour window (the 2026-10-15 load test moved a 20x seat's
// window 0.08 points a pass) and, for an account without a 5-hour limit,
// the measured share of the weekly window at the run's effort (0.04 at
// xhigh, 0.125 otherwise; REVIEW_GATES_LENS_COST_7D_PCT); and the
// per-account cap.
Json PoolConfig(const std::map<std::string, std::string>& env, std::string_view effort = {});
double OauthProjected(const Json& seat, const Json& config);
// A window whose reset has passed counts as empty until the next reading.
std::vector<std::string> ExpireWindows(Json& seat, double now);

View file

@ -86,6 +86,14 @@ export namespace Crafter::Review::Seats {
std::pair<Json, std::string> BindingWindow(const Json& windows);
// Quantised for ranking.
std::pair<Json, std::string> SeatUtil(const Json& windows);
// The window a burst of passes moves: the 5-hour one, else the weekly one.
// Anthropic removes the 5-hour limit in stages (from 2026-10-31), and an
// account without it reports no 5-hour window. nullopt for a seat with no
// reading.
std::optional<std::string> BurstWindow(const Json& windows);
// A window's utilisation as a report cell: `no limit` in the 5-hour column
// of an account without that limit.
std::string WindowCell(const Json& windows, std::string_view window);
std::string ModelFamily(std::string_view model);
bool IsFableWindow(std::string_view key);
const Json* FableWindowOf(const Json& seat);

View file

@ -248,7 +248,10 @@ per hash: a second on the same hash is refused while the first is live
drivers share a scratch dir. `flatpak run net.catcrafts.CrafterReview seats` shows the
drivers running and what they hold. The seat pool still places
every process by its seat's projected 5-hour headroom
(`REVIEW_GATES_LENS_COST_PCT`, `REVIEW_GATES_SEAT_MAX_UTIL`), and a pass
(`REVIEW_GATES_LENS_COST_PCT`, `REVIEW_GATES_SEAT_MAX_UTIL`) — or, on an
account whose 5-hour limit Anthropic has removed, by its weekly window
under the 95 % cutoff — at most 128 passes per account, every driver's
counted (`REVIEW_GATES_OAUTH_CONCURRENCY`), and a pass
launches only when its environment's instrument is free and it fits in
free memory (How the passes share the stacks): the driver logs per gate
how many passes of each environment memory and the stacks allow. Run
@ -572,3 +575,12 @@ repository.
repository up and ships the skill, `doc` the texts, `browser install` the
Chromium, `stop` ends a run from any sandbox. A driver takes its passes
with it however it ends.
- 2026-10-31 — **accounts without a 5-hour limit** (Jorijn: "antrophic is
gradually removing the 5h limits"). Anthropic removes the 5-hour limit in
stages, and an account without it reports the weekly window alone. Such
an account is ranked and admitted by its weekly window with the passes in
flight projected on it, never taken for an unprobed seat; every OAuth
account takes at most 128 passes at once (every run measured with 149 or
more in flight on one account drew usage-limit rejections); a 'concurrents' rejection backs the seat off for seconds,
not until a window's reset; `seats` shows `no limit` in such an account's
5-hour column.

View file

@ -339,7 +339,8 @@ namespace {
// projection.
const Json base = sm["windows"]["five_hour"]["utilization"];
Check("a driver's passes fold at its own projection (2 % each) into another driver's (0.25 %)",
Dyn::Equal(sm["foreign"], {{"count", 2}, {"cost5h", 4.0}, {"costUsd", 0}, {"drivers", {{D.Me(), 2}}}}) && base == 40 && SeatPool::OauthProjected(sm, cfg) == base.get<double>() + 4.0,
Dyn::Equal(sm["foreign"], {{"count", 2}, {"cost5h", 4.0}, {"cost7d", 0.25}, {"costUsd", 0}, {"drivers", {{D.Me(), 2}}}}) && base == 40
&& SeatPool::OauthProjected(sm, cfg) == base.get<double>() + 4.0,
std::format("{} {}", DumpSpaced(sm["foreign"]), base.dump()));
const std::optional<std::string> unviewed = Platform::ReadFile(path);
Json v = A.View(now);
@ -347,6 +348,18 @@ namespace {
Check("view: the drivers and, per account, the passes in flight by driver - nothing written",
v["drivers"].contains(A.Me()) && v["drivers"].contains(D.Me()) && v["seats"][km]["inflight"] == 2 && Dyn::Equal(v["seats"][km]["by"], {{D.Me(), 2}}) && v["drivers"][D.Me()]["inflight"] == 2
&& Platform::ReadFile(path) == unviewed);
// 2026-10-31: an account without a 5-hour limit, as another driver's
// pass loads it.
Json sdn = mk("7", "oauth", "t7");
Json san = mk("7", "oauth", "t7");
sdn["windows"] = {{"seven_day", {{"utilization", 10}, {"resetsAt", now + 86400}}}};
san["windows"] = sdn.at("windows");
place1(D, sdn, cfg, chooserFor("7", "n0"));
fold1(A, san, now);
std::vector<Json> weeklySeats = {san};
const std::vector<std::string> ol = A.OthersLines(weeklySeats, now, cfg, acfg);
Check("others: an account without a 5-hour limit shows the other driver's pass on its weekly projection",
std::ranges::any_of(ol, [](const std::string& l) { return l.contains("seat 7: 1 passes in flight") && l.contains("projected 7d 10.1 %, no 5-hour limit"); }), Dyn::Join(ol, " | "));
D.Leave();
Check("no secret on the board", !DumpSpaced(Read(path).at("seats")).contains("t1") && !Platform::ReadFile(path).value_or("").contains("k1"));
// Memory across drivers (2026-10-31): what the board's passes were

View file

@ -97,6 +97,51 @@ int main() {
requeued = SeatPool::Settle(api2, credits, now, cfg, acfg, &ledger, log);
Check("credit error: out, recorded in the ledger, requeued", requeued && api2.at("state") == "out" && Dyn::Truthy(ledger.Entry("api2").at("outSince")));
// 2026-10-31: a 'concurrents' rejection (too many requests at once on the
// account) is a short back-off, not the window's reset.
Json sc = With(SeatPool::NewSeat("c", "oauth", "t"), {{"state", "ok"}, {"util", 10}, {"running", 1}, {"pauseUntil", 0}, {"windows", {{"five_hour", {{"utilization", 10}, {"resetsAt", now + 7000}}}}}});
Json concurrents = {{"status", "limit"}, {"reason", "You've hit your usage limit"}, {"rateType", "concurrents"}, {"rateLimit", {{"five_hour", {{"utilization", 10}, {"resetsAt", now + 7000}}}}}};
requeued = SeatPool::Settle(sc, concurrents, now, cfg, acfg, &ledger, log);
Check("a 'concurrents' rejection on an oauth seat: requeued, paused for seconds, not until the 5h reset",
requeued && sc.at("state") == "limited" && sc.at("pauseUntil").get<double>() == now + SeatPool::ConcurrentBackoffS && !SeatPool::Eligible(sc, now, cfg, acfg));
Json backOk = {{"status", "ok"}, {"rateLimit", {{"five_hour", {{"utilization", 11}, {"resetsAt", now + 7000}}}}}};
requeued = SeatPool::Settle(sc, backOk, now + SeatPool::ConcurrentBackoffS + 1, cfg, acfg, &ledger, log);
Check("...and the seat is back after the back-off", !requeued && sc.at("state") == "ok" && SeatPool::Eligible(sc, now + SeatPool::ConcurrentBackoffS + 1, cfg, acfg));
// 2026-10-31: an account whose 5-hour limit is removed reports the weekly
// window alone; until then the pool took it for an unprobed seat - ranked
// after every other, never held to the weekly cutoff. The cutoff is 95 %.
const Json px = SeatPool::PoolConfig({}, "xhigh");
Json nf = With(SeatPool::NewSeat("n", "oauth", "tn"), {{"state", "ok"}, {"running", 0}, {"pauseUntil", 0}, {"windows", {{"seven_day", {{"utilization", 3}, {"resetsAt", now + 6 * 86400}}}}}});
Json f15 = With(SeatPool::NewSeat("f", "oauth", "tf"), {{"state", "ok"}, {"running", 0}, {"pauseUntil", 0},
{"windows", {{"five_hour", {{"utilization", 15}, {"resetsAt", now + 3600}}}, {"seven_day", {{"utilization", 11}, {"resetsAt", now + 86400}}}}}});
std::vector<Json> pair = {f15, nf};
std::vector<std::string> order;
for (const Json* s : SeatPool::Ranked(pair, now, px, acfg)) order.push_back(s->at("id").get<std::string>());
Check("an account without a 5-hour window is placed by its weekly one: ranked by it, ahead of a seat at 5h 15 %, its running passes projected at the weekly figure",
order == std::vector<std::string>{"n", "f"} && std::abs(SeatPool::OauthProjected(With(nf, {{"running", 100}}), px) - 7.0) < 1e-9, Dyn::Join(order, ","));
Json n93 = With(nf, {{"windows", {{"seven_day", {{"utilization", 93}, {"resetsAt", now + 86400}}}}}});
Json n93r40 = With(n93, {{"running", 40}});
Json n93r60 = With(n93, {{"running", 60}});
Check("...under the weekly cutoff alone, no 5-hour ceiling: at 93 % it takes a pass with 40 running, not with 60 (93 + 61 x 0.04 >= 95)",
SeatPool::Eligible(n93r40, now, px, acfg) && !SeatPool::Eligible(n93r60, now, px, acfg));
Json n95 = With(nf, {{"windows", {{"seven_day", {{"utilization", 95}, {"resetsAt", now + 86400}}}}}});
Check("...and over the cutoff it waits for its weekly reset + 30 s", !SeatPool::Eligible(n95, now, px, acfg) && SeatPool::EarliestResume({n95}, now) == now + 86430);
Json n127 = With(nf, {{"running", 127}});
Json n128 = With(nf, {{"running", 128}});
Json nShared = With(nf, {{"running", 100}, {"foreign", {{"count", 28}, {"cost5h", 0.0}, {"cost7d", 0.0}, {"costUsd", 0.0}}}});
Json f127 = With(f15, {{"running", 127}});
Json f128 = With(f15, {{"running", 128}});
Check("128 passes per OAuth account, every driver counted, whatever its windows; REVIEW_GATES_OAUTH_CONCURRENCY overrides",
SeatPool::Eligible(n127, now, px, acfg) && !SeatPool::Eligible(n128, now, px, acfg) && !SeatPool::Eligible(nShared, now, px, acfg) && SeatPool::Eligible(f127, now, px, acfg)
&& !SeatPool::Eligible(f128, now, px, acfg) && px.at("oauthConcurrency") == 128 && SeatPool::PoolConfig({{"REVIEW_GATES_OAUTH_CONCURRENCY", "64"}}, "xhigh").at("oauthConcurrency") == 64);
Json weekly = {{"status", "ok"}, {"rateLimit", {{"seven_day", {{"utilization", 4}, {"resetsAt", now + 6 * 86400}}}}}};
requeued = SeatPool::Settle(nf, weekly, now, px, acfg, &ledger, log);
Check("...a finished pass's reading keeps it a weekly seat, unpaused; the log table says `no limit` in its 5h column",
!requeued && Seats::BurstWindow(nf.at("windows")) == "seven_day" && nf.at("pauseUntil") == 0 && SeatPool::Table({nf}).contains("| no limit |"), SeatPool::Table({nf}));
Check("the weekly share of a pass follows the run's effort (0.04 at xhigh, 0.125 otherwise); the env overrides it",
Dyn::Equal(px.at("lensCost7dPct"), 0.04) && Dyn::Equal(SeatPool::PoolConfig({}).at("lensCost7dPct"), 0.125) && Dyn::Equal(SeatPool::PoolConfig({}, "max").at("lensCost7dPct"), 0.125)
&& Dyn::Equal(SeatPool::PoolConfig({{"REVIEW_GATES_LENS_COST_7D_PCT", "0.5"}}, "xhigh").at("lensCost7dPct"), 0.5));
const fs::path scratch = td / "scratch";
fs::create_directories(scratch);
const std::map<std::string, std::string> baseEnv = {{"PATH", "/bin"}, {"ANTHROPIC_API_KEY", "leak"}, {"CLAUDE_CODE_OAUTH_TOKEN", "leak2"}, {"HOME", td.string()}};

View file

@ -153,6 +153,15 @@ int main() {
r = RenderStatusReport({MergeFableProbe(revoked, revoked)}, cfg, now, utc);
Eq(r.contains("not usable: seat 3 invalid (token revoked)"), true);
Eq(r.contains("fable probe:"), false);
// 2026-10-31: an account whose 5-hour limit is removed reports the weekly
// window alone - `no limit` in its 5h cell, the weekly window its burst
// window, ranked by it.
const Json nf = {{"seat", 3}, {"state", "ok"}, {"windows", {{"seven_day", {{"utilization", 3}, {"resetsAt", nowS + 6 * 86400}}}}}, {"util", 5}, {"binding", "seven_day"}, {"resetsAt", nowS + 6 * 86400}};
r = RenderStatusReport({seat(2, 61, 18), nf}, cfg, now, utc);
Eq(Lines(r).at(3), std::string("| 3 | no limit | — | 3% | Sun 13 Sept, 10:00 (in 6d 0h) | — |"));
Eq(Lines(r).at(6), std::string("eligible now: seat 3 (80% headroom), seat 2 (25% headroom) → next lens: seat 3"));
Eq(BurstWindow(nf.at("windows")) == "seven_day" && BurstWindow(seat(2, 61).at("windows")) == "five_hour" && !BurstWindow(Json::object()), true);
Eq(std::format("{}|{}|{}", WindowCell(nf.at("windows"), "five_hour"), WindowCell(Json::object(), "five_hour"), WindowCell(nf.at("windows"), "seven_day")), std::string("no limit|—|3%"));
// The local login is seat 0, named `local`.
r = RenderStatusReport({seat(0, 30), seat(2, 50)}, cfg, now, utc);
Eq(Lines(r).at(2).starts_with("| local | 30%"), true);