feat(pool): --pool-strategy=fill-first (primary/backup seat semantics)#780
Conversation
…s on the first seat headroom spreading stays the default. fill-first lands every unbound conversation on the alphabetically-first eligible seat until its headroom drains to the 2% floor, then spills to the next alias — primary/backup semantics (a z-backup seat keeps fully fresh windows until a-main is actually drained) and cache concentration. Failover follows fill order too, so one 429 doesn't defeat the concentration. Alias order, not insertion order: accounts load from a readdir whose order the OS doesn't guarantee; alias naming (1-main, 2-overflow) is a knob the operator actually holds. Sticky bindings behave identically in both modes — strategy only decides where NEW conversations land. Sourced from --pool-strategy / DARIO_POOL_STRATEGY / config pool.strategy, editable in the TUI Config tab (string enum). Invalid flag values fail startup; invalid env/config values fall through to the default, matching resolveSessionRotationConfig's leniency.
Wire drift: ✅ CLEANRan Report{
"drift": false,
"checkedAt": "2026-07-17T00:17:42.143Z",
"ccVersion": "2.1.211",
"captured": {
"claude-opus-4-8": {
"beta": "claude-code-20250219,interleaved-thinking-2025-05-14,thinking-token-count-2026-05-13,context-management-2025-06-27,prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07,advisor-tool-2026-03-01,effort-2025-11-24",
"cchEmitted": false,
"billing": "x-anthropic-billing-header: cc_version=2.1.211.678; cc_entrypoint=sdk-cli;"
},
"claude-sonnet-5": {
"beta": "claude-code-20250219,interleaved-thinking-2025-05-14,thinking-token-count-2026-05-13,context-management-2025-06-27,prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07,advisor-tool-2026-03-01,effort-2025-11-24",
"cchEmitted": false,
"billing": "x-anthropic-billing-header: cc_version=2.1.211.678; cc_entrypoint=sdk-cli;"
},
"claude-haiku-4-5": {
"beta": "interleaved-thinking-2025-05-14,thinking-token-count-2026-05-13,context-management-2025-06-27,prompt-caching-scope-2026-01-05,claude-code-20250219,advisor-tool-2026-03-01",
"cchEmitted": false,
"billing": "x-anthropic-billing-header: cc_version=2.1.211.678; cc_entrypoint=sdk-cli;"
},
"claude-fable-5": {
"beta": "claude-code-20250219,interleaved-thinking-2025-05-14,thinking-token-count-2026-05-13,context-management-2025-06-27,prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07,advisor-tool-2026-03-01,effort-2025-11-24,fallback-credit-2026-06-01",
"cchEmitted": false,
"billing": "x-anthropic-billing-header: cc_version=2.1.211.678; cc_entrypoint=sdk-cli;"
}
},
"findings": []
} |
Compat test: ✅ PASSEDRan Output |
sprayberry-reviewer
left a comment
There was a problem hiding this comment.
Automated review from the Sprayberry Labs fleet code reviewer. This is a
bot-generated verdict, not a human sign-off.
Verdict: No blocking issues found — clean, well-tested addition.
What I checked
src/pool.ts — pickFillFirst algorithm
Traced through every ordering scenario: accounts arriving out of lexicographic order, the primary seat at the floor, all seats at the floor, ineligible seats mixed in. The two-line loop is correct: the best !== null && a.alias >= best.alias guard ensures best only updates toward a lexicographically-earlier alias, and the headroom check prevents updating with a drained account. Returns null only when every account in accounts is at/below the floor, matching the stated intent.
select() fill-first path (pool.ts:418–430)
When pickFillFirst returns null (all eligible accounts at the floor), control falls through to return pickMaxHeadroom(eligible, family) — identical terminal fallback to the headroom strategy. Verified in the "every seat at/below the floor" test and visually in the code.
selectExcluding() fill-first path (pool.ts:552–565)
Same structure: inner if (this.strategy === 'fill-first') { ...; if (first) return first; } exits cleanly when first is null, and the outer return pickMaxHeadroom(eligible, family) provides the same fallback. The excluded set is filtered before eligible is built, so the fill order correctly advances past the 429'd seat.
CLI precedence (cli.ts:461–463)
poolStrategyFromFlag ?? process.env['DARIO_POOL_STRATEGY'] ?? fileCfg.pool?.strategy — flag beats env beats config, as documented. The flag is validated at parse time (process.exit on invalid); env and config values flow into resolvePoolStrategy in proxy.ts, which silently falls through to 'headroom' on unrecognized strings, matching the codebase convention.
resolvePoolStrategy (pool.ts:152–162)
Iterates [explicit, env.DARIO_POOL_STRATEGY] in order, skips non-strings, lowercases and trims, returns on first valid value. Case and whitespace tolerance confirmed. The function receives explicit which may already carry the env value (merged by cli.ts) — the secondary env lookup is mildly redundant but not harmful; if the explicit value is valid it returns immediately.
sanitize() in config-file.ts
The config-file path only copies pool.strategy when it's exactly 'headroom' or 'fill-first'; any other value is silently ignored, producing undefined downstream. This is consistent with the design note in the PR.
TUI config tab (src/tui/tabs/config.ts)
pool.strategy added to both FIELDS and STRING_ENUMS, so TUI editing constrains input to the two valid values.
Tests (test/pool-strategy.mjs)
20 checks covering: resolver precedence, invalid-value fallback, case/whitespace, default headroom preservation, fill-first concentration and spill, floor recovery, ineligible-seat skipping, all-at-floor max-headroom fallback, per-model 7d bucket interaction, failover order in both modes, sticky-binding precedence in both modes. Comprehensive for a feature of this scope.
Minor (non-blocking)
selectExcluding has no test for the all-at-floor fallback in fill-first mode (the analogous select() case is covered). The code path is correct by inspection and the existing failover test provides partial coverage, but an extra check like check('selectExcluding falls back to max-headroom when all at floor', ...) would close the gap:
header('selectExcluding: fill-first all-at-floor falls back to max-headroom');
{
const pool = new AccountPool('fill-first');
addAccount(pool, 'a-main', { util5h: 0.99 });
addAccount(pool, 'b-other', { util5h: 0.98 });
// Exclude a-main — both remaining are at the floor
const next = pool.selectExcluding(new Set(['a-main']));
// Should return b-other (max-headroom among the drained eligible set)
check('selectExcluding fill-first all-at-floor fallback', next?.alias === 'b-other');
}
What's good
The pickFillFirst loop is elegantly minimal — two conditions, correct for every ordering. The documentation trail (docstring, commands.md, multi-account-pool.md, help text) is thorough. The "invalid env/config values fall through to default" design is explicitly justified and consistent with resolveSessionRotationConfig. Test structure mirrors the feature description section-by-section.
What
--pool-strategy=fill-first(envDARIO_POOL_STRATEGY, configpool.strategy, TUI Config tab) — an opt-in alternative to headroom spreading for where unbound (new) conversations land in a multi-account pool. Default behavior is untouched.Fill-first concentrates new conversations on the alphabetically-first eligible seat until its headroom drains to the 2% floor (
POOL_HEADROOM_FLOOR), then spills to the next alias in line.selectExcludingfollows the same order, so a 429 failover tries the next alias rather than the max-headroom seat — one failover doesn't defeat the concentration.Why
Two setups headroom spreading can't serve:
z-backupaccount keeps fully fresh 5h/7d windows untila-mainis actually drained. Spreading nibbles at both from the first request.Decisions worth reviewing
readdirwhose order the OS doesn't guarantee. Alias naming (1-main,2-overflow) is a knob the operator actually holds. Documented inmulti-account-pool.md.selectStickyre-validates and returns an existing binding before any strategy code runs; strategy only picks where a NEW key binds. Tested for both modes.select()never returns null just because the strategy ran dry.--pool-strategyflag fails startup with a clear error; invalid env/config values fall through to the default, matchingresolveSessionRotationConfig's leniency.Tests
test/pool-strategy.mjs— 20 checks: resolver precedence, default-behavior preservation, concentration, floor spill + recovery, ineligible-seat skipping, per-model 7d bucket interaction, failover order in both modes, sticky-binding precedence in both modes. Full suite green locally.