Skip to content

feat(pool): deterministic weighted tier model pools - #6

Draft
yury-procoders wants to merge 17 commits into
procoders:mainfrom
yury-procoders:feat/tier-model-pool
Draft

feat(pool): deterministic weighted tier model pools#6
yury-procoders wants to merge 17 commits into
procoders:mainfrom
yury-procoders:feat/tier-model-pool

Conversation

@yury-procoders

Copy link
Copy Markdown
Contributor

Summary

  • add per-stance, per-tier weighted backend pools with deterministic manifest-order assignment
  • freeze concrete pool members and backend/model assignments into validated run state for dispatch and resume
  • keep retry, circuit-breaker, fallback, status, and every backend-keyed consumer on concrete assignments
  • add fail-closed manifest/config/state guards, including all-member never-Haiku checks and bounded ring expansion

Merge prerequisite

Do not merge this PR before #5. The code paths are fixture-testable independently, but the shipped Codex + z.ai pool policy depends on the z.ai backend. PR #5 is currently open and green; this PR does not claim it has merged.

Verification

  • 35 Python script selftest suites checked, 0 failed
  • targeted changed suites: config 38, resolver 105, pool state 60, manifest validator 133, failure policy 35, classifier 43
  • 12 manifests checked, 0 failed
  • 4 injected adversarial errors detected: huge weight, negative retry counters, fallback Haiku, invalid auth/top-up breaker clearance
  • frontmatter, JSON, version lockstep, shellcheck, anti-ruflo, and git diff --check passed
  • dead-link scan checked 200 Markdown files, 0 dead links
  • final independent SPEC, QUALITY, and INTEGRATION reviews: APPROVED; all 12 acceptance criteria pass

Python 3.9 grammar parsing passed for changed scripts. A Python 3.9 runtime was not installed locally; GitHub CI is the authoritative Python 3.9 execution gate.

What this does not show

Weighted rotation distributes job counts, not tokens, credits, messages, wall-clock time, quota burn, savings, or provider capacity. No balance percentage or improvement metric is claimed.

…(PR 2 of 3)

Lets one tier name several (backend, model) pairs and hands successive jobs to
them in turn, so a run burns three providers' quotas evenly instead of draining
one. Purely additive: a config with no pools key behaves exactly as today.

Round-robin decides at FIRST dispatch and the choice is frozen into state.json,
so /v:resume re-dispatches to the recorded backend rather than re-deriving it —
otherwise an interrupted job could come back on a different backend with
different isolation and a different worktree. The one exception is a quota
failure, where moving the job is the point.

Independent of the zai backend; zai just becomes an eligible pool member.
The pool spec is cut from main and linked the zai design doc, which lives only
on feat/zai-backend. The gate is line-based and does not respect inline code
spans, so even quoting the path in backticks trips it — the branch dependency is
now stated in prose instead.

Adds the archaeology and library audits.
Four blocking defects in the first draft, two of them mine and inverted:

  * the justification for skipping quota-aware balancing named the wrong
    backend. z.ai is the MOST measurable of the three; the genuinely blind one
    is claude, which this repo already lists in UNMEASURED_BACKENDS. The real
    reason is architectural — Compound V never speaks HTTP to a provider, it
    reads a CLI's stdout, so no provider header ever reaches the dispatcher.

  * the draft would have introduced the policy regression its own Non-goals
    promised to avoid: the never-Haiku execution-layer gate fires on a job's
    explicit model, and a pool job carries none, so the gate became unreachable.
    It now runs on the RESOLVED model.

claude is excluded from pools by default: Anthropic shares usage limits between
Claude and Claude Code, so a claude pool member competes with the operator's own
session. Integer weights (default 1) ship now — they are the only way to express
a reduced share, and weighted rotation is what comparable routers actually do.

Assignment is computed from MANIFEST order, not dispatch order, and the member
list is frozen at run start; an unavailable member is skipped with the counter
still advancing, so a shrinking pool cannot shift later assignments.

Corrected: rate_limited retries the same backend and never reroutes; only
out_of_credits reroutes, and today always to claude — which under a pool would
dump an exhausted run onto the operator's subscription. Availability has no
mechanism in this repo at all (removed in v2.6.2), so the spec now defines it
narrowly instead of assuming it. PR 1 is a declared merge prerequisite.

Adds the domain audit and its knowledge-base file.
Six rounds between the archaeologist and the doc-validator; real errors found in
both directions. Two practices came out of it and are now in the knowledge base:
report a check's denominator and a negative control that can fail (a broken
harness and a clean repo both print zero), and re-run a gate after editing any
doc that describes it — explaining a construct requires reproducing it, which is
how a dead link landed inside the paragraph warning against dead links.
Fixes every CONFIRMED finding from docs/superpowers/reviews/2026-08-01-tier-model-pool-review.md:

- Two Blocking, provably-vacuous selftest guards (recorded-assignment
  idempotence in pool-state.py, the pool retry-budget halt in
  failure-policy.py) now have fixtures that reproduce the exact planted
  mutation that exposed them.
