pi-pinball
pi provider failover: auto-switch and retry on rate limits, quota errors, or 5xx.
Package details
Install pi-pinball from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-pinball- Package
pi-pinball- Version
1.0.5- Published
- Sep 18, 2026
- Downloads
- 809/mo · 27/wk
- Author
- noguerol
- License
- MIT
- Types
- extension
- Size
- 45.2 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"image": "https://raw.githubusercontent.com/noguerol/pinball/main/docs/preview.jpeg",
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README

Pinball — Automatic Provider Failover for pi
Pinball keeps the ball in play. When your active model hits a rate limit, runs out of quota/credits, or a provider has a persistent outage, pinball transparently bounces the task to the next model in your bounce list and retries — mid-conversation, without you lifting a finger.
It works in interactive sessions and in headless spawned processes (pi -p, pi --mode json -p), which makes it the failover layer for multi-agent setups: a sub-agent that burns its quota at 2 AM no longer kills the run — pinball flips it to the next provider and the task finishes.
Features
- Automatic failover on rate limits (429), payment/quota errors (402/403), billing/credit exhaustion, and persistent server errors (5xx)
- Three detection points — catches failures before pi even creates the error message, mid-stream errors that arrive with HTTP 200, and runs that die with no auto-retry left
- Transparent retry — the agent continues the same task with the next model; the conversation just keeps going
- Bounce list with priorities — an ordered model list (same registry as
/model); order defines the bounce chain - Per-model cooldowns — a failed model is skipped for a configurable window, then given another chance
- Retry budget —
maxRetriesattempts per model before pinball gives up cleanly instead of looping forever - Smart error classification — bounce-worthy errors vs. context overflow (pi handles that with compaction, not switching)
- Provider tester — one keystroke to check connectivity/latency of every configured model
- Interactive everything — native pi menus for every setting; no config file editing required
- Status bar widget — a compact
🎯 (on)footer indicator with live cooldown count; hidden entirely while pinball is off
Install
Pinball is a pi package: one extension (src/index.ts) declared in package.json.
# From GitHub
pi install git:github.com/noguerol/pinball
# Pin a tag/commit (refs are never moved by `pi update`)
pi install git:github.com/noguerol/pinball@v1.0.0
# Local checkout (development)
pi install /path/to/pinball
# Try it for one run only, without installing
pi -e git:github.com/noguerol/pinball
pi list # show installed packages
pi remove git:github.com/noguerol/pinball
Security: pi packages run with full system access — extensions execute arbitrary code. Install only packages you trust and review the source.
Requirements: a working pi installation with at least one configured provider. Pinball reads its bounce candidates from pi's own model registry — the same models you see in /model.
Quick Start
/pinball add # pick provider → model from pi's registry (repeat for each fallback)
/pinball status # verify: ENABLED, bounce list, retry budget
That's it. The next time a provider returns 429 Too Many Requests mid-task:
🔄 provider/x → provider/y; retrying…
… and the task continues on provider/y. A 🎯 (on 1) marker in the footer shows one model in cooldown (plain 🎯 (on) when everything is healthy; the indicator disappears when pinball is disabled).
How It Works
Detection points
Pinball watches the failure at three stages, all feeding the same bounce core:
| Stage | Event | Catches |
|---|---|---|
| 1. HTTP hook | after_provider_response |
HTTP 429/402/403 — before pi creates the error message, so pi's own retries already use the new model |
| 2. Message end | message_end with stopReason: "error" |
Providers that fail mid-stream with HTTP 200 — e.g. SSE error events like "usage limit has been reached" |
| 3. Settled | agent_settled |
The run died on a bounce-worthy error and pi will not auto-retry: pinball bounces and awaits a re-trigger via sendUserMessage, so the task resumes with the next model even in headless print mode (a fire-and-forget retry would die with the process) |
Repeated reports of the same failing model within a short dedupe window (10s) are collapsed, so the bounce list is never thrashed by redundant signals.
What counts as bounce-worthy
| Bounces ✅ | Does NOT bounce ❌ |
|---|---|
| HTTP 429 / 402 / 403 / 5xx | Context overflow — pi handles it with compaction |
rate limit, too many requests |
Normal completion (stop, toolUse, length) |
quota, usage limit, limit reached |
User aborts |
insufficient, out of budget, available balance |
Bounce-unrelated errors |
billing, credit, payment required |
|
resource exhausted, overloaded, service unavailable |
The bounce chain
Models bounce in list order: M1 → M2 → M3 → …. When the chain is exhausted, pinball stops cleanly (se agotaron N intentos consecutivos) instead of looping — reset with /pinball reset when you've topped up.
A bounced model enters cooldown (default 60s): it's skipped during that window, then becomes eligible again. The original model is remembered and shown in /pinball status.
Commands
All management lives under one command with subcommands:
| Command | Description |
|---|---|
/pinball |
Toggle pinball on/off |
/pinball enable / disable |
Enable/disable |
/pinball status |
State, active model, retries, cooldowns, last bounce |
/pinball config |
Interactive config menu |
/pinball add |
Add a model to the bounce list (provider → model picker from pi's registry) |
/pinball list |
Manage the bounce list: reorder, replace, remove |
/pinball remove |
Quick-remove a model |
/pinball test |
Test connectivity of every configured model |
/pinball reset |
Clear all cooldowns and the retry budget |
/pinball log |
Show the bounce history of this session |
(There is no /pinball daemon, service or background process — it's a pure in-process extension.)
/pinball config
The menu exposes every setting with live values:
- 🟢 Enable/disable pinball
- 📋 List models — jump to the bounce list manager
- ➕ Add model / ➖ Remove model
- 🧪 Test all providers — pings each model, shows ✅/🔑/❌ + latency
- ⏱️ Cooldown — how long a failed model is skipped
- 🔁 Max retries — bounce attempts per model before giving up
- 🔔 Notifications — on/off for bounce notifications
- 🔃 Reset cooldowns
/pinball list — the bounce list manager
The list order is the bounce priority. Selecting a model opens an action menu:
- ⬆️⬇️ Move up/down · ⏫⏬ Move to top/bottom
- 🔁 Replace with another model from pi's registry (duplicate-checked)
- 🗑️ Remove
The list re-renders after every action until you exit. Models in cooldown show ⏳; the original model shows ⭐.
/pinball status
State: ✅ on
Model: provider/x
List: 3
Retries: 1/9
Streak: 0
Cooldowns: 1
Cooldown: 60s
Original: provider/x
Last: provider/x → provider/y (rate-limit/quota, 2m ago)
Configuration
Everything is configurable through the UI, but the persisted file is simple — ~/.pi/agent/pinball.json:
{
"enabled": true,
"maxRetries": 3,
"cooldownMs": 60000,
"notifyOnBounce": true,
"models": [
{ "provider": "anthropic", "id": "claude-sonnet-4" },
{ "provider": "openai", "id": "gpt-4.1", "name": "GPT-4.1" },
{ "provider": "local-models", "id": "127.0.0.1:8080/Qwen3.6-35B-A3B" }
]
}
| Field | Default | Description |
|---|---|---|
enabled |
true |
Master switch |
maxRetries |
3 |
Bounce attempts per model before giving up |
cooldownMs |
60000 |
How long a failed model is skipped |
notifyOnBounce |
true |
Show a notification on every bounce |
models |
[] |
The bounce list, in priority order |
Coming from model-bouncer? Pinball is the renamed successor. On first run it migrates an existing
~/.pi/agent/model-bouncer.jsonintopinball.jsonautomatically — no action needed.
Headless & Multi-Agent Notes
Pinball is designed to be the failover layer for spawned agents (pi -p, pi --mode json -p, multi-agent runtimes):
- In print mode the process exits as soon as the prompt settles. Pinball's
agent_settledhandler therefore bounces and awaits a re-trigger throughsendUserMessage— the retry is delivered before the process is allowed to die. - The bounce list is per-machine (
~/.pi/agent/pinball.json), so every spawned agent inherits the same failover chain with zero per-agent setup. - Combined with a multi-agent orchestrator, a whole fleet of agents can survive a provider outage with no code changes.
Architecture
pinball/
├── package.json # pi package manifest (pi-package)
├── LICENSE # MIT
├── README.md
└── src/
├── index.ts # Extension entry point: hooks, state, config, command registration
└── commands.ts # Lazy-loaded /pinball UI and command handlers
Zero-dependency extension (only pi's bundled @earendil-works/pi-coding-agent + Node built-ins):
- Small startup path —
src/index.tsregisters hooks and/pinball; interactive command UI loads on demand via dynamicimport() - Error classification — regex sets for bounce-worthy vs. context-overflow errors, defensive
stopReasonnormalization - Bounce core — target selection (skipping cooldowns), budget accounting, bounce log
- Detection hooks —
after_provider_response,message_end,agent_settled - Persistence —
~/.pi/agent/pinball.jsonwith one-time migration from the legacymodel-bouncer.json - UI — native pi menus, status bar widget, bounce log viewer
License
MIT © Javier Noguerol
