pi-reservoir
v0.1.0
Published
Quota-aware proactive model routing for Pi, Senpi, and omo-native.
Readme
pi-reservoir
Quota-aware proactive model routing for Pi, Senpi, and omo-native.
Reservoir watches provider/model quota headroom and proactively spreads the next turn across the models you declared to be interchangeable — before a limit error ever happens. It complements (never replaces) your host's reactive retry fallback.
Why
- Native
retry.fallbackChainsswitch models after a quota/rate-limit error. - pi-quotas reads quota telemetry but never switches models.
- Task routers pick what kind of model to use; Reservoir picks which provider/model inside a pool you defined should carry the next turn.
Install
Drop the source into your host's extensions directory:
git clone <this repo> ~/.omo/agent/extensions/pi-reservoir # omo
# or: ~/.senpi/agent/extensions/pi-reservoir # senpi / piThe host extension loader picks up extensions/<dir>/index.ts automatically.
Configure pools in <agentDir>/reservoir.json:
{
"mode": "advisory",
"freshnessTtlMs": 120000,
"refreshIntervalMs": 60000,
"pools": {
"frontier": {
"reservePercent": 15,
"hysteresisPercent": 10,
"billingOrder": ["subscription", "free", "paid_credit", "unknown"],
"models": [
{ "id": "anthropic/claude-fable-5", "billingClass": "subscription" },
{ "id": "kimi-coding/k3", "billingClass": "subscription" }
]
}
}
}One model belongs to exactly one pool; duplicate membership is a config error.
billingClass is per-model-candidate (a single OpenRouter account can hold both
free and paid models). Start with "mode": "advisory" — it records what it
would do without switching anything.
Commands
| Command | Effect |
|---|---|
| /reservoir why | Print the last decision receipt (reason + candidates). |
| /reservoir refresh | Force quota refresh (15s max timeout). |
| /reservoir auto\|advisory\|off | Switch session routing mode. |
| /reservoir pin / unpin | Respect / release manual model choice. |
| /reservoir doctor | Config schema, duplicate pools, missing/unavailable/scoped models, capability probe, Bifrost conflict, quota freshness. |
Commands never write the config file.
Safety model
- Auto switching uses only
pi.setSessionModel()— the persisted default is never mutated; hosts without that API are advisory-only. - Routing hook is
input, only when the session is idle, cache-only (no network in the hot path). - While native fallback is active, Reservoir fully yields (
suppressed-native-fallback). - Manual
set/cyclepins the model until/reservoir unpin. - If Bifrost is loaded, Reservoir refuses
auto(ERROR competing-router:bifrost) — two auto-routers cannot coexist by load order. - Stale/unknown quota is treated as unknown capacity, never assumed healthy.
- No tokens, prompts, or raw error bodies are ever logged; receipts are sanitized.
Docs: architecture, host contract, provider matrix.
Development
bun install # dev deps only (@types/bun, typescript)
bun test # unit + contract suites
bunx tsc --noEmit # typecheck
npm pack --dry-run # package audit
bun scripts/smoke-load.ts # no-network fake-host load smokeZero runtime dependencies. Source-only TypeScript extension.
