Summary
Model selection, reasoning effort, speed, and permission mode in the AI panel are not persisted. They live only in component state and reset back to the provider-reported default every time the panel re-initializes (e.g. on app return from background, on session disconnect, on sessionState === "ended" | "expired").
On iOS this is very visible: pick a non-default model (anything other than the GPT-5.5 default), send a message, leave the app, return — and the selector has snapped back to GPT-5.5.
Repro
- Open the app, connect a CLI session.
- In the AI panel, change model to something other than the backend's default (e.g. for Codex pick a non-default model), set reasoning to
medium, set permission mode to full-access.
- Send a message — it sends with the chosen options as expected.
- Background the app for a moment and reopen (or disconnect/reconnect the session).
- Reopen the panel: model has reverted to the provider default; reasoning/speed/permission have reverted to their initial constants.
Expected
- Once the user has picked model / reasoning / speed / permission, those choices stick — both within an ongoing chat and across app launches — until the user changes them again.
- Falling back to the provider default should only happen the first time a user touches the backend, or when the previously-selected value is no longer in the provider's supported list.
Where it happens
All state is plain useState (no persistence):
app/plugins/core/ai/Panel.tsx ~ L2715–L2729
const [selectedModelByBackend, setSelectedModelByBackend] = useState<Record<AiBackend, string>>({ opencode: "", codex: "" });
const [codexReasoningEffort, setCodexReasoningEffort] = useState(..."medium");
const [codexSpeed, setCodexSpeed] = useState("default");
const [codexPermissionMode, setCodexPermissionMode] = useState(..."default");
The init effect re-fetches providers and unconditionally overwrites the selection to the backend default, at ~ L3308–L3345:
setSelectedModelByBackend((prev) => ({
...prev,
[backend]: models.length > 0 ? (defaultModelId || models[0].id) : "",
}));
isInitialized is reset on disconnect / session end at ~ L3395–L3400, which causes init to run again and clobber the selection on return.
Only AI_DETAILED_VIEW_STORAGE_KEY is persisted via AsyncStorage today (Panel.tsx L75, L3556, L3568). Nothing else.
Proposed fix (minimal)
Persist the four pieces of selection state via @react-native-async-storage/async-storage (already imported) using keys like:
ai-selected-model:<backend>
ai-selected-agent:<backend>
ai-codex-reasoning
ai-codex-speed
ai-codex-permission
Then:
- Hydrate them on mount before/alongside provider fetch.
- Write on every change.
- In the provider-fetch block (L3342), prefer the persisted value if it still exists in
models, else fall back to defaultModelId || models[0].id.
- The existing validators at L2900–L2913 already snap reasoning/speed back to a valid value if a persisted value is no longer supported, so no extra work needed there.
This is a small, contained change inside Panel.tsx. Happy to send a PR if a maintainer can confirm direction.
Scope / non-goals
- Not adding a settings pane in this issue (that's a follow-up —
app/settings/ai.tsx currently only exposes aiFontSize).
- Not changing the picker UI.
- Not changing defaults for first-time users.
Environment
- Reproduced on iPhone app build (latest from App Store as of 2026-05-18).
- Source inspected at
lunel-dev/lunel@main.
Related
Summary
Model selection, reasoning effort, speed, and permission mode in the AI panel are not persisted. They live only in component state and reset back to the provider-reported default every time the panel re-initializes (e.g. on app return from background, on session disconnect, on
sessionState === "ended" | "expired").On iOS this is very visible: pick a non-default model (anything other than the GPT-5.5 default), send a message, leave the app, return — and the selector has snapped back to GPT-5.5.
Repro
medium, set permission mode tofull-access.Expected
Where it happens
All state is plain
useState(no persistence):app/plugins/core/ai/Panel.tsx~ L2715–L2729The init effect re-fetches providers and unconditionally overwrites the selection to the backend default, at ~ L3308–L3345:
isInitializedis reset on disconnect / session end at ~ L3395–L3400, which causes init to run again and clobber the selection on return.Only
AI_DETAILED_VIEW_STORAGE_KEYis persisted via AsyncStorage today (Panel.tsx L75, L3556, L3568). Nothing else.Proposed fix (minimal)
Persist the four pieces of selection state via
@react-native-async-storage/async-storage(already imported) using keys like:ai-selected-model:<backend>ai-selected-agent:<backend>ai-codex-reasoningai-codex-speedai-codex-permissionThen:
models, else fall back todefaultModelId || models[0].id.This is a small, contained change inside
Panel.tsx. Happy to send a PR if a maintainer can confirm direction.Scope / non-goals
app/settings/ai.tsxcurrently only exposesaiFontSize).Environment
lunel-dev/lunel@main.Related
Panel.tsxto add Pi backend + in-picker search; does not persist selection.