32 KiB
status, system, requirements, created, owners
| status | system | requirements | created | owners | ||||
|---|---|---|---|---|---|---|---|---|
| current | agents |
|
2026-08-23 |
|
No Silent Model Fallback System Design Part 1
Purpose and boundaries
This design defines executor-authoritative model fallback, including safe resolution from a bare requested model to one advertised bracketed variation.
Requirement mapping
| Requirement | Design section |
|---|---|
REQ-AGENTS-NO-SILENT-MODEL-FALLBACK-001 |
Migrated source detail |
REQ-AGENTS-NO-SILENT-MODEL-FALLBACK-002 |
Unique model-variation resolution |
REQ-AGENTS-NO-SILENT-MODEL-FALLBACK-003 |
Profile selection and warning placement |
Migrated source detail
Status: implemented (executor-authority amendment 2026-08-15)
Date: 2026-08-04
Slug: no-silent-model-fallback
Problem
Agents such as OMP are configured with models from multiple providers (e.g. Claude + GPT). When a provider's login/auth expires (e.g. Claude), its models become unavailable. Today Kandev silently compensates in several places:
- Session start (agentctl runtime) —
SessionManager.InitializeSessionapplies the profile's start model via ACPSetModelas best-effort: on failure it logs a warning and continues the session on the provider default model.SetModelitself fails fast when the model is not in the agent's advertised model list (validateAvailableModel), so the failure is known but ignored. - Profile reconciler (boot) —
healProfileoverwrites a profile's start model with the probe'sCurrentModelIDwhen the configured model is no longer advertised. The user's explicit model choice is silently replaced. - Office post-start fallback — when a launched run fails mid-session
with a fallback-allowed error (
auth_required,model_unavailable, …),HandlePostStartFailuresilently re-dispatches the run to the next provider in the workspace routing order. - Frontend model lists — model pickers have no concept of an
unavailable ("gone") model;
clearStaleActiveModelsilently drops an active model that disappeared from the ACP list, so the session continues on whatever the agent picks.
Bottom line: the host and executor can advertise different model catalogs. Kandev must keep the task operational and explain the effective model.
Goals
- A configured model that the executor does not advertise is never applied. The agent continues with its current or default model.
- Every default or explicit fallback creates a persisted warning in task chat. The warning survives reload and shows the effective model when known.
- A Claude model that is valid for the user's account can start in a cold, isolated executor even when the initial Claude ACP model list omits it.
- "Gone" models render greyed out and unselectable in every model picker, while remaining visible so the user can see what was configured.
- Agent profiles keep their previously configured start model even when gone (shown red + unselectable in the editor) instead of having it auto-healed.
- Profiles remain selectable when the host probe does not advertise the saved model. The host probe is an editing hint, not an executor launch gate.
- A new optional per-profile agent fallback model allows an explicit,
single-model automatic switch when the start model becomes unavailable at
session start. Kandev applies it only when the executor advertises it.
Office post-start routing is unchanged and remains governed
by the workspace routing configuration (see ADR
2026-08-08-provider-neutral-agent-error-recovery.md): the profile's model policy is the owner of the session-start decision, not of office post-start provider fallback. - A new explicit per-profile toggle "Fallback automatically to next model" restores the legacy automatic-fallback behavior (session start best-effort + office routing re-dispatch). A failed apply continues with the agent default and creates a persisted warning. Enabling the toggle disables the optional fallback-model controls without clearing their saved value (the two are mutually exclusive opt-ins).
- Agent-profile editors group both fallback choices in a collapsed Fallback settings disclosure. Expanding it shows the two choices side by side on desktop and as one vertical flow on phone-sized screens. Each choice keeps a short visible explanation and adds contextual help that works by hover or keyboard focus with a fine pointer and by tap on a coarse pointer.
Non-Goals
- Changing the workspace provider-routing configuration model (tiers, provider order, execution profiles) itself.
- Provider-level auth flows (login/refresh UI) — only the consequence of auth expiry (unavailable models) is addressed.
- Requiring equal model catalogs on the Kandev host and executor.
- Requiring portable configuration copying before a task can start.
- Mid-turn model switching on the live ACP session (retry the current turn on a different model) — fallback applies to the next attempt/launch, not to an in-flight turn.
Definitions
- Available models: the model IDs an agent currently advertises —
hostutility.AgentCapabilities.Models(probe cache, surfaced viaGET /api/v1/agents/availableandGET /api/v1/agent-models/:agentName) or the ACP session'smodels_updatedlist. - Gone model: a model ID that is configured (profile start model, active
session model, fallback model) but absent from the currently advertised
list. Deterministic on the frontend:
configured ∉ advertised. - Default-on-mismatch mode: profile has
auto_fallback = falseand nofallback_model. If the executor omits the start model, Kandev skips model selection and the agent uses its default. Kandev persists a warning. - Fallback-model mode: profile has
auto_fallback = falseand a non-emptyfallback_model. The only permitted automatic switch is to that single model when the executor advertises it. Otherwise, the agent uses its default and Kandev persists a warning. - Auto-fallback mode: profile has
auto_fallback = true. Legacy behavior (session-start best-effort; office routing re-dispatch to next candidate). Every session-start deviation creates a warning.fallback_modelis ignored. - Cold isolated executor: a non-standalone executor that does not share the host probe's Claude settings or model cache. Docker, remote Docker, Sprites, and SSH executors can have this property.
Behavior Matrix
Per agent profile, one of three modes (precedence: auto_fallback wins
over fallback_model):
| Scenario | Default-on-mismatch | Fallback-model | Auto-fallback |
|---|---|---|---|
| Session start, start model not advertised | Do not call SetModel. Continue on the agent default and persist a warning. |
If the fallback is advertised, apply it and persist a warning. Otherwise, use the agent default and persist a warning. | Do not call SetModel. Continue on the agent default and persist a warning. |
| Session start, advertised start model fails to apply | Fail explicitly. | Fail explicitly. | Continue on the agent default and persist a warning. |
| Session start, model selection unsupported | Continue on the agent default and persist a warning. | Same | Same |
| Mid-session model/auth failure (office run, post-start) | Unchanged ADR behavior: office re-dispatches via the workspace routing chain (routingerr.Decide(ContextOffice); availability codes → DecisionFallback). The profile's model policy does not gate office fallback — the workspace routing configuration is the office authorization owner. |
Same as default-on-mismatch: fallback_model is a session-start policy, not an office routing input. |
Legacy: re-dispatch to next candidate in the provider order (unchanged). |
| Boot reconciliation | Never overwrite a gone start model (keep it; UI shows it red). Same for a gone fallback_model. |
Same | Same (reconciler is mode-independent). |
| New-task / new-agent profile picker | Profile selectable without a host model advisory. | Same. | Same. |
| Model picker (profile editor, session toolbar) | Gone models greyed out, unselectable, visible. | Same. | Same. |
SetModel failures that mean "this agent does not support model selection"
(JSON-RPC -32601, sessionmodel.MethodNone / IsMethodNotFound) do not stop
the launch. The agent uses its default and Kandev persists a warning.
The executor ACP catalog is authoritative for launch. The host probe remains an editing hint and does not block profile selection.
Unique model-variation resolution
Matching contract
ACP model IDs remain opaque and case-sensitive. Kandev recognizes only this structural relationship:
<requested>[<variant>]
The requested ID must contain no [ or ] character. The advertised ID must
start with the complete requested ID followed by [, end with ], and contain
a non-empty variant with no bracket character. Kandev does not trim, parse, or
rank the variant.
Examples:
| Requested | Advertised candidates | Result |
|---|---|---|
opus |
opus[1m] |
Apply opus[1m]. |
opus |
opus[270k], opus[1m, fast] |
Do not infer. |
opus[1m] |
opus[270k] |
Do not infer. |
opus |
myopus[1m], opus-extra[1m] |
Do not infer. |
opus |
opus[], opus[1m][fast] |
Do not infer. |
Duplicate catalog rows with the same model ID count as one distinct candidate. This rule keeps the outcome stable when an agent repeats a catalog entry.
Resolution order
For profiles with auto_fallback = false, the lifecycle applies this order
after ACP initialization:
- Apply the exact requested model when advertised.
- Apply the configured explicit fallback when advertised.
- Apply the requested model's unique advertised variation when one exists.
- Otherwise, make no model-selection call and keep the provider current or default model.
An unadvertised explicit fallback does not block step 3. More than one distinct variation blocks inference even when one candidate appears first or is the provider current model.
When auto_fallback = true, the legacy path takes precedence when the
requested model is absent: Kandev ignores the explicit fallback and does not
infer a variation. It makes no model-selection call and continues on the
provider current or default model with a warning. Apply errors for an
advertised model remain best-effort in that mode.
applyStartModelPolicy remains the single runtime owner. A pure helper returns
the unique candidate or no candidate. Initial launch, context reset, and
workspace rebind continue to call the same policy.
Decision and warning contract
The typed decision adds the outcome unique_variation and the stable reason
unique_variation_applied. EffectiveModel contains the advertised variation.
FallbackModel remains reserved for a configured explicit fallback and is not
overloaded with an inferred value.
Kandev calls SetModel only with the candidate's exact advertised ID. A failed
call follows the existing advertised-model rule: fail the operation unless
legacy auto_fallback permits provider-default continuation. A
method-not-supported response also follows the current provider-default path.
The persisted model_selection_warning event uses the new reason and existing
requested and effective model fields. The profile remains unchanged. The
decision ID continues to include the requested model, reason, and effective
model, so the warning stays idempotent for one session decision.
Host advisory and mobile behavior
The frontend uses a small pure helper with the same case-sensitive matching table. This calculation is advisory because the host catalog can differ from the selected executor catalog.
When the host advertises one variation, the task profile option remains selectable without a host model advisory. The profile editor keeps the saved bare ID visible and describes the unique host variation instead of reporting that the model is simply gone. Selecting the advertised variation remains an explicit way to update the profile.
When the host advertises zero or multiple variations, the profile editor's missing-model presentation remains. The runtime can still reach a different decision from the selected executor catalog.
The task-create selector has no host model tooltip or help drawer. The profile editor uses its existing responsive selector. The durable chat warning uses the existing status-message layout on desktop and mobile. This change adds no nested scroll area or hover-only information.
Tests and operational evidence
Backend table tests cover exact precedence, explicit-fallback precedence, one variation, duplicate rows, multiple variations, bracketed requests, case differences, malformed IDs, method-not-supported, and apply errors. Existing launch, reset, rebind, warning persistence, and reload tests cover the new decision outcome.
Frontend unit tests mirror the matching table and cover the task picker, profile editor, and localized status warning. Existing desktop and mobile model mismatch E2E flows add the unique and ambiguous catalog cases.
Structured lifecycle logs record the requested model, effective model, outcome, and distinct candidate count. They do not record credentials or configuration file contents.
No database migration is required.
Related decisions
Backend Changes
1. Agent profile schema (new columns)
agent_profiles gains:
fallback_model TEXT NOT NULL DEFAULT ''— optional single fallback model ID (ACP model ID, same vocabulary asmodel).auto_fallback INTEGER NOT NULL DEFAULT 0— explicit opt-in to legacy automatic fallback.
Migration follows the existing r.migrate.Apply("agent_profiles.<col>", "ALTER TABLE ... ADD COLUMN ...") pattern in
apps/backend/internal/agent/settings/store/sqlite.go. Wire through:
models.AgentProfile(internal/agent/settings/models/models.go)- store scan/insert paths (
settings/store/sqlite.go) dto.AgentProfileDTO+ create/update request structs (settings/dto/dto.go,settings/controller/profile_crud.go)- frontend
AgentProfiletype,normalizeAgentProfile/toAgentProfilePayload(apps/web/lib/types/agent-profile.ts,apps/web/lib/api/domains/agent-profile-normalize.ts)
Validation: fallback_model may be set independently; when auto_fallback
is enabled the UI hides/disables the field and the backend ignores
fallback_model at runtime (precedence rule). No cross-field rejection —
saving both is allowed; runtime precedence is auto_fallback.
2. Reconciler stops healing gone models
healProfile in internal/agent/settings/controller/reconciler.go currently
replaces a gone p.Model with caps.CurrentModelID. Change: keep the
user-configured model when it is gone (log an info line; do not overwrite).
The p.Model == "" seed-default branch is unchanged. Apply the same
keep-when-gone rule to fallback_model (no auto-heal; UI surfaces it).
Mode healing is unchanged (modes are not part of this feature).
3. Session start: executor-authoritative model application
In internal/agent/runtime/lifecycle/session.go, the start-model policy uses
the executor session state after InitializeSession.
The policy returns a typed model decision with these values:
- Requested model.
- Advertised fallback model, when one is applied.
- Effective model, when the agent reports it.
- Outcome and warning reason.
- Whether Kandev called
SetModel.
The policy applies this order:
- If the start model is advertised, call
SetModel(start_model). - If the start model is absent and the explicit fallback is advertised, call
SetModel(fallback_model). - Otherwise, do not call
SetModeland continue with the agent default.
An empty advertised list follows step 3.
It does not authorize a speculative SetModel request.
If an advertised start or fallback model fails to apply, fail the launch. Transport and protocol errors remain explicit.
If auto_fallback is enabled, an apply error remains best-effort.
The launch continues with the agent default and persists a warning.
If sessionmodel.IsMethodNotFound(err) is true, continue with the agent default.
Persist a warning because the profile requested a model that Kandev could not apply.
The start-model policy owns every model-selection attempt. Later profile or configuration layers must not repeat a handled attempt.
The same policy applies after a context reset. A reset cannot send a model that the new executor session does not advertise.
3a. Persisted model-selection warning
If Kandev does not apply the requested model, it emits a provider-neutral
session_model_selection_warning event.
The orchestrator persists one status message for the session-start decision.
The message uses metadata.variant = "warning" and
metadata.kind = "model_selection_warning".
The metadata contains the reason, requested model, effective model, fallback model, agent ID, executor type, and executor profile ID.
The message content is structured and localized by the frontend status renderer.
If the agent reports no effective model, the UI shows
provider default, model not reported.
The warning includes this remediation guidance:
- Inspect the executor credentials.
- Inspect copied agent configuration.
- Inspect the agent version in the executor.
Message persistence is best-effort and does not stop the agent launch. Persistence errors are logged without model data from configuration files.
The persistence path uses an idempotency key for the session-start decision. Reconnect, replay, and browser reload do not create duplicate warning messages.
The existing ephemeral session.model_fallback signal becomes a compatibility
projection of the provider-neutral decision event.
3b. Claude ACP model exposure in cold executors
The host probe can advertise a Claude model from its warm settings or model cache. A cold isolated executor can start with only the bridge's baseline model list. This difference does not prove that the configured model is unavailable to the user's account.
Before the initial Claude ACP process starts in a non-standalone executor, the
lifecycle exposes the effective start model through Claude's documented launch
environment. The effective model is the request model override when present.
Otherwise, it is the profile start model. The lifecycle exposes an explicit
profile fallback as a selectable custom model when auto_fallback is false.
During workspace recovery, a persisted session runtime model is treated as the
selected model for launch-environment derivation and takes precedence over the
profile start model.
Provider launch environment values use fill-missing precedence. Request,
executor, and profile environment values remain authoritative. The lifecycle
does not replace an existing ANTHROPIC_MODEL or
ANTHROPIC_CUSTOM_MODEL_OPTION value.
This behavior is an optional agent capability. Other agents receive no Claude environment values. A standalone Claude process keeps the current host behavior because it shares the settings and cache used by the probe.
SSH keeps its credential allowlist unchanged, but projects these non-secret Claude model values into the remote agent controller so its child process sees the same launch environment. Recovery fails before environment construction if the execution profile cannot be resolved, rather than silently dropping its model and environment values.
Kandev does not relax model validation after the ACP session starts. If Claude still omits the model, the executor-authoritative policy uses the default and warns.
Kandev does not copy the bridge private cache.
The separate portable-configuration feature can copy settings.json after explicit user selection.
4. Office post-start failure gating (unchanged, ADR-governed)
HandlePostStartFailure in
internal/office/scheduler/routing_lifecycle.go is not modified by this
feature. Office post-start fallback authorization stays with the workspace
routing configuration and provider chain
(routingerr.Decide(ContextOffice)), per ADR
2026-08-08-provider-neutral-agent-error-recovery.md. Rationale: the
feature's model policy is owned by the session-start decision
(execution.AgentProfileID); the office post-start path sees the stable
office identity (run.AgentProfileID), and the two can disagree. Reading
agent.FallbackModel / agent.AutoFallback there would create a second,
conflicting policy owner. Office runs still get the session-start guarantee.
Kandev does not send an unadvertised model, and it persists a warning.
Mid-session Office failures keep the workspace-configured routing behavior.
Terminal office failures (for example after max attempts) surface an
actionable "provider/model unavailable — change the model" hint: map
model_unavailable / auth_required / missing_credentials /
subscription_required codes onto the failure message via
routingerr.ModelUnavailableMessage when composing it.
5. Error message mapping
Add a small helper (backend) that renders an actionable message for
model/auth failure codes, e.g. "Model unavailable: the configured model <id> is no longer available. Change the model in the agent profile." Used
by the session-start failure and the office terminal-failure path so both
chat and run detail show the request-to-change-model copy. The frontend may
additionally map the same codes to a banner (see frontend).
Frontend Changes
6. Model pickers: disabled ("gone") support
apps/web/components/model-config-selector.tsx:
ModelSelectorOptiongainsdisabled?: booleananddisabledReason?: string.ModelRowrenders disabled options greyed (opacity-40,cursor-not-allowed), with the reason in a tooltip, andonSelectis guarded (!option.disabled). Follow the existing disabled pattern fromapps/web/components/combobox.tsx(separate visual treatment + tooltip).
Session toolbar picker (apps/web/components/task/model-selector.tsx):
clearStaleActiveModelinapps/web/lib/ws/handlers/session-models.tsstops clearing the active model when it disappears from the ACP list. Instead the active model is kept and marked gone: the picker shows it greyed out with a reason ("model no longer available — select a new model"), so the user is explicitly asked to change it.buildModelOptions/resolveAvailableModelsmark configured-but-absent models (profile model, active model) asdisabled.
7. Profile editor rows
apps/web/components/settings/profile-form-fields.tsx (CapabilitiesRow /
ModelPicker):
- Start model: when
profile.modelis not in the current model list, keep it as the current value but render it red (text-destructive) + disabled with a reason ("no longer available"). - Agent fallback row (new, under Start model): optional select of the
same model list bound to
profile.fallback_model. It remains visible but is disabled whenauto_fallbackis ON, preserving the configured value while making the mutual exclusion understandable. It uses the same gone-red/disabled treatment if the fallback model itself is gone. - "Fallback automatically to next model" toggle (new row): a switch
bound to
profile.auto_fallback. When ON, the fallback-model controls are disabled. Helper text explains the semantics. - Fallback settings disclosure: both choices sit inside a semantic collapsible section that is closed on initial render. Its closed header summarizes the effective mode (executor default, automatic, or the configured explicit fallback) so saved state and hidden dirty state remain discoverable. The trigger is keyboard-operable, reports expanded state, and has a touch target of at least 44px.
- Responsive layout and help: the expanded choices use two equal columns at the desktop breakpoint and one column below it. Each label has an info-icon button. Fine pointers expose localized help through a tooltip on hover and focus; coarse pointers open the same help in an inset bottom drawer. The visible helper sentence remains in the option card, so the icon is supplementary rather than the only explanation.
- The lighter editor
apps/web/components/agent/cli-profile-editor.tsx(ModelModeFields) gets the same disclosure, layout, help, and mutual exclusion behavior for parity.
All new copy is externalized via t() into the settings i18n namespace
(apps/web/src/locales/{en,pseudo,pt-pt,zh-cn}/settings.json) — the i18n ratchet
judges added lines even in unmigrated files.
8. Profile selection and warning placement
apps/web/lib/state/slices/settings/types.ts — AgentProfileOption gains
model, fallbackModel, autoFallback (populated in
toAgentProfileOption).
apps/web/components/task-create-dialog-options.tsx
(useAgentProfileOptions) derives profile labels and eligibility without
comparing the saved model with the host model catalog. Remove
advertisedModelIDs, ModelProbeWarning, and ModelProbeWarningIndicator from
this module, plus imports used only by those helpers. The hook no longer needs
useAvailableAgents solely to produce model advisories.
Both renderLabel and renderTriggerLabel use the same profile presentation.
Keep the option interface compatible with its consumers. Preserve profile
eligibility, recent-use ordering, names, logos, and CLI passthrough indicators.
Keep getCapabilityWarning and its authentication, installation, and probe
failure states. Those states describe agent health rather than a model mismatch.
Consumers include task creation, new subtasks, new sessions, quick chat, automation configuration, and Office setup. All receive this behavior from the shared hook. Do not add consumer-specific warning suppression flags.
Retain profile model/configuration fields and the editor's
findUniqueModelVariation. After checking references, remove these unused
selector keys from every locale:
profileStartModelNotAdvertisedOnHost and
profileStartModelUniqueVariationOnHost.
apps/web/app/office/setup/agent-profile-setup-controls.tsx
(useSelectableProfileOptions) retains its Office eligibility filtering around
the shared profile options.
Desktop and mobile composition
Keep the touch-usable combobox and shared selection logic. A row tap completes the temporary profile choice without another help drawer or an empty hit area. Preserve keyboard selection, focus return, picker-owned scrolling, and safe-area constraints. Assert no document horizontal overflow.
Use e2e/tests/settings/mobile-no-silent-model-fallback.spec.ts as the shipped
flow and components/task/mobile/mobile-picker-sheet.tsx as the curated
temporary-choice precedent. No picker-shell replacement is needed.
Rationale and compatibility
A host observation cannot establish the executor outcome. A neutral icon or
better ID comparison would still report valid catalog differences during
selection. Keep the existing executor-authority ADR and runtime behavior.
This change needs no resolver, cache, schema, or new ADR. Explain warning
placement in docs/public/agents-and-profiles.md when implementation ships.
The persisted model_selection_warning chat message renders through
apps/web/components/task/chat/messages/status-message.tsx.
The renderer uses localized copy and structured metadata.
The message appears in the normal chat flow on desktop and mobile. It does not require a new drawer or a hover-only control.
9. i18n
New keys in apps/web/src/locales/{en,pseudo}/settings.json (camelCase,
settings: namespace), cover the gone-model hint and fallback controls.
New keys in the chat namespace cover the persisted model-selection warning. No added or edited line contains hardcoded user copy.
Tests
Backend (Go, *_test.go beside source):
- Store:
fallback_model/auto_fallbackround-trip + migration. - Reconciler: gone start model is kept, not overwritten; empty model still seeded; gone fallback_model kept.
- Runtime session start: an unadvertised start model causes no
SetModelcall. The session continues with the reported current or default model. - Runtime session start: an advertised explicit fallback is applied. An unadvertised fallback causes no call and the agent default remains active.
- Runtime session start: an empty catalog and method-not-supported both continue with the agent default and create a model-selection warning.
- Runtime session start: an advertised model apply error fails explicitly. Auto-fallback keeps best-effort behavior and creates a warning.
- Runtime session start: each decision produces at most one model-selection attempt with no profile-layer retry.
- Runtime launch environment: a non-standalone Claude launch exposes the effective start model before ACP initialization. It exposes an explicit fallback only when auto fallback is off. Request, executor, and profile environment values take precedence. Standalone Claude launches and other agents receive no new environment values.
- Office post-start: unchanged — availability failures requeue via the workspace routing chain regardless of the profile's fallback settings (regression test pins that the profile policy does not gate office).
- Model-decision event and warning metadata tests.
- Orchestrator persistence tests for message type, warning metadata, idempotency, reload hydration, and persistence errors.
Frontend (Vitest, *.test.ts(x)):
model-config-selector: disabled option not selectable; greyed class.session-modelsWS handler: stale active model is kept (not cleared).useAgentProfileOptions: every otherwise eligible host-mismatch profile remains selectable with no model advisory in either label. Cover canonical IDs, legacy bracketed IDs, unique and multiple variations, and empty catalogs.- Capability health warnings, recent-use ordering, disabled-profile filtering, and saved profile values remain unchanged by selector rendering.
- Profile editor: gone start model renders red + disabled; auto-fallback keeps the explicit fallback choice visible but disables its controls.
- Profile editors: fallback settings start collapsed; expanding exposes both choices, auto-fallback disables rather than removes the explicit fallback, and desktop tooltip help is available through pointer and keyboard focus.
agent-profile-normalize: new fields round-trip.- Status message: structured model-selection metadata renders localized requested, effective, agent, executor, and remediation text.
E2E (Playwright, apps/web/e2e):
- Mock backend (
KANDEV_E2E_MOCK=true): create a profile whose start model is not in the host catalog. Make sure that the task-create picker keeps the profile selectable without a host model warning in the option or selected label. Select the profile with keyboard on desktop and a row tap on mobile. - Launch a profile whose executor advertises its exact model while the host omits it. Verify the selected model, no model-selection warning, and unchanged profile values after reload.
- Launch with an executor catalog that omits the profile model. Make sure that no model-selection call occurs, the task continues, and chat shows one warning.
- Reload the task page. Make sure that the warning remains in chat.
- Desktop profile settings: the disclosure starts closed, expands to two horizontally aligned option columns, summarizes the current fallback mode, and exposes each info explanation on hover/focus.
- Mobile profile settings: tapping the same disclosure reveals one stacked flow; tapping either 44px info target opens the localized help drawer without horizontal document overflow.
Persistence & Migration
agent_profiles.fallback_model TEXT NOT NULL DEFAULT ''agent_profiles.auto_fallback INTEGER NOT NULL DEFAULT 0
Existing rows use auto_fallback = 0 and no fallback model.
This state means default-on-mismatch behavior.
If the executor omits the saved model, the agent uses its default and Kandev persists a warning. The saved profile model does not change.