From 37596ce31c5522262e117678796c9f27a28f76d8 Mon Sep 17 00:00:00 2001
From: Bryan Van Deusen
Date: Fri, 22 May 2026 21:03:25 -0400
Subject: [PATCH] remove(llm): retire think_enabled setting entirely
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Two-in-one cleanup motivated by the chat hang in dev 2026-05-22.
The crash root cause from the guarded-task traceback:
UnboundLocalError: cannot access local variable 'get_setting'
where it is not associated with a value
File generation_task.py:257, in run_generation
think = (await get_setting(user_id, 'think_enabled', 'false'))...
generation_task.py imports get_setting at module top, but a later
'if voice_mode: from ... import get_setting' block scopes it as a
function-local. When voice_mode=False the local import never runs,
but Python had already flagged get_setting as local for the entire
body — the think_enabled read at line 257 hit UnboundLocalError.
The line itself was dead-weight anyway. With the conversation+curator
architecture: chat ships tools=[] (think on a no-tools pass is pure
latency cost; nothing for the model to reason ABOUT in tool-call
terms), and the curator hardcodes think=False already. The user
setting was a holdover from before the architecture pivot. Removing
it entirely is cleaner than fixing the scoping bug to preserve a
toggle nobody should be using:
- generation_task.py: think hardcoded False. Removed the get_setting
call (which fixes the UnboundLocalError as a side effect).
- SettingsView.vue: dropped the Enable model thinking checkbox, the
thinkEnabled / savingThinkEnabled refs, the saveThinkEnabled
function, and the think_enabled load step.
- Migration 0050: DELETE FROM settings WHERE key='think_enabled'
to clean up any stored rows.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
.../0050_drop_think_enabled_setting.py | 27 ++++++++++++++
frontend/src/views/SettingsView.vue | 37 ++-----------------
.../services/generation_task.py | 18 +++++----
3 files changed, 41 insertions(+), 41 deletions(-)
create mode 100644 alembic/versions/0050_drop_think_enabled_setting.py
diff --git a/alembic/versions/0050_drop_think_enabled_setting.py b/alembic/versions/0050_drop_think_enabled_setting.py
new file mode 100644
index 0000000..9b58d0b
--- /dev/null
+++ b/alembic/versions/0050_drop_think_enabled_setting.py
@@ -0,0 +1,27 @@
+"""drop stored think_enabled rows — setting was removed
+
+Revision ID: 0050
+Revises: 0049
+Create Date: 2026-05-23
+
+The `think_enabled` user setting was retired with the chat+curator
+architecture: chat has tools=[] and curator hardcodes think=False, so
+the toggle was dead weight. Any rows already in `settings` for that
+key are now unread by the app; this migration clears them.
+"""
+from alembic import op
+
+
+revision = "0050"
+down_revision = "0049"
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ op.execute("DELETE FROM settings WHERE key = 'think_enabled'")
+
+
+def downgrade() -> None:
+ # No-op. The setting is dead; restoring NULL rows wouldn't help.
+ pass
diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue
index 37e23c5..b743e94 100644
--- a/frontend/src/views/SettingsView.vue
+++ b/frontend/src/views/SettingsView.vue
@@ -20,8 +20,6 @@ const savingTimezone = ref(false);
const timezoneSaved = ref(false);
const autoConsolidateTasks = ref(true);
const savingAutoConsolidate = ref(false);
-const thinkEnabled = ref(false);
-const savingThinkEnabled = ref(false);
async function saveAutoConsolidate() {
savingAutoConsolidate.value = true;
@@ -36,18 +34,10 @@ async function saveAutoConsolidate() {
}
}
-async function saveThinkEnabled() {
- savingThinkEnabled.value = true;
- try {
- await apiPut('/api/settings', {
- think_enabled: thinkEnabled.value ? "true" : "false",
- });
- } catch {
- toastStore.show('Failed to save setting', 'error');
- } finally {
- savingThinkEnabled.value = false;
- }
-}
+// think_enabled setting removed 2026-05-23. The chat+curator architecture
+// has tools=[] on the chat model; think on a no-tools conversational pass
+// is pure latency cost. See generation_task.py:run_generation comment for
+// the full reasoning.
function detectTimezone() {
userTimezone.value = Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -826,9 +816,6 @@ onMounted(async () => {
userTimezone.value = allSettings.user_timezone ?? "";
// Default true if unset; explicit "false" disables auto-consolidation.
autoConsolidateTasks.value = (allSettings.auto_consolidate_tasks ?? "true") !== "false";
- // Default false: think mode is opt-in. Bench showed it costs 1-2 min/turn
- // for unclear benefit; qwen3 users can re-enable if tool calls regress.
- thinkEnabled.value = (allSettings.think_enabled ?? "false") === "true";
chatRetentionDays.value = allSettings.chat_retention_days !== undefined
? Number(allSettings.chat_retention_days)
: 90;
@@ -1569,22 +1556,6 @@ function formatUserDate(iso: string): string {
-
-
-
- When on, the chat model runs in reasoning mode before responding.
- Default off — bench data showed thinking adds 1–2 minutes per turn for unclear quality benefit.
- Turn on for qwen3-family models if tool-call reliability drops without it.
-