Repository navigation
feat(vibe): Codify Codex budget rules - #312
Merged
Merged
Conversation
- Default Codex workers to high reasoning effort; xhigh only when raised explicitly, persisted session effort never rewritten - Bind worker models by role from one policy per harness: explorers on gpt-5.6-terra in Codex and sonnet in Claude Code, writers, reviewers and verifiers on the existing gpt-6-sol - Run only the tests code_tests_for selects per changed file plus Biome, source gates and typecheck; drop the verify full-suite mode and the handoff recipe's test suites, with their now unused budgeting - Serialize containerized e2e through a machine-wide lock and run only the specs the change needs - Check Codex rate limits before spawning workers and on stalls, and stop Claude turns on a reached limit or paid overage, unless the person allowed credits - Bump vibe to 1.0.60 Testing: uv run ruff check ., uv run pyright, uv run pytest plugins/vibe (340 passed), vibe node:test suites (355 passed), vibe-workers vitest (735 passed) and typecheck, plugin manifest parity (6 passed) Risks: Handoff no longer runs broad local suites; CI and the repo's pre-push hook remain the broad gate
This was referenced Oct 20, 2026
… monitor codex-fleet-monitor's ratelimit.mjs now owns readRateLimits and classifyUsage (ok, LIMIT, LIMIT-SPENDING-CREDITS, LIMIT-STOPPED, unknown). liveness.mjs gains a stdio transport next to the daemon proxy, both on one JSON-RPC client base. vibe's codex-usage.mjs imports the reader, keeps its private stdio App Server, and maps the state to its gate: it now continues while ordinary usage is still allowed, and stops otherwise unless credits were allowed. Exit codes and statuses are unchanged; the output gains a state field.
LIMIT-STOPPED means neither ordinary usage nor credits remain, so --allow-credits cannot make a turn run. The gate now returns stop there regardless of --allow-credits, and the skill only offers paid credits when the state is LIMIT-SPENDING-CREDITS.
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.
Summary
Fourteen vibe sessions on Codex exhausted two accounts' weekly limits. They ran xhigh writers, explorers on the sol default, broad local suites at every handoff, and containerized e2e stacks that collided on one machine. This codifies five budget rules in the vibe plugin.
1. Reasoning effort defaults to
high.cl-sweep/scripts/model-policy.mjsnow ownsDEFAULT_REASONING_EFFORT = 'high', andDEFAULT_TICKET_REASONING_EFFORTreads it, so new cl-sweep ticket-owner sessions persisthigh.--reasoning-effort xhighstill works, and a persisted session keeps its stored effort on every turn (new test). The support-lane text in cl-sweep and cl-execute now sayshigh, withxhighonly when raised explicitly.2. Model by role, one source of truth per harness.
model-policy.mjscarries a role table (writer, reviewer, verifier ongpt-6-sol, the existing default; explorer ongpt-5.6-terra; mechanical ongpt-6-luna).native-app-server-orchestrator.mjsnow reads its model constants from the policy instead of repeating them. The vibe, handoff and vibe-seed-refresh skills take each native spawn'smodelandreasoning_effortfrom the newvibe/scripts/worker-model.mjs codex <agent>, which maps every vibe agent to a role (tested againstagents/*.md). Explorer and research support lanes that ran on sol move to terra; lanes already on luna stay there. Persisted sessions keep their stored binding, and the "never create a new gpt-5.6-* session" rule is now scoped to ticket-owner sessions.vibe-backend-worker(read-only advice) moves from opus to sonnet. The other explorers were already on sonnet, and writers, reviewers and verifiers keep their models. No vibe agent is a pure lookup, so none moves to haiku.3. Targeted validation. The verify worker runs Biome on changed files, source gates,
pnpm typecheck:affected, and only the testscode_tests_forreturns for each changed file. It never runspnpm test,pnpm verify,test:affectedor a whole lane, and its full-suite mode is gone. The handoff publication recipe (tools/vibe-workers/src/main-sync-checks.ts) dropstest:affected, lane execution,turbo test,pnpm verify,pnpm test,test:lint,test:skills,desktop test:rendererandstorybook test. It keeps Biome, source gates,typecheck:affected,turbo typecheck ...[since](changed packages and dependents),storybook validate:catalogandvibe storybook-diff. Lane discovery still runs, but only to record required browser and Desktop e2e lanes for CI evidence. Storybook guidance runs only the changed story files. The repo's own pre-push hook still runspnpm verify --scope=push, and the broad suites run in CI and after merge. Rule text was amended where it lives (verify worker, handoff, guardrails, quality loop, graph table, environment, primitive and decomposer agents); cl-execute and cl-sweep validation text is unchanged.4. Shared e2e stack. The plugin cannot own symphony-alpha's compose project name or ports, so it serializes instead.
vibe/scripts/e2e-lock.mjs run <command>holds one machine-wide lock, waits for the stack to be free (exit 75 with the holder when it stays busy), and takes over a lock left by a dead process. The verify worker and handoff run only the specs the change touches, through the lock.5. Usage awareness.
vibe/scripts/codex-usage.mjs checkcalls App Serveraccount/rateLimits/read(verified against codex-cli 0.163.0). It exits 3 withstatus: stopwhen a limit is reached and ordinary usage is no longer allowed, or continues with--allow-creditswhile credits remain (LIMIT-STOPPED, with no credits left, stops even then). A reached limit that still allows ordinary usage continues (status: ok,state: LIMIT). The vibe skill runs it before spawning workers or forwarding a request, and when a turn stalls.codex-fleet-monitor/scripts/ratelimit.mjs(merged in feat(vibe): Add codex-fleet-monitor skill #310).readRateLimits({ transport })andclassifyUsagereturnok,LIMIT,LIMIT-SPENDING-CREDITS,LIMIT-STOPPEDorunknown.codex-usage.mjsimports them and only maps the state to its gate; its exit codes and statuses are unchanged, and the output gainsstate.liveness.mjsnow has one JSON-RPC client base with two framings. The monitor uses the daemon proxy. The gate uses a private stdiocodex app-server, so a check inside a vibe session never starts the managed daemon. The classification table is tested once inratelimit.test.mjs;codex-usage.test.mjskeeps the gate tests and adds the ordinary-usage-allowed case.rate_limit_eventinstead. It returns the last one asrateLimitand stops a turn that hitsrejectedor starts paid overage withUSAGE_LIMIT_REACHED, unless the launch input setsallowCredits. So on Claude, the check runs during each turn rather than before it.Removed as unreachable after rule 3:
main-sync-budget-ref-read.ts, the affected-test wrapper pin and its fixture, thetest:lintbudget, the test-selection report admission, the captured-main mapping oftest:affected, and the budget variant of the native-failure helpers.These tests were deleted because their only contract was budgeting or admitting test commands inside the recipe:
main-sync-budget-ref-read.test.tsmain-sync-budgetcases (aggregate test budgets, the fulltest:lintbudget, the captured-main test pin)testcases of the report-admission selected-task testreplaycase of the input-boundary retained-report testThese were rewritten instead:
main-sync.test.tsnow pin the targeted matrix, with counterfactual "no test command runs" assertionsNot changed, possible follow-ups: cl-execute's "run the comprehensive validation once on the final tree" step still allows broad local suites. The prototype publication recipe still runs
pnpm --filter prototypes test.Vibe goes to 1.0.60 in both manifests, because #310 claims 1.0.59.
Validation
uv run ruff check .anduv run pyright: cleanuv run pytest plugins/vibe plugins/code-review: 1758 passed, 3 skippedplugins/**/*.test.{mjs,cjs,js}(the CI node step): 454 passeduv run pytest plugins/vibe340 passed, vibenode:test355 passedtools/vibe-workers: typecheck clean, vitest 735 passed, bundles rebuilttools/plugin-manifests: 6 passedcodex-usage.mjs checkagainst a live account returnedstop(exit 3) on an exhausted weekly window, andcredits_allowedwith--allow-credits. After the unification it returnedokwithstate: okon an open window, and the monitor printedcodex-usage local 0% okand, over ssh,codex-usage remote 50% okRisk
Handoff no longer runs broad local suites, so a regression outside the
code_tests_forset first shows up in PR CI. The Claude usage stop can end a writer turn mid-way. The request stays queued for the same writer, as it does after a timeout.