Skip to content

feat(pi,omp): add runtime model fallback - #2358

Merged
shunkakinoki merged 7 commits into
mainfrom
claude/pi-fallback-support-335ac0
Aug 12, 2026
Merged

feat(pi,omp): add runtime model fallback#2358
shunkakinoki merged 7 commits into
mainfrom
claude/pi-fallback-support-335ac0

Conversation

@shunkakinoki

@shunkakinoki shunkakinoki commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Adds a fallback chain on Pi, and turns on OMP's native one. OpenCode is unchanged.

Each host gets the mechanism it actually has

Investigation changed the shape of this PR twice, so the current split is:

Host Mechanism Why
OMP native retry.fallbackChains in config/omp/config.yml OMP ships first-class fallback chains — per-role, wildcard selectors, cooldown-expiry revert, thinking-level preservation, integrated with turn recovery. Strictly better than anything an extension can do.
OpenCode unchanged, opencode-runtime-fallback@0.2.3 It aborts the in-flight session request and replays message parts through the OpenCode server API, plus parent-session resolution for subagents. ~116KB of OpenCode-specific machinery with nothing portable.
Pi new extension, config/pi/fallback.ts Pi's built-in retry only retries the same model, and it has no chain support. This is the one host that needs code.

Since Pi is the only consumer, the policy lives in that one self-contained file rather than a shared module — an earlier revision of this PR had a config/shared/fallback/ extracted for Pi + OMP, which the OMP finding made pointless.

Pi extension

Reads ~/.pi/agent/fallback.json (same schema as opencode-fallback.jsonc minus timeout_seconds, which an extension cannot honor on Pi). On a turn ending in a retryable error it cools the failed model down, selects the next eligible model in the chain, and re-triggers the turn.

  • Candidates the host cannot select (unregistered, no API key) are cooled and skipped, so one bad entry does not strand the chain; exhaustion is reported only when every candidate fails.
  • Malformed config degrades to defaults (fallback disabled) instead of throwing from inside agent_end.
  • A model active that the extension did not select is treated as a user choice: the chain resets and the primary is never restored over it. Pi emits model_select and OMP does not, so this diffs ctx.model instead of relying on an event only one host has.
  • The primary is only forgotten once a restore is confirmed.

Chains

Host Default Fallbacks
Pi cliproxyapi/glm-4.7 minimax-m3, gemini-3.6-flash, openrouter-preset GLM, claude-haiku
OMP cliproxyapi/deepseek-v4-flash gpt-5.6-luna, gpt-5.3-codex-spark, gpt-5.6-sol

OMP's default chain is expanded to every model role without its own chain, so role turns fall back too.

Testing

  • shellspec spec/llm_update_spec.sh — 86 examples, 0 failures.
  • OMP config verified by OMP itself: omp config get retry.fallbackChains returns the configured chain and retry.fallbackRevertPolicy returns cooldown-expiry.
  • Pi extension driven against a stub host in an isolated HOME: 429 → minimax, 503 → gemini-flash, 400 ignored; unregistered candidates skipped to the next; manual switch to claude-opus-5 survives a clean turn with no restore; {"fallback_models": "..."} yields [] and no-ops.
  • nix fmt, nix flake check, biome check clean.

@indent-zero

indent-zero Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Warning

Indent Zero is shutting down on August 7th. Please migrate over to Indent 2.0 to continue getting PR reviews.

PR Summary

