Skip to content

Model registry: deprecation fallback, mode presets, reasoning-effort, alias patterns - #107

Merged
gnanam1990 merged 1 commit into
mainfrom
model-registry
Jun 7, 2026
Merged

Model registry: deprecation fallback, mode presets, reasoning-effort, alias patterns#107
gnanam1990 merged 1 commit into
mainfrom
model-registry

Conversation

@gnanam1990

@gnanam1990 gnanam1990 commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator

Module 2 of N — Model registry

Second reviewable slice of the runtime-core split (off main, after #105 merged). Scope: internal/modelregistry only (7 files, ~556 lines incl. tests). Fully additive — the whole tree builds unchanged against it; no new deps.

What's in it

  • Alias patterns: regex MatchPatterns + pattern-based Resolve so models resolve from provider-qualified ids, short aliases, and patterns.
  • Deprecation + fallback: DeprecationRule{FallbackID, WarningMsg}; ResolveWithFallback returns the active fallback model plus a user-facing notice for deprecated ids. NewRegistry validates that every Deprecation.FallbackID resolves to a real model — a misconfigured rule fails at startup instead of being silently ignored.
  • Reasoning effort: per-model ReasoningEfforts + DefaultReasoningEffort (validated to be a member of the model's supported set) + EffectiveReasoningEffort().
  • Mode presets (modes.go): smart/deep/fast/large/precise → concrete model id + reasoning effort.

Scope note

This is the registry-capability layer. The CLI/agent adoption of it — --model deprecation-fallback, the --mode flag, -r/--reasoning-effort, and the enhanced zero models table — lands in a later module so each PR stays reviewable. The new functions are exercised by the package's own tests (resolve_test.go, modes_test.go, catalog_test.go).

Testing

go build ./..., go vet ./..., go test ./..., and go test -race ./internal/modelregistry/ all green.

Part of decomposing #101 (draft) into reviewable PRs; subagents excluded.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

New Features

  • Added fuzzy pattern matching to resolve models from flexible user input formats
  • Introduced model deprecation with automatic fallback redirects and user-friendly notices
  • Added preset conversation modes (smart, deep, fast, large, precise) with predefined reasoning and tool settings
  • Enhanced reasoning effort handling with configurable defaults and smart fallback selection

Tests

  • Added comprehensive test coverage for model resolution, deprecation behavior, reasoning effort validation, and mode discovery

… alias patterns

Module 2 of the runtime-core split (off main, after #105). Scope: internal/modelregistry only — fully additive (the whole tree builds unchanged against it).

- Regex MatchPatterns aliases + pattern-based Resolve.

- DeprecationRule with FallbackID; ResolveWithFallback returns the active fallback model + a user notice for deprecated ids; NewRegistry validates every Deprecation.FallbackID resolves (fail-fast on misconfig).

- ReasoningEffort support: per-model ReasoningEfforts + DefaultReasoningEffort (validated to be a member of the model's set) + EffectiveReasoningEffort().

- Mode presets (modes.go: smart/deep/fast/large/precise -> model id + reasoning effort).

These are the registry-capability layer; the CLI/agent adoption (--model deprecation-fallback, --mode, -r/--reasoning-effort, enhanced 'zero models') lands in a later module to keep PRs reviewable. Build/vet/full-suite/-race green; no new deps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Zero automated PR review

Verdict: No blockers found

Blockers

  • None found.

Validation

  • [pass] Diff hygiene: git diff --check
  • [pass] Tests: go test ./...
  • [pass] Build: go run ./cmd/zero-release build
  • [pass] Smoke build: go run ./cmd/zero-release smoke

Scope

Head: 2946d0904208
Changed files (7): internal/modelregistry/catalog.go, internal/modelregistry/catalog_test.go, internal/modelregistry/models.go, internal/modelregistry/modes.go, internal/modelregistry/modes_test.go, internal/modelregistry/resolve.go, internal/modelregistry/resolve_test.go

This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality.

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR extends the model registry with deprecation support, fuzzy resolution via regex patterns, preset operating modes, and per-model default reasoning effort. ModelEntry gains three new fields (DefaultReasoningEffort, MatchPatterns, Deprecation), the Registry compiles patterns at construction time and validates cross-entry dependencies, and a new Modes system provides five presets (smart, deep, fast, large, precise) with integrated reasoning effort and tool filter management.

Changes

Model Registry Deprecation & Preset Modes

Layer / File(s) Summary
Model Deprecation & Reasoning Effort Data Model
internal/modelregistry/models.go
ModelEntry gains DefaultReasoningEffort, MatchPatterns, and Deprecation fields; new DeprecationRule type includes fallback/warning metadata and Clone() helper; ModelEntry.Validate() enforces reasoning effort consistency and requires non-empty FallbackID in deprecation rules.
Registry Pattern Compilation & Cross-entry Validation
internal/modelregistry/models.go
NewRegistry() compiles MatchPatterns into regex and validates deprecation FallbackID references resolve to known models; compilation errors or dangling references fail fast at registry creation.
Model Catalog Decoration & Deep Cloning
internal/modelregistry/catalog.go
decorateModelDepth enriches DefaultModelEntries with per-model reasoning effort, patterns, and deprecation rules; cloneModelEntry now deep-clones MatchPatterns and Deprecation to prevent shared mutable references.
Model Resolution Utilities
internal/modelregistry/resolve.go
Resolve() maps user input to models via exact lookup then regex patterns; ResolveWithFallback() applies deprecation redirects and emits user-facing notices; EffectiveReasoningEffort() selects effort by honoring requested value, default, first supported, or none.
Preset Modes System
internal/modelregistry/modes.go
Five preset modes (smart, deep, fast, large, precise) define name, description, model, reasoning effort, max-turn budget, and optional tool filters; Modes(), LookupMode(), ModeNames() provide case-insensitive lookup and defensive cloning to prevent mutation.
Resolution Utility Tests
internal/modelregistry/resolve_test.go
Helpers construct ModelEntry fixtures and registries; tests verify regex-based alias resolution, deprecation fallback redirection with notices, and EffectiveReasoningEffort selection under supported/unsupported conditions.
Catalog Integration Tests
internal/modelregistry/catalog_test.go
Tests exercise fuzzy-pattern resolution of default entries (e.g., "sonnet 4.5", "opus 4.1"), deprecation redirects with non-empty notices, and presence/validity of default reasoning effort for reasoning-capable models.
Preset Modes Integration Tests
internal/modelregistry/modes_test.go
Tests validate LookupMode normalization, Modes() defensive copying, ModeNames() order, and end-to-end mode resolution: each preset model is resolvable, effort is valid, EffectiveReasoningEffort honors preset, and MaxTurns is non-negative.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Gitlawb/zero#56: Both PRs extend internal/modelregistry/catalog.go's catalog structure and cloneModelEntry deep-cloning; this PR's pattern-based resolution and deprecation decoration build on the registry foundation.

Suggested reviewers

  • anandh8x
  • Vasanthdev2004
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the four primary features added: deprecation fallback logic, mode presets, reasoning-effort handling, and alias pattern resolution.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch model-registry

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

@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.

🧹 Nitpick comments (1)
internal/modelregistry/modes_test.go (1)

41-50: ⚡ Quick win

Extend test to verify slice field defensive copying.

Test only mutates Name, but cloneMode specifically deep-copies EnabledTools and DisabledTools. Should verify those are also independent.

Proposed extension
 func TestModesReturnsIndependentCopies(t *testing.T) {
 	modes := Modes()
 	if len(modes) == 0 {
 		t.Fatal("Modes() returned no presets")
 	}
+	// Test struct field mutation
 	modes[0].Name = "mutated"
 	if again := Modes(); again[0].Name == "mutated" {
 		t.Fatal("Modes() should return defensive copies, not shared state")
 	}
+	// Test slice field mutation (even though current modes have nil slices, 
+	// cloneMode explicitly handles this)
+	modes = Modes()
+	modes[0].EnabledTools = []string{"injected"}
+	modes[0].DisabledTools = []string{"injected"}
+	again := Modes()
+	if len(again[0].EnabledTools) > 0 || len(again[0].DisabledTools) > 0 {
+		t.Fatal("Modes() should deep-copy tool filter slices")
+	}
 }
🤖 Prompt for 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.

In `@internal/modelregistry/modes_test.go` around lines 41 - 50, Update
TestModesReturnsIndependentCopies to also verify that slice fields are
defensively copied: after calling modes := Modes(), mutate modes[0].EnabledTools
and modes[0].DisabledTools (e.g., append new entries and/or modify existing
elements) and then call again := Modes() and assert that again[0].EnabledTools
and again[0].DisabledTools have not been changed; this ensures cloneMode's deep
copy of EnabledTools and DisabledTools is effective.
🤖 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.

Nitpick comments:
In `@internal/modelregistry/modes_test.go`:
- Around line 41-50: Update TestModesReturnsIndependentCopies to also verify
that slice fields are defensively copied: after calling modes := Modes(), mutate
modes[0].EnabledTools and modes[0].DisabledTools (e.g., append new entries
and/or modify existing elements) and then call again := Modes() and assert that
again[0].EnabledTools and again[0].DisabledTools have not been changed; this
ensures cloneMode's deep copy of EnabledTools and DisabledTools is effective.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 657bd2e6-7d8f-4ed6-ba90-735f53b3dcaa

📥 Commits

Reviewing files that changed from the base of the PR and between 561fe0b and 2946d09.

📒 Files selected for processing (7)
  • internal/modelregistry/catalog.go
  • internal/modelregistry/catalog_test.go
  • internal/modelregistry/models.go
  • internal/modelregistry/modes.go
  • internal/modelregistry/modes_test.go
  • internal/modelregistry/resolve.go
  • internal/modelregistry/resolve_test.go

@anandh8x anandh8x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What's good

  • Decorator pattern keeps the catalog terse. decorateModelDepth separates the base catalog (id, name, capabilities, cost) from the depth metadata (patterns, defaults, deprecation). The base DefaultModelEntries stays readable, the depth wiring is easy to audit, and the layering is explicit. Adding a new depth field (e.g., per-model cost tier overrides) is a one-entry change in the depth map.

  • Cross-entry validation in NewRegistry is the right place. "Deprecation fallback must resolve to a real model" is checked at construction time, so a typo or missing model fails loudly at startup rather than silently redirecting to nothing at runtime. The error message includes both the deprecated model and the missing fallback id, which makes the failure mode actionable.

  • DeprecationRule.Clone() is a real defensive helper. Deep-copying a struct with a pointer field requires explicit cloning. cloneModelEntry uses entry.Deprecation.Clone() to ensure catalog callers can't mutate the registry's internal state by holding onto a returned Deprecation. The TestModesReturnsIndependentCopies test (for the sibling Mode type) confirms the same defensive-copy pattern works.

  • Resolve vs ResolveWithFallback separation is correct. Resolve is the pure "find the model" function (exact id/api-model/alias, then regex pattern). ResolveWithFallback adds the deprecation redirect and the user-facing notice. Callers who don't want a redirect (e.g., a UI listing active models) use Resolve; callers who want the redirect (e.g., the CLI exec command) use ResolveWithFallback. Both are tested independently.

  • EffectiveReasoningEffort cascade is right. Requested if supported → model default → first supported → ReasoningEffortNone. The four-step cascade handles every case. TestEffectiveReasoningEffort asserts: high supported returns high; xhigh unsupported falls back to default low; empty requested falls back to default low. The cascade is also exercised by TestEveryModeResolvesToRealRegistryModel — every mode's requested effort must be supported by the resolved model, so a mode never silently downgrades on apply.

  • Fuzzy match patterns are anchored regexes. Patterns like (?i)^sonnet[^a-z0-9]*4[\.\s]?5$ are anchored to start and end, so "sonnet 4.5 turbo" wouldn't match. [^a-z0-9]* handles spaces, dots, dashes, and other delimiters. [.\s]? allows "4.5" or "4 5". TestResolveRegexAlias confirms "claude-sonnet-4-5" (id), "sonnet-4.5" (alias), "Sonnet 4.5" (mixed case), and "sonnet4.5" (no separator) all resolve.

  • Default reasoning effort is validated to be in the model's supported list. Validate() checks at construction time, and TestDefaultRegistryReasoningModelsHaveDefaultEffort asserts the runtime contract: sonnet-4.5's default is in its supported efforts, etc. A typo in the decorator would fail at NewRegistry time, not at user request time.

  • Mode presets are data-driven. defaultModes is a slice of Mode structs. Adding a new mode is one struct literal. The 5 modes cover the realistic use cases (smart daily driver, deep reasoning, fast edits, long context, precise work) and each has a description.

  • Mode.MaxTurns = 0 semantics are documented as "leave the configured/default turn budget untouched". The cloneMode helper preserves the field on copy. A future slice that actually applies modes will need to respect this contract.

  • The notice logic in ResolveWithFallback is correct. Three branches: redirected (uses deprecation warning or generates one), deprecated with warning but no fallback (returns warning), active (empty notice). TestResolveWithFallbackActiveNoNotice confirms active models get no notice. The fallback warning is generated if WarningMsg is empty: "X is deprecated; using Y instead".

  • ModelStatusDeprecated is the trigger for redirect. A model with Status: ModelStatusActive and a Deprecation rule wouldn't redirect — only the status drives behavior. That's the right call: a rule is just metadata; the status is the action.

  • Test coverage is comprehensive. 13 new test functions across 3 new test files. Every new API is tested. The decorator is tested transitively via the integration tests. The defensive copy is tested. The case-insensitive + trimmed lookup is tested. The deprecation redirect is tested. The fuzzy match patterns are tested. The unsupported effort fallback is tested.

  • The cloneModelEntry and cloneMode helpers are correctly used in DefaultModelEntries and Modes(). The pattern is consistent: build a private catalog, deep-copy before returning.

  • Doc comments on the new types are clear and actionable. DeprecationRule says "FallbackID is required; the date/warning fields are advisory". Mode says "Effort of '' means 'let the model's effective default apply'". EffectiveReasoningEffort says "Requested if the model supports it, otherwise the model's default (or first supported, or none)". A future maintainer can read the doc and understand the contract.

  • The catalog depth wiring addresses a real product need. Deprecation of claude-haiku-3.5 and gpt-4-turbo was a flag in my prior PR review (#56) — the deprecation was set but no fallback. This slice wires the fallback and the notice, and the TestDefaultRegistryDeprecatedModelsRedirect test locks it. The fuzzy aliases ("sonnet 4.5") are also a real UX improvement over requiring exact canonical ids.

Observations (non-blocking)

  1. Fuzzy match patterns are catalog-only, not user-extensible. A user can't add their own pattern for a custom model. NewRegistry accepts patterns via ModelEntry.MatchPatterns, but the canonical catalog is hard-coded. A future enhancement could let users add patterns via config.

  2. Deprecation dates are strings (ISO-8601), not time.Time. SoftDate and HardDate are documentation only — no logic checks against time.Now(). A future enhancement could add "past soft date? warn but resolve" vs "past hard date? error". Currently both are advisory.

  3. DeprecationRule.HardDate is present but unused in the logic. A future slice could enforce it (e.g., return an error from Resolve if past hard date).

  4. No test for active model with a deprecation warning (no redirect). The current TestResolveWithFallbackActiveNoNotice covers active-no-deprecation. An active model with a Deprecation.WarningMsg (but no ModelStatusDeprecated) would return the warning as a notice. A test would lock the contract.

  5. No test for Resolve with overlapping patterns across models. The current catalog has non-overlapping patterns, so order doesn't matter. A future validation could detect overlaps at NewRegistry time (or at least a test that demonstrates the first-match-wins behavior).

  6. EffectiveReasoningEffort could be a method on ModelEntry. A method entry.EffectiveReasoningEffort(requested) would be more discoverable than a free function. The cascade is data-bound to the entry, so method semantics fit.

  7. reasoningEffortAllowedIn helper is duplicated. The test file defines a local helper for the same logic that Validate uses inline. A modelHasReasoningEffort(model, effort) helper would deduplicate.

  8. No benchmark for Resolve or ResolveWithFallback. These are called on every model lookup, so a small benchmark would catch future regressions where someone adds a linear scan over patterns.

  9. The defaultModes order is not asserted by name. TestModeNamesMatchCatalogOrder asserts the order matches Modes() but doesn't lock a specific order (smart, deep, fast, large, precise). A future test could assert the exact order so a reorder is intentional.

  10. The smart and precise modes both use claude-sonnet-4.5. The difference is the effort (Medium vs High). A doc note explaining the distinction would help users pick.

  11. Mode.EnabledTools / DisabledTools are present but unused in the default modes. The cloneMode already deep-copies them. A future slice could wire modes to tool filters.

  12. The decorateModelDepth function mutates entries in place. A future refactor could return a new slice for safety. The current code is correct (the slice is the local one from DefaultModelEntries), but the mutation pattern is subtle.

  13. The notice logic in ResolveWithFallback has 3 branches (redirected, deprecated-with-warning, neither). A future refactor could collapse to 2 by treating "redirected" as a special case of "deprecated with fallback".

  14. No test for Resolve with empty input. strings.TrimSpace("") is "", so the regex ^$ would match a pattern like ^$ (none of the default patterns do). A test could lock the behavior (no match).

  15. No test for Resolve with a model that has MatchPatterns but no aliases. A model with patterns but no aliases would still be resolvable by regex. The current tests use both. A focused test would document the contract.

  16. The doc comment on decorateModelDepth could mention it's catalog-only — user registries from a custom []ModelEntry wouldn't get the depth wiring unless they call a public DecorateDepth helper.

  17. The MatchPatterns are checked in registration order. A future validation could detect overlaps at NewRegistry time. The current code accepts overlaps silently.

  18. The TestResolveRegexAlias test covers 4 input variants (id, alias, mixed case, no separator). A test for partial-match rejection (e.g., "sonnet 4.5 turbo" should NOT match) would lock the anchor behavior.

  19. The MatchPatterns test uses (?i)sonnet[^a-z0-9]*4[.\s]?5 (no anchors) in resolveTestRegistry. This is intentional (the helper tests the pattern in isolation, not the anchored catalog pattern). The catalog uses anchored patterns.

  20. The Decorate function is not exposed as a public helper. A future slice could export DecorateModelDepth(entries []ModelEntry) []ModelEntry for users building custom catalogs.

  21. The cloneMode helper is similar to cloneModelEntry but for a different type. A future refactor could unify them, but they have different field shapes (Mode has no costs/context limits).

  22. The ModelEntry.Validate checks the DefaultReasoningEffort is in ReasoningEfforts via a manual loop. A modelHasReasoningEffort helper (or inlining the test helper) would deduplicate.

Approving — this is a well-architected slice. The decorator pattern keeps the catalog terse, the cross-entry validation catches typos at startup, the Resolve/ResolveWithFallback separation is correct, the EffectiveReasoningEffort cascade handles every case, the fuzzy match patterns are anchored, and the mode presets are data-driven. The test coverage is comprehensive (13 new tests across 3 new test files). The deprecation wiring closes a real gap from prior slices (PR #56 had deprecation but no fallback). The follow-ups are minor (user-extensible patterns, time.Time dates, active model with deprecation notice contract, order lock test, benchmark).

@gnanam1990
gnanam1990 merged commit 07ee160 into main Jun 7, 2026
6 checks passed
gnanam1990 added a commit that referenced this pull request Jun 7, 2026
…eractive-command detection (#109)

* Sandbox hardening: destructive/network/installer classification + interactive-command detection

Module 3 of the runtime-core split (off main, after #107). Scope: internal/sandbox only — clean additive extract; no new deps; whole tree builds + full suite/-race green.

LIVE (Classify is already called by sandbox.Evaluate and agent/loop.go, so this hardens existing behavior immediately):

- rm -rf targeting / $HOME ~ * — now tolerant of surrounding quotes, ${HOME} braces, combined/reordered -r/-f flags, and an optional -- separator; chmod 777 only flagged when recursive or root-targeted (single-file chmod no longer false-positives).

- network-command + piped-installer detection (curl|sh incl |zsh and other shells); command resolved across command/cmd/script/shell aliases so an alias key can't bypass the gate.

FORWARD (compiles now, wired by the later tools/bash module): DetectInteractiveCommand flags interactive programs (vim/less/...) behind wrappers (sudo/nice/timeout, and sh -c/bash -c payloads), hardened against absolute-path/quote/escape/substitution bypasses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address #109 review (CodeRabbit): fix sandbox detection over/under-reach

- [critical] rm long-flag root: --no-preserve-root -rf -- "/" and ... -rf "/" now caught (quote + -- handling restored across short AND long flags).

- chmod 777 abs-path narrowed to root or a sensitive SYSTEM tree (/, /etc, /usr, …); a single-file abs path like chmod 777 /tmp/build.sh is no longer a false positive.

- piped_installer now requires a remote fetch (curl/wget/fetch/aria2c) before the pipe; a local 'cat x | bash' / 'printf | sh' is no longer mis-flagged (engine_test updated to assert the corrected semantics).

- nonInteractiveREPLFlags adds mongo/mongosh (--eval/-f/--file) so 'mongo --eval ...' isn't flagged interactive.

- programIndex now normalizes tokens (basename/quote-strip/lowercase) like firstProgram, so full-path invocations (/usr/bin/python script.py, /bin/bash -c 'vim …') classify correctly (no false positives / missed nested detections).

TDD for each; build/vet/-race/windows cross-compile/full-suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: KRATOS <kratos@KRATOSs-Mac-mini.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Vasanthdev2004
Vasanthdev2004 deleted the model-registry branch June 28, 2026 08:27
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.

2 participants