[feat] model_catalog: discover OpenCode free models live with cache + fallback (§55)#439
Merged
Merged
Conversation
04de2c5 to
e7504b0
Compare
…fallback
Implements Backlog §55. The static `OPENCODE_FREE_MODELS` tuple
in scripts/model_catalog.py was drifting against the live OpenCode
registry: `opencode/minimax-m3-free` is no longer listed but was
still in the static fallback, and 4 new free models
(`gemini-3-flash`, `gpt-5.1-codex-mini`, `gpt-5.4-mini`,
`north-mini-code-free`) appeared in the registry that the static
list did not know about.
## What changed in scripts/model_catalog.py
- New `fetch_opencode_free_models()` calls `opencode models`,
filters the line-delimited output for free-tier models
(`-free` substring, plus an explicit exact-match list for
slugs whose free status is implied by provider convention), and
returns an `OpencodeModelCache` value object with
`fetched_at`, `models`, and `source` (`live` | `cache` |
`fallback`).
- File-based cache at `$XDG_CACHE_HOME/ai-issue-solver/opencode_models.json`
(or `~/.cache/...`), TTL 1h. One cache file keeps the lookup
deterministic across the same batch of Solver runs.
- `opencode` binary resolution honours $OPENCODE_BIN (override)
and falls back to `~/.opencode/bin/opencode` (standard
install). If neither is found, returns the static
`OPENCODE_FREE_MODELS` list as fallback.
- `build_opencode_catalog()` now takes a `live_models` parameter
(defaults to calling `fetch_opencode_free_models()`).
- `OPENCODE_DEFAULT_MODEL` is only applied as `default_for` when
it is in the live list; otherwise no entry gets `default_for`,
avoiding a silent 'first one wins' fallback.
- The dead `opencode/minimax-m3-free` slug is removed from the
static fallback list. If the live discovery ever fails AND this
slug is still wanted, the regression test `test_dead_minimax_m3_free_is_not_in_static_fallback`
will catch it.
- `build_model_catalog()` forwards an optional
`live_opencode_models` parameter to `build_opencode_catalog()`.
## Tests
- 11 new tests in tests/test_model_catalog.py:
- `FetchOpencodeFreeModelsTests` (7): live fetch + filter,
binary missing, subprocess error, cache fresh, cache stale,
use_cache=False override, pattern recognition (including the
dead-slug regression comment).
- `test_opencode_live_only_models_show_up_when_discovered` —
ensures brand-new free models surface via the live path.
- `test_opencode_default_is_first_live_model_when_static_default_missing` —
no implicit default_for when static default is not in live
list.
- `test_opencode_no_models_returns_empty_catalog` — empty live
list yields an empty opencode section.
- `test_dead_minimax_m3_free_is_not_in_static_fallback` —
regression guard for the dead slug removal.
- Existing `test_opencode_free_models_are_catalogued` updated to
inject `live_opencode_models=OPENCODE_FREE_MODELS` for
deterministic behaviour.
17 unit tests in test_model_catalog green (was 6, +11 new).
15 unit tests in test_model_selection still green.
## Out of scope (deliberately)
- Cross-provider free-model aggregation (e.g. `minimax/MiniMax-M2.7`
is also free in some configurations but lives under a different
provider slug). Defer to a follow-up if the Solver pipeline ever
picks models across providers.
- Auto-update of role_routing.yaml `model:` fields when the
discovered free list changes. Today the catalog is informational
only; Solver still uses `--model-name` as configured.
e7504b0 to
a80ad28
Compare
This was referenced Jun 25, 2026
Merged
SaJaToGu
pushed a commit
that referenced
this pull request
Jun 25, 2026
- open.md: §58 stubbed out as DONE-via-PR-#443 (squash 11eafc1). Notes that the docs/AGENTS.md Recently-Removed-Patterns list is now maintainer-pflegbar; future PRs that intentionally remove a pattern should add a row in the same PR. Cross-references the still-open potential §59 (patch-mismatch hardening). - done.md: full §58 closure entry with problem (PR #441 episode), fix locations (docs/AGENTS.md, scripts/solve_issues.py, tests/test_solve_issues.py), the user-found path-leak fix from live review (now repo-relative when under PROJECT_ROOT, verbatim for explicit env-var override), verification matrix (168 OK + GitHub CI green + manual probe + user live review 'mergebereit'), and the maintainer obligation going forward. Backlog is now EMPTY: §51/§52/§53/§54 done-via-PRs, §55 done-via-PR-#439, §56/§57/§58 documented as done. The implicit §59 (patch-mismatch hardening for the normal solve path) remains a possible future item but is not filed.
SaJaToGu
pushed a commit
that referenced
this pull request
Jun 25, 2026
§59 = 'Watchlist: Patch-mismatch hardening for the normal solve path'. Priority 4, labels kind/watchlist + theme/solver + area/prompt. Status: WATCHLIST ONLY — not an active backlog commitment. No code or architecture work until the activation trigger (≥3 Mode-C patch-mismatch runs on the normal solve path, ideally across different files) is met. Rationale: - The Mode-C failure (worker produces a patch that git apply rejects because it does not match the current working tree) is the same patch-mismatch symptom §56 addressed for --rework-pr. - Before §57, Mode-C on the normal solve path could produce regression-PRs (PR #441 vs. PR #439). After §57, Mode-C is a clean failure with no PR — acceptable behavior. - A §59 fix would turn these clean failures into clean successes, but the architectural cost (prompt anchoring, per-file git apply --check, optional re-prompt loop) is only worth it if the failure pattern is systematic. One data point (Issue #389 re-run, scripts/model_selection.py:52, 1 patch, 0 applied, no PR) is not enough to justify the work. - When a Mode-C failure appears on the normal solve path, log the file + issue + date here so we can see when the threshold is met. Non-goal while parked: no code changes. §57 + §58 are sufficient to keep the pipeline correct.
SaJaToGu
pushed a commit
that referenced
this pull request
Jun 25, 2026
…ed-patterns guards Extends PR #447 (liquid/lfm-2.5-1.2b-instruct:free initial fix that hard-stopped cleanly after §57+§60, but covered only partial scope of Issue #446). User reviewed and asked for two missing points: 1. **OpenCode Free Models (dynamisch)**: new block in README that explains scripts/model_catalog.py as the live source of truth for free-model discovery (with cache + static fallback, 'missing' / 'stale' classification, --verify-opencode and --list-opencode-free-models CLI paths). 2. **Recently-Removed-Patterns-Guard**: new block that explains the docs/AGENTS.md 'Recently Removed Patterns' table the solve prompt now consults before re-introducing a recently-removed pattern. Lists the two current entries (static free_models removed by PR #439; $20/$20 cost-cap defaults replaced by $15/$50 + staggered warnings in PR #437). Notes that the guard is 'soft' (worker explains in the PR body if needed) and that maintainers update the table in every removal PR. PR #447 also kept its earlier additions (Hard-Stop-Sicherheit absatz + Backlog-Status-Update). Closes #446 (now full scope).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements Backlog §55
The static
OPENCODE_FREE_MODELStuple in scripts/model_catalog.py was drifting against the live OpenCode registry:opencode/minimax-m3-freewas no longer listed but still in the static fallback, and 4 new free models appeared in the registry that the static list did not know about:opencode/gemini-3-flashopencode/gpt-5.1-codex-miniopencode/gpt-5.4-miniopencode/north-mini-code-freeAfter this PR, the catalog is built from the live
opencode modelslisting (cached for 1h), with the static list as a fallback when the binary is unavailable.What changed in scripts/model_catalog.py
fetch_opencode_free_models()callsopencode models, filters the line-delimited output for free-tier models (-freesubstring, plus an explicit exact-match list for slugs whose free status is implied by provider convention), and returns anOpencodeModelCachevalue object withfetched_at,models, andsource(live|cache|fallback).$XDG_CACHE_HOME/ai-issue-solver/opencode_models.json(or~/.cache/...), TTL 1h. One cache file keeps the lookup deterministic across the same batch of Solver runs.opencodebinary resolution honours $OPENCODE_BIN (override) and falls back to~/.opencode/bin/opencode(standard install). If neither is found, returns the staticOPENCODE_FREE_MODELSlist as fallback.build_opencode_catalog()now takes alive_modelsparameter (defaults to callingfetch_opencode_free_models()).OPENCODE_DEFAULT_MODELis only applied asdefault_forwhen it is in the live list; otherwise no entry getsdefault_for, avoiding a silent 'first one wins' fallback.opencode/minimax-m3-freeslug is removed from the static fallback list.build_model_catalog()forwards an optionallive_opencode_modelsparameter tobuild_opencode_catalog().Tests
11 new tests + 1 regression guard in tests/test_model_catalog.py:
FetchOpencodeFreeModelsTests(7): live fetch + filter, binary missing, subprocess error, cache fresh, cache stale, use_cache=False override, pattern recognition.test_opencode_live_only_models_show_up_when_discovered— ensures brand-new free models surface via the live path.test_opencode_default_is_first_live_model_when_static_default_missing— no implicit default_for when static default is not in live list.test_opencode_no_models_returns_empty_catalog— empty live list yields an empty opencode section.test_dead_minimax_m3_free_is_not_in_static_fallback— regression guard for the dead slug removal.17 unit tests in test_model_catalog green (was 6, +11 new). 15 unit tests in test_model_selection still green.
Files
scripts/model_catalog.py— +218/-8tests/test_model_catalog.py— +197/0Out of scope
minimax/MiniMax-M2.7is also free in some configurations but lives under a different provider slug).model:fields when the discovered free list changes.