- The zai/PR-5 merge-order landmine: freeze_pool_members now reports a clear,
  member/tier-scoped error instead of a bare resolver ValueError, and a
  configured pool collapsing to <=1 available backend surfaces a warning
  instead of silently defeating the feature's whole purpose.
- A case-variant `backend: Pool` no longer bypasses every pool gate;
  pool-state.py now normalizes the same way the manifest validator does.
- context_length tier escalation and breaker-clearing on a pool job no
  longer produce a state the validator permanently rejects.
- liveness.py now sweeps `dispatched` jobs, not only `running` ones.
- backend_max_parallel is actually shape-validated now (it never was); the
  pool-sensitive-job filter matches type+id+title like the reviewer filter
  already does.
- One CI-tracked manifest now exercises `backend: pool` end to end via a new
  examples/compound-v.example.json, closing the "entirely untested in CI"
  gap for the whole pool validation path.
- Assorted Minor doc/test-coverage gaps: weight-bound fixtures, a typo'd
  pool-member key now warns, contract-block labels now name all three
  copies, state-machine.md no longer documents `deep` as a legal pool_tier,
  the dashboard shows the resolved backend instead of the literal "pool".

Every selftest is green with a higher assertion count than before; all 12
tracked manifests validate; jq/lint-frontmatter/shellcheck 0.11.0 clean.
Adds the review that drove the previous commit (CONFIRMED/PLAUSIBLE/REJECTED
per finding, merge verdict, and the applied-fixes table) plus the original
handoff brief it was run against.
…s/ dir

- compound-v-pool-state.py: validate_state() compared job.get("backend")
  != "pool" directly instead of via _normalized_backend(), the third spot
  the prior review fix (199e794) missed alongside manifest_pool_ordinals
  and freeze_assignments. A job with backend "Pool" (mixed case) now hits
  the same resume-safety gate as the lowercase form.
- SKILL.md: list docs/superpowers/reviews/ in Output Directory
  Conventions — introduced by this branch's own adversarial self-review
  but never added to the canonical directory list.
yury-procoders added a commit to yury-procoders/superpowers-v that referenced this pull request Aug 4, 2026
This branch (PR procoders#7) forked from tier-model-pool (PR procoders#6) two commits
before PR procoders#6's own adversarial-review fixes landed, so PR procoders#7 was
missing them. Reconciles both branches:

- decide()'s earliest_reset_seconds now gates to the terminal halt
  action only (was leaking onto ordinary retry/reroute results),
  matching PR procoders#6's review fix ported onto the new pool-agnostic
  decide() signature (pool_routed bool instead of pool_members list).
- context_length tier-escalation now sets clear_assignment for
  pool-routed jobs, so a stale pool_index isn't left behind when a
  job leaves the ring — same fix, ported the same way.
- validate_state()'s "pool" backend comparison uses the shared
  _normalized_backend() helper (was comparing the raw string
  directly), the third spot the original review fix missed.
- skills/compound-v/failure-policy.md: PR procoders#7 rewrote this doc's
  cooldown/network-pause model; folded back PR procoders#6's escalation/
  non-pool-fallback facts and the anti-patterns section (including
  the pool retry-budget footgun) that the rewrite had dropped, since
  those aren't duplicated anywhere else.
- compound-v-dashboard.py: merged both branches' fixture/assertion
  additions (PR procoders#7's cooldown-state rendering checks, PR procoders#6's pool
  backend-cell check) into one fixture; had to add a matching
  pool_members ring entry for the added pool job, which PR procoders#7's
  now-stricter validate_state() call requires.
- compound-v-pool-state.py: updated a PR procoders#6 selftest ("an available
  member that fails concrete resolution") that used "zai" as a
  stand-in for an unresolvable backend — PR procoders#7 has since given zai a
  real resolver entry, so the scenario the test needs no longer
  reproduces with that name. Swapped in a synthetic backend name
  that stays genuinely unresolvable regardless of future backends.

All affected selftests pass: compound-v-pool-state.py (128/0),
compound-v-failure-policy.py (59/0), compound-v-dashboard.py
(77/0), compound-v-validate-manifest.py (145/0),
compound-v-classify-failure.py (114/0), compound-v-resolve-model.py
(PASSED).
…eption

An independent Codex spec-compliance review flagged that the code (from
this branch's own adversarial-review fix) clears a pool job's frozen
assignment on context_length tier-escalation, while §3/§5 only documented
the quota-class exception. The code is correct — pools don't cover deep,
so an escalating job is leaving the ring by construction, and retaining a
stale pool_index would fail validation — the spec just never caught up.
Document it as the second exception instead of reverting tested code.

The example json's codex+cursor pairing (flagged as diverging from the
spec's codex+zai illustration) was left unchanged: the review doc already
records that choice as deliberate, to keep the CI-covered example free of
a secret-dependent backend.
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.

1 participant