🤖 feat: route skills to model classes (Settings-managed large/medium/small) - #3849
🤖 feat: route skills to model classes (Settings-managed large/medium/small)#3849asm wants to merge 28 commits into
Conversation
|
To use Codex here, create a Codex account and connect to github. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 60f19ad5e5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review All three findings addressed in b1b0bf8:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1b0bf8591
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Round-2 finding addressed in 6c4903d: routed sends now compact within |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c4903deb0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Both round-3 findings addressed in 297b210:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 297b210330
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Round-4 finding addressed in 50b68ee: added |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 50b68ee8fc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review All three round-5 findings addressed in 6452b8a:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6452b8a491
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Both round-6 findings addressed in 47afc04:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 47afc04612
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Round-7 findings in 44facc0:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 44facc03ea
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review All three round-8 findings addressed in 79fb8e0:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 79fb8e040b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Round-9 finding addressed in 6284377: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 62843778d0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
6284377 to
3d6ffbd
Compare
|
@codex review Both round-10 findings addressed, and the branch is rebased onto latest main (the #3844 conflict in agentSession.ts resolved by adopting the new gateway-preserving
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3d6ffbd18d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Round-11 finding addressed: the compact-and-retry metadata rebuild now carries |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f9eb115404
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Round-12 finding addressed: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2ecc3f4b18
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aa9787ede8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…adjacent gates
Wave-17 review findings:
- The consent chain now terminates AT the provider-dispatch boundary: a
shared rejection closure (durable pre_stream_rejected abandon on the
accepted row + visible stream error) runs before streamWithHistory's
startup work AND inside it immediately before aiService.streamMessage,
so revocation during partial-state commit / file detection / history
reread / attachment resolution cannot ship the routed request.
- Both late gates surface as ACCEPTED pre-stream failures (Ok(undefined)
+ notifyAcceptedPreStreamFailure), never a pre-acceptance Err: the
row is durable by then, and a renderer draft-restore would duplicate
it. The abandon marker keeps startup recovery from resuming the
persisted routed retry options without the gates.
- Project-scope consent now tracks CONTENT, not just the invocation:
materialization reports any project-scope ref (slash or inline,
deduped included — a deduped snapshot rides history and cannot be
omitted, so it rejects like the invocation), and the late gates key on
that flag, covering a global routed skill carrying an inline
$project-skill ref.
- On-send compaction re-runs the budgeted-goal pricing gate on the
compaction model (it may inherit the unpriced pre-routing ambient
model that the routed-model gate never validated).
- On-send compaction defers the routed skill, so the accepted payload
reports { queued: true } instead of a class model that never
dispatched; attribution happens when the follow-up re-resolves.
- RLM preserved-tail copies retain preStreamRejected (the boundary hides
the original row; a marker-less copy would re-send the rejected
prompt).
- extractStagedAttachmentNotices only matches GENERATED notice blocks —
an <attached-files> example inside the user's own argument text is
restored by the argument rebuild and must not be duplicated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@codex review — wave-17 addressed: consent checked at the provider-dispatch boundary via a shared rejection closure with accepted-failure semantics and durable non-retryable marking; project-scope consent tracks content (inline + deduped refs) not just the invocation; compaction-model pricing gate; deferred routed telemetry across on-send compaction; preStreamRejected survives RLM tail copies; generated-only notice extraction. Head is 3b9ba9e. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3b9ba9ee03
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…re-verification
Wave-18 review findings:
- The consent gate now runs INSIDE AIService immediately before
streamManager.startStream (threaded as StreamMessageOptions.
preDispatchConsentGate): runtime init, model creation, memory
resolution, and request building were all unchecked revocation
windows. Failure follows the same cleanup as a failed stream start;
the session-side caller still converts it into an accepted pre-stream
failure.
- The gate also fires when the assembled REQUEST carries project-scope
snapshot rows from EARLIER turns (scanned where MuxMessage metadata is
still in hand): an untrusted workspace's history can hold a project
snapshot even when the current routed invocation is global with no
project refs.
- Late-gate rejections are durable and request-visible: a new
HistoryService.markMessagesPreStreamRejected stamps the accepted user
row and its snapshot rows (filterPreStreamRejectedRows keys on ROW
metadata — the sidecar abandon alone left the rejected turn
provider-eligible for the next ordinary send), belted by the existing
abandon marker.
- Every resumed dispatch re-verifies consent: routedProjectConsent is
carried in the in-memory resume state (final post-materialization
flag) and persisted in retrySendOptions (acceptance-time seed), and
resumeStream rechecks trust before replaying routed options — bounded
by the retry machinery's caps, and re-granting trust lets a later
attempt proceed.
- Compaction-deferred routed skills are attributed at dispatch time:
dispatchPendingFollowUp captures message_sent via the backend
TelemetryService when the routed follow-up actually streams (the
original send reported { queued: true } and recorded nothing).
- useModelClasses schedules an authoritative retry when a fetch fails
while the subscription is LIVE — no further notification is
guaranteed, so a transient IPC error no longer disables the editor
until an unrelated config change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@codex review — wave-18 addressed: preDispatchConsentGate inside AIService right before startStream, request-history project-snapshot scanning, durable row-level rejection stamps via HistoryService, consent re-verification on every resumed dispatch, dispatch-time telemetry for compaction-deferred routed skills, and live-subscription fetch retries in the editor hook. Head is 35282db. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 35282db0d9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ue drain Wave-19 review findings: - The consent gate rides TurnExecutionOptions into StreamManager. startStream and is invoked INSIDE the critical section (workspace mutex held, ensureStreamSafety and temp-dir setup done) immediately before the provider stream is constructed — nothing awaitable remains between the verdict and dispatch. The AIService-level invocation moved there (double invocation would double the rejection bookkeeping). - Resumed dispatches carry the gate to the same boundary: resumeStream threads a consent closure into streamWithHistory that fires on the persisted acceptance-time seed OR on the request scan — the replayed request carries the original turn's persisted snapshot rows, so history-carried and materialization-discovered project content is re-checked even when the pre-crash durable seed missed it. - Rejected-row stamping now includes the @file-mention snapshot, retries once on failure, and FAILS CLOSED: still-unstamped ids are quarantined in memory (request assembly filters them for the session) and startup recovery re-attempts the durable stamp when the abandon marker names a rejected row whose stamp is missing. - A dequeued send that resolves success with the turn back at IDLE and no stream-start (accepted pre-stream failure) now drains the queue — previously every later queued message was stranded until an unrelated event. - Busy-queued SKILL sends are attributed at dispatch via the shared backend message_sent capture (routed or unbound) — the renderer's only messageSent call was suppressed for them before routing was known. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@codex review — wave-19 addressed: the consent gate now executes inside StreamManager's stream-start critical section (nothing awaitable remains before provider dispatch), resumed dispatches carry the same gate with request scanning, rejected-row stamping is fail-closed with in-memory quarantine + recovery re-stamp and includes @file snapshots, accepted-no-stream outcomes drain the queue, and queued skill dispatches emit backend attribution. Head is 5b7babf. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5b7babf9e1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: 5b7babf9e1
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
…y filtering Wave-20 review findings: - The consent verdict now runs in prepareStep — the last awaited hook before EVERY step's provider request, inherited by fallback and retry recreations via the request config — so revocation during step preparation (media extraction, message rebuilding) or mid-turn stops the next request instead of riding the stream. The trust message constant moved to utils/sendMessageError.ts (StreamManager cannot import agentSession). - Untrusted-workspace HISTORICAL project snapshots are now EXCLUDED from routed requests (least privilege, mirroring the fresh-snapshot omission) instead of rejecting the turn: global/built-in skills are allowed to route in untrusted projects, and rejecting on rows the rejection cannot remove failed every later routed send deterministically. Rows kept under trust still arm the per-step gate. - Consent refusals on resumed dispatches are non-retryable: RetryManager treats "unknown" as retryable with no attempt cap, so the session would recheck the same revoked verdict forever; the refusal now persists the abandon and stops recovery until the user acts. - Startup quarantine repair runs regardless of the auto-retry preference (the hazard is the next MANUAL send) and restamps the whole rejected turn — the user row plus its contiguous skill/MCP/@file snapshot prefix — not just the user row. - The in-memory quarantine now also guards side-channel model calls: the edit-path abandoned-branch summarizer filters removed tail rows, and refine excludes quarantined rows via a workspaceService-wired lookup. - Queue attribution skips compaction-deferred dispatches ({ queued: true }): dispatchPendingFollowUp owns their attribution, and capturing at dequeue double-counted with a false model. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@codex review — wave-20 addressed: per-step consent in prepareStep (fallback/retry recreations included), untrusted historical snapshots filtered from routed requests instead of deterministic rejection, non-retryable consent refusals, preference-independent whole-turn quarantine repair, quarantine coverage for refine and edit summarization, and deferred-dispatch attribution dedup. Head is 3f74763. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f74763617
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: 3f74763617
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
…eline Wave-21 review findings: - tryModelFallbackAfterRefusal's rebuilt request now carries preDispatchConsentGate — the fallback attempt ships the same routed project-skill turn and previously had no verdict at its prepareStep. - Consent refusals surfacing through the generic stream error pipeline (a per-step prepareStep rejection is a plain Error there) are recognized by message: handleStreamFailureForAutoRetry never arms the retry manager for them, and updateStartupAutoRetryAbandonFromFailure preserves the pre_stream_rejected marker instead of clearing the repair key the rejection callback just persisted. - Startup quarantine repair reads the full active epoch instead of a 50-row tail: a turn's snapshot prefix has no count limit, and a bounded read stamped only the newest subset. - Refine's pre-publication recheck applies the same quarantine filter as its initial segment — an unfiltered recheck mismatched at the quarantined row and deterministically refused to publish after the model call was spent. - ChatInput defers attribution for EVERY queued skill send (explicit one-shot overrides included): the backend dequeue-dispatch capture owns it, and acceptance-time recording double-counted or attributed sends that were later canceled or rejected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@codex review — wave-21 addressed: fallback requests carry the per-step consent gate, consent refusals keep their non-retryable classification through the stream error pipeline, the quarantine repair walks the full epoch, refine's recheck applies the quarantine filter, and queued skill attribution is fully deferred to the backend dispatch capture. Head is 82ba4d1. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 82ba4d1aa0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…on lands A per-step consent rejection after an earlier step emitted text or completed tools leaves those parts in partial.json (persisted by the stream error path); the next send would commit them as an orphaned assistant row that the user-row rejection filter cannot remove — and that can break tool/message ordering. The terminal stream-error handler now recognizes the consent refusal and deletes the partial with the rejected turn, quarantining the would-be committed row id if the delete fails. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d58879e43f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
When deletePartial fails at rejection time, the assistant quarantine was memory-only and a restart lost it — the surviving partial.json would be committed as an unmarked assistant row on the next send. Startup repair (keyed on the pre_stream_rejected abandon marker, which any accepted send clears) now deletes a surviving partial before any request-build path can commit it, re-quarantining its id if the delete fails again; this runs even when the row stamp itself succeeded, since the two failures are independent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f9b4c8e80
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…rget read - A manual send can race the asynchronous startup recovery (getOrCreateSession exposes the session without awaiting it, and the send's PREPARING turn makes scheduleStartupAutoRetryIfNeeded defer): streamWithHistory now runs the marker-gated quarantine repair before reading history or committing partials, so the rejected turn's rows and surviving partial are excluded on the very first send after restart. - The edit path rechecks consent immediately before truncateAfterMessage, after the materialization and truncate-target awaits, using the widened flag (inline project refs discovered by the pre-truncation materialization included) — a rejection after truncation cannot restore the discarded tail. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: 5f9b4c8e80
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
The consolidation completion hook reloads the raw compaction epoch and sent every row to the dream model (which may use an explicit alternate provider) — bypassing the rejected-row exclusions request assembly applies. The harvest boundary now filters stamped preStreamRejected rows AND the session's in-memory quarantine (late-bound lookup through WorkspaceService, which is constructed after core services). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Skills can now be routed to user-defined model size classes so mechanical skills (wrap-up chores, formatting passes, routine repo tasks) don't consume frontier-model tokens. Classes map a name to a
model[+thinking]value (one-shot syntax) and are edited in Settings → Models → Model Classes; skills bind to a class via the spec-standard frontmattermetadata: model-class: smallor a localskillModelClassesconfig table. The class model applies to that invocation only — the workspace model is untouched. One-shot overrides also compose with skill invocations now (/haiku+0 /deep-review), and an explicit one-shot always beats class routing.Background
Models churn constantly, so per-skill bindings shouldn't name concrete models — they name a class (
large/medium/small), and only the class map names models. Updating one class re-routes every bound skill.model) and extends it to compose with skill slash invocations.ai.modelparsed but not consulted); this PR takes the same position for skills — a declared model preference should be honored — while keeping it strictly opt-in.metadatamap, which other harnesses ignore. Frontmatter bindings to a class the user never defined are deliberately inert, so skills shippingmetadata: model-classcan never break users who haven't opted in. The config table exists for routing skills the user doesn't own — and because the table is the user's own explicit intent, a dangling table entry (naming a class that was deleted) fails loudly instead of silently unrouting.Implementation
modelClassesandskillModelClassesrecords (schema, load normalization,saveConfigwhitelist,config.updateModelClassesroute). Maps are stored verbatim — entries this build can't parse are preserved, not dropped, so edits from an older/newer build never destroy classes they don't understand. Validity is judged lazily at send time by the resolver.src/common/utils/ai/skillModelClasses.ts): binding resolution as a discriminated union (unbound/unknown-class/invalid-value/resolved), plusisModelServableWithProvidersConfig(modelAvailability.ts) wrapping the routing layer'sisModelAvailablewith the same exported provider/gateway predicatesuseRoutingconsumes — so a model reachable only via a configured gateway (e.g. OpenRouter) correctly counts as available, route-priority membership is honored, and the editor warning cannot drift from the send-time gate.AgentSession.sendMessage): the override is resolved before the pricing gate, PDF-support preflight, and any history mutation, so those gates evaluate the model that will actually stream and a broken binding errors before persisting side effects. Routing is gated by a dedicatedskipSkillModelRoutingsend option (set by explicit one-shot composition and compaction retries) rather than overloadingskipAiSettingsPersistence. Bound-but-broken mappings (dangling table entry, invalid value, no configured route for the model) fail the send with an actionable error naming the fix and the one-shot bypass; unbound skills take a null fast-path and infrastructure failures (unreadable skill/config, providers state unavailable) fail open.ROUTED_SEND_COMPACTION_HEADROOM_PERCENT(10 points) of the routed model's window — headroom for the pending turn, while still far above the workspace threshold so a small-context class model can't trigger surprise compaction of a history the workspace model handles fine.large/medium/small— a shared vocabulary keeps skill frontmatter portable across machines), model + thinking selects per class, custom hand-edited classes preserved on save and listed read-only (unparseable raw values shown in a tooltip), and an inline "no configured route can serve this model" warning using the same predicate as the send-time check. Edits are disabled until config and routing state finish loading, so an early click can't clobber persisted classes; thinking suffixes carry across model swaps only when the target model's policy supports them.parseCommandWithSkillInvocationcomposes a leading one-shot with a skill invocation by re-runningparseCommandon the one-shot's message — registered commands and nested one-shots stay out of skill resolution, mirroring direct-invocation semantics exactly. Composed sends record the full command prefix (model /skill) in message metadata so transcript badges render what was actually typed. Numeric one-shot thinking is model-relative, so a thinking-only composed send (/+0 /skill) also passes the raw index (oneShotThinkingIndex) for the backend to re-resolve against the routed model's ladder —+0means the class model's lowest level, not the workspace model's. Compact-and-retry rebuilds re-derive the one-shot's model and thinking from the original text (withskipAiSettingsPersistence, so a re-dispatch never persists one-shot values as new workspace defaults), andprepareCompactionMessagekeeps carried one-shot fields from being clobbered by ambient stored options.requestedModel), so the pending-turn label and history consumers see the model that actually streams.Review-round hardening
Sixteen Codex review rounds tightened the edges (all threads resolved):
skipAiSettingsPersistence), and process relaunch (durablecompactionBaseOptionsinretrySendOptions, honored even in child task workspaces).ProviderModelFactory.resolveModelRouteboth apply model-aware OpenAI credential rules (Codex-OAuth-only serves the OAuth set; API keys attempt anything; custom openai-compatible providers shadowing theopenaiid are exempt).routedModel+ post-floorroutedThinkingLevel; persisted metadata re-stampsrequestedModel. Queued-send event attribution is documented as a follow-up (needs backend-side event capture).Validation
saveConfigwhitelist (including preservation of unknown classes), end-to-end AgentSession routing and error paths via the session harness (gate ordering,skipSkillModelRoutingexemption, thinking-only bindings, compaction follow-up model), composition parser cases, and editor UI behavior (clear preserves custom classes; load gating; warning states).bun test srcfailure set is identical tomain's on the same machine (pre-existing env-sensitive tests only).ModelsSectionstories now seed classes, including one pointing at an unconfigured provider to exercise the warning; row layout wraps at mobile widths) and in a packaged build used for daily work.Risks
The sensitive area is the insertion in
AgentSession.sendMessage. Scope is tightly bounded: only sends carryingagent-skillmetadata withoutskipSkillModelRoutingare considered, and workspaces with nomodelClasses/table binding hit an early return before any skill read — no behavior change for anyone who hasn't opted in. Compaction interplay (threshold on the routed model, compaction request and mid-stream forced compaction on the user's model, follow-up resume options) is covered by tests. One known asymmetry, documented at the helper: the shared servability predicate mirrors the routing layer's gateway/priority gates but not per-request policy checks, so an editor warning can under-report in exotic policy setups — the send-time error remains authoritative.🤖 Generated with Claude Code