Adds runtime model fallback for the Pi and OMP hosts. Design split by host: OMP uses its native retry.fallbackChains (per-role, with dedicated chains for the GPT-pinned roles to avoid OMP's positional candidate selection stranding them at chain tails), and Pi — which has no built-in support — ships a self-contained extension with the previously-shared policy inlined into config/pi/fallback.ts. Pi's chain has been repointed to all-free-tier CLIProxy models.

  • Pi fallback chain: [cliproxyapi/gemma-4-31b-it, cliproxyapi/glm-4.7, cliproxyapi/free] — all zero-cost. gemma-4-31b-it and free (router-fallback) are added to config/pi/models.json and .tpl.json. glm-4.7 is Pi's default model but including it in the chain is safe because onError filters ref !== currentRef before selecting.
  • Pi extension (config/pi/fallback.ts): per-field parseFallbackConfig, chain-walking loop that cools and skips unselectable candidates, transactional pendingRestore/confirmRestore, ctx.model vs appliedRef diff for manual-switch detection, timeout_seconds omitted.
  • OMP native chains (config/omp/config.yml + .tpl.yml): retry.modelFallback: true, retry.fallbackRevertPolicy: "cooldown-expiry", explicit chains for default and slow/vision/plan (avoiding the positional-tail zero-candidate problem in findRetryFallbackCandidates).
  • Spec (spec/llm_update_spec.sh): asserts Pi chain composition, every Pi fallback ref resolves against config/pi/models.json (guards against unregistered refs cooling the whole chain), Pi schema keys ⊂ OpenCode keys with delta exactly ["timeout_seconds"], Pi self-containment (no config/shared/fallback), OMP native config values via yq, and a positional-invariant assertion that no modelRole sits at the last index of its inherited chain (checked by both exact and base selector).

Issues

All clear! No issues remaining. 🎉

7 issues already resolved
  • onSuccess clears primaryRef before the caller confirms the restore actually succeeded. If selectRef(restore) returns false, the primary is silently lost — the session stays on the fallback model with no way to re-arm the restore. (fixed by commit fface40)
  • try/catch around host.on("model_select", …) is dead code — Pi accepts the handler natively and OMP unconditionally accepts any event string via handlers.set(...) without throwing. The catch and its comment are misleading defensive code. (fixed by commit fface40)
  • loadFallbackConfig uses a Partial<FallbackConfig> type assertion, not runtime validation — a syntactically-valid JSON with wrong types (e.g., "fallback_models": "gpt", "retry_on_errors": null) will parse cleanly, slip past the catch, and later throw inside agent_end (.some, .find, .includes on non-arrays), killing the very turn the extension is supposed to rescue. (fixed by commit fface40)
  • Fallback stops after the first setModel rejection instead of trying the next candidate: when selectRef returns false the handler cools the target down, notifies once, and returns — no sendMessage, no next-model attempt. A single unregistered/keyless fallback model thus swallows the entire chain until the user manually retries. (fixed by commit 88eca69)
  • timeout_seconds is declared in FallbackConfig, defaulted to 30, and shipped in all three fallback JSON files, but no code path in createFallbackPolicy or attachFallback ever reads it — the setting is silently ignored. Either wire it into the fallback flow (e.g., wrap setModel/sendMessage in an AbortSignal.timeout) or drop the field so the shared schema stops advertising unimplemented behavior. (fixed by commit fface40)
  • On OMP, a user's manual model switch can be silently overwritten by the restore path: OMP's ExtensionAPI never emits model_select (only internal model_changed), so noteManualSelection never fires. After a fallback armed primaryRef=A, if the user manually picks C, the next successful turn past A's cooldown will restore A on top of C. (fixed by commit fface40)
  • The new Describe 'Pi and OMP runtime fallback' block in spec/llm_update_spec.sh isn't closed before the pre-existing OMP CLIProxy/DeepSeek/OpenRouter tests, so the closing End at line 218 swallows eight unrelated cases into this Describe. (fixed by commit fface40)

CI Checks

Waiting for CI checks...

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@shunkakinoki, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 10 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d0674c8-61c4-4500-8180-4889556489b8

📥 Commits

Reviewing files that changed from the base of the PR and between 7e57181 and 5000be0.

📒 Files selected for processing (10)
  • config/omp/config.tpl.yml
  • config/omp/config.yml
  • config/pi/default.nix
  • config/pi/fallback.json
  • config/pi/fallback.tpl.json
  • config/pi/fallback.ts
  • config/pi/models.json
  • config/pi/models.tpl.json
  • scripts/llm-update.sh
  • spec/llm_update_spec.sh
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added automatic model fallback for Pi and OMP when configured provider or HTTP errors occur.
    • Supports ordered fallback models, retry limits, cooldowns, timeouts, and notifications.
    • Automatically resumes with the primary model after a successful cooldown.
    • Added configurable fallback settings with template-based installation.
  • Bug Fixes

    • Prevents fallback policy files from being loaded as extensions.
    • Excludes default models from fallback chains.

Walkthrough

Adds shared runtime fallback policy logic and integrates it with Pi and OMP. The change adds configurable retry rules, model cooldowns, fallback switching, continuation turns, Home Manager installation, template generation, and validation tests.

Changes

Runtime fallback

Layer / File(s) Summary
Shared fallback policy
config/shared/fallback/runtime-fallback.ts
Defines fallback configuration, model-reference parsing, retry decisions, attempt tracking, cooldowns, model restoration, and fallback selection.
Host event integration
config/shared/fallback/runtime-fallback.ts
Handles provider responses and agent-end events. It selects fallback models, sends notifications, and starts continuation turns.
Pi and OMP integration
config/pi/..., config/omp/..., scripts/llm-update.sh
Adds Pi and OMP extensions, fallback configurations, Home Manager mappings, and template-to-output mappings.
Fallback generation and wiring tests
spec/llm_update_spec.sh
Tests template mappings, generated fallback chains, model exclusions, shared policy wiring, and installation paths.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Provider
  participant RuntimeFallback
  participant FallbackPolicy
  participant PiOrOMP
  Provider->>RuntimeFallback: provider response
  RuntimeFallback->>FallbackPolicy: classify status or error
  FallbackPolicy-->>RuntimeFallback: fallback decision
  RuntimeFallback->>PiOrOMP: select fallback model
  PiOrOMP-->>RuntimeFallback: model selection result
  RuntimeFallback->>PiOrOMP: dispatch continuation turn
Loading

Poem

I’m a rabbit with a fallback trail,
When one model fails, I hop without fail.
Cooldowns mark the paths I know,
Pi and OMP help the next turn flow.
Configured carrots guide me where to go.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: adding runtime model fallback support for Pi and OMP.
Description check ✅ Passed The description directly explains the Pi and OMP fallback changes, host-specific mechanisms, configuration, and testing.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/pi-fallback-support-335ac0

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@config/shared/fallback/runtime-fallback.ts`:
- Around line 232-246: Update the fallback handling around
selectRef(decision.to) so a failed selection marks that candidate unavailable
and continues attempting the next eligible fallback model instead of returning
immediately. Reuse the existing fallback-selection flow, and notify/report
exhaustion only after all eligible candidates fail to select; preserve the
commit and continue-message behavior once a candidate is successfully selected.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: df576f49-8363-49fb-942c-02411d630c46

📥 Commits

Reviewing files that changed from the base of the PR and between 5b82760 and 7e57181.

📒 Files selected for processing (11)
  • config/omp/default.nix
  • config/omp/fallback.json
  • config/omp/fallback.tpl.json
  • config/omp/fallback.ts
  • config/pi/default.nix
  • config/pi/fallback.json
  • config/pi/fallback.tpl.json
  • config/pi/fallback.ts
  • config/shared/fallback/runtime-fallback.ts
  • scripts/llm-update.sh
  • spec/llm_update_spec.sh

Comment thread config/shared/fallback/runtime-fallback.ts Outdated
Comment thread config/shared/fallback/runtime-fallback.ts Outdated
Comment thread config/shared/fallback/runtime-fallback.ts Outdated
Comment thread config/shared/fallback/runtime-fallback.ts Outdated
Comment thread spec/llm_update_spec.sh
Comment thread config/shared/fallback/runtime-fallback.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 11 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread config/shared/fallback/runtime-fallback.ts Outdated
Comment thread config/shared/fallback/runtime-fallback.ts Outdated
Comment thread spec/llm_update_spec.sh Outdated
Comment thread config/shared/fallback/runtime-fallback.ts Outdated
Comment thread config/shared/fallback/runtime-fallback.ts Outdated
Comment thread config/shared/fallback/runtime-fallback.ts Outdated
@shunkakinoki shunkakinoki changed the title feat(pi,omp): add runtime model fallback with shared policy feat(pi,omp): add runtime model fallback Aug 12, 2026
@shunkakinoki
shunkakinoki merged commit 2bdaef4 into main Aug 12, 2026
9 of 11 checks passed
@shunkakinoki
shunkakinoki deleted the claude/pi-fallback-support-335ac0 branch August 12, 2026 15:43
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