import type { AccountRef } from "../../types.js"; import type { AgentWorkflowRole } from "../template-kit.js"; /** * Least-loaded auth-profile pool selection + a `--max-per-profile` admission * guard. Drain dispatch used to pick a pool member by a pure FNV-1a hash of the * work-item seed (worker=pool[i], verifier=i+1, planner=i+2), with no awareness * of how many runs each subscription account was already carrying. At high * concurrency that stacked several concurrent workers on one ChatGPT account and * tripped the provider-side 429/stream-drop wall. This module spreads work to * the least-loaded account instead, and defers a route when every pool member is * already saturated. * * The hash order is preserved as the deterministic tie-break: with equal load * counts (e.g. a cold store) the selection is byte-for-byte identical to the old * behaviour, so it is fully reproducible and does not perturb rendered-template * snapshots. */ /** FNV-1a index into a pool of `size`. Shared by the deterministic default * (templates) and the least-loaded tie-break so the two never drift. */ export declare function stableIndex(seed: string, size: number): number; /** Historical per-role offset from the worker index (worker+0, verifier+1, * planner+2, everything else +3). Kept identical to the original rolePoolValue * so equal-load selection reproduces the legacy assignment. */ export declare function poolRoleOffset(role: AgentWorkflowRole): number; /** * Pick the least-loaded pool member. Selection key, minimised lexicographically: * 1. not in `exclude` before excluded (so verifier/planner land on a DIFFERENT * profile than the worker whenever the pool is large enough), * 2. lower running-step load, * 3. deterministic order starting at `anchor` (first encountered wins ties). * When every member is excluded (pool smaller than the number of roles) the * exclusion is ignored and pure least-loaded/tie-break applies. */ export declare function selectLeastLoadedProfile(pool: string[], loadCounts: Record, anchor: number, exclude: ReadonlySet): string; /** Select the least-loaded verifier account without ever reusing the worker account. */ export declare function selectVerifierAccount(pool: readonly AccountRef[], loadCounts: Readonly>, workerAccount: AccountRef | undefined): AccountRef | undefined; export interface PoolAuthProfileAssignmentInput { pool: string[]; seed: string; loadCounts: Record; /** Defer the whole route when EVERY pool member already has >= K running * steps. Undefined disables the guard (spread only, never defer). */ maxPerProfile?: number; /** Roles present in the workflow that draw from the pool. */ roles: AgentWorkflowRole[]; } export interface PoolAuthProfileAssignment { /** Chosen auth profile per role. Empty when deferred. */ profiles: Partial>; /** True when the max-per-profile guard fired: every pool member is saturated. */ deferred: boolean; reason?: string; /** Minimum running-step load across the pool at decision time (attribution). */ minLoad: number; } /** * Assign one pool member per role: the worker takes the globally least-loaded * account; verifier/planner/triage each take the least-loaded account not * already claimed by an earlier role (so reviews run on a different subscription * than the work they review). Returns `deferred` when the max-per-profile guard * fires so the caller can hold the route until an account frees up. */ export declare function assignPoolAuthProfiles(input: PoolAuthProfileAssignmentInput): PoolAuthProfileAssignment;