diff --git a/routes/auth_routes.py b/routes/auth_routes.py
index 5c7a4e04a..c8db2dbaf 100644
--- a/routes/auth_routes.py
+++ b/routes/auth_routes.py
@@ -656,6 +656,10 @@ def setup_auth_routes(auth_manager: AuthManager) -> APIRouter:
"agent_max_rounds": (1, 200),
"agent_max_tool_calls": (0, 1000), # 0 = unlimited
}
+ _CHOICE_VALUES = {
+ "default_reasoning_effort": {"", "auto", "off", "on", "none", "minimal", "low", "medium", "high", "xhigh"},
+ "default_verbosity": {"", "auto", "low", "medium", "high"},
+ }
for key in DEFAULT_SETTINGS:
if key not in body:
continue
@@ -667,6 +671,14 @@ def setup_auth_routes(auth_manager: AuthManager) -> APIRouter:
except (TypeError, ValueError):
raise HTTPException(400, f"{key} must be an integer")
val = max(lo, min(val, hi))
+ if key in _CHOICE_VALUES:
+ val = str(val or "").strip().lower().replace("-", "_")
+ if val == "x_high":
+ val = "xhigh"
+ if val in ("auto", "default"):
+ val = ""
+ if val not in _CHOICE_VALUES[key]:
+ raise HTTPException(400, f"Unsupported value for {key}")
current[key] = val
_save_settings(current)
return current
diff --git a/routes/model_routes.py b/routes/model_routes.py
index 600150a66..d6472812a 100644
--- a/routes/model_routes.py
+++ b/routes/model_routes.py
@@ -2438,6 +2438,8 @@ def setup_model_routes(model_discovery):
ep_id = (_user_prefs.get("default_endpoint_id") or "").strip()
model = (_user_prefs.get("default_model") or "").strip()
_fallbacks = _user_prefs.get("default_model_fallbacks") or []
+ default_reasoning_effort = (_user_prefs.get("default_reasoning_effort") or "").strip()
+ default_verbosity = (_user_prefs.get("default_verbosity") or "").strip()
# If user has no personal default, fall back to global default
# But only based on the "share_defaults_with_users" flag
# (only if share_defaults_with_users is enabled)
@@ -2448,10 +2450,35 @@ def setup_model_routes(model_discovery):
model = settings.get("default_model", "")
if not _fallbacks:
_fallbacks = settings.get("default_model_fallbacks") or []
+ if not default_reasoning_effort:
+ default_reasoning_effort = (settings.get("default_reasoning_effort") or "").strip()
+ if not default_verbosity:
+ default_verbosity = (settings.get("default_verbosity") or "").strip()
else:
ep_id = settings.get("default_endpoint_id", "")
model = settings.get("default_model", "")
_fallbacks = settings.get("default_model_fallbacks") or []
+ default_reasoning_effort = (settings.get("default_reasoning_effort") or "").strip()
+ default_verbosity = (settings.get("default_verbosity") or "").strip()
+
+ def _clean_default_reasoning(value: str) -> str:
+ cleaned = (value or "").strip().lower().replace("-", "_")
+ if cleaned in {"", "auto", "default"}:
+ return ""
+ if cleaned == "none":
+ return "off"
+ return cleaned if cleaned in {"off", "on", "minimal", "low", "medium", "high", "xhigh"} else ""
+
+ def _clean_default_verbosity(value: str) -> str:
+ cleaned = (value or "").strip().lower()
+ if cleaned in {"", "auto", "default"}:
+ return ""
+ return cleaned if cleaned in {"low", "medium", "high"} else ""
+
+ default_controls = {
+ "default_reasoning_effort": _clean_default_reasoning(default_reasoning_effort),
+ "default_verbosity": _clean_default_verbosity(default_verbosity),
+ }
db = SessionLocal()
try:
ep = None
@@ -2504,7 +2531,7 @@ def setup_model_routes(model_discovery):
_last_q = owner_filter(_last_q, ModelEndpoint, _user, include_shared=False)
ep = _last_q.first()
if not ep:
- return {"endpoint_id": "", "endpoint_url": "", "model": ""}
+ return {"endpoint_id": "", "endpoint_url": "", "model": "", **default_controls}
base = _normalize_base(ep.base_url)
chat_url = build_chat_url(base)
if not model and (getattr(ep, "cached_models", None) or getattr(ep, "pinned_models", None)):
@@ -2514,7 +2541,7 @@ def setup_model_routes(model_discovery):
model = visible[0]
except Exception:
pass
- return {"endpoint_id": ep.id, "endpoint_url": chat_url, "model": model}
+ return {"endpoint_id": ep.id, "endpoint_url": chat_url, "model": model, **default_controls}
finally:
db.close()
diff --git a/src/settings.py b/src/settings.py
index 5836765f1..16839efaf 100644
--- a/src/settings.py
+++ b/src/settings.py
@@ -134,6 +134,8 @@ DEFAULT_SETTINGS = {
"task_model": "",
"default_endpoint_id": "",
"default_model": "",
+ "default_reasoning_effort": "",
+ "default_verbosity": "",
# Optional prose style used only for normal document writing/editing.
# Email replies use email_writing_style instead because greetings,
# signatures, and mailbox identity rules are medium-specific.
@@ -271,6 +273,7 @@ _PER_USER_KEYS = {
# account inherited whatever the most-recent admin picked, which then
# got injected into the chat composer on first open.
"default_endpoint_id", "default_model", "default_model_fallbacks",
+ "default_reasoning_effort", "default_verbosity",
"utility_endpoint_id", "utility_model", "utility_model_fallbacks",
"research_endpoint_id", "research_model",
}
diff --git a/static/app.js b/static/app.js
index bac07392a..14db8905d 100644
--- a/static/app.js
+++ b/static/app.js
@@ -209,6 +209,10 @@ try {
if (cachedDefaultChat && cachedDefaultChat.endpoint_url && cachedDefaultChat.model) {
_defaultChat = cachedDefaultChat;
window.__odysseusDefaultChat = cachedDefaultChat;
+ window.__odysseusModelControlDefaults = {
+ reasoning_effort: cachedDefaultChat.default_reasoning_effort || 'auto',
+ verbosity: cachedDefaultChat.default_verbosity || 'auto',
+ };
}
} catch (_) {}
async function _refreshDefaultChat() {
@@ -218,6 +222,10 @@ async function _refreshDefaultChat() {
_defaultChat = d;
try {
window.__odysseusDefaultChat = d;
+ window.__odysseusModelControlDefaults = {
+ reasoning_effort: d.default_reasoning_effort || 'auto',
+ verbosity: d.default_verbosity || 'auto',
+ };
localStorage.setItem('odysseus-default-chat-cache', JSON.stringify(d));
} catch (_) {}
return d;
@@ -248,7 +256,11 @@ async function _createDirectChatFromPreferredModel() {
const dc = await _refreshDefaultChat();
if (dc) {
- sessionModule.createDirectChat(dc.endpoint_url, dc.model, dc.endpoint_id, { source: 'default' });
+ sessionModule.createDirectChat(dc.endpoint_url, dc.model, dc.endpoint_id, {
+ source: 'default',
+ reasoning_effort: dc.default_reasoning_effort || '',
+ verbosity: dc.default_verbosity || '',
+ });
return true;
}
@@ -1991,6 +2003,29 @@ function initializeEventListeners() {
const v = String(value || 'auto').toLowerCase();
return Object.prototype.hasOwnProperty.call(labels, v) ? v : 'auto';
};
+ const normalizeDefaultControl = (key, value) => {
+ let normalized = String(value || 'auto').toLowerCase().replace(/-/g, '_');
+ if (normalized === 'none') normalized = 'off';
+ const config = controls.find(c => c.key === key);
+ return config ? normalizeValue(normalized, config.labels) : 'auto';
+ };
+ let modelControlDefaults = {
+ reasoning_effort: 'auto',
+ verbosity: 'auto',
+ };
+ function setModelControlDefaults(values = {}) {
+ modelControlDefaults = {
+ reasoning_effort: normalizeDefaultControl(
+ 'reasoning_effort',
+ values.reasoning_effort || values.default_reasoning_effort || 'auto',
+ ),
+ verbosity: normalizeDefaultControl(
+ 'verbosity',
+ values.verbosity || values.default_verbosity || 'auto',
+ ),
+ };
+ try { window.__odysseusModelControlDefaults = { ...modelControlDefaults }; } catch (_) {}
+ }
const isThinkingModel = model => {
const m = String(model || '').toLowerCase();
@@ -2216,6 +2251,7 @@ function initializeEventListeners() {
});
refreshCapabilities();
+ setModelControlDefaults(window.__odysseusModelControlDefaults || window.__odysseusDefaultChat || {});
window.odysseusModelControls = {
applySession(meta = {}) {
@@ -2227,9 +2263,22 @@ function initializeEventListeners() {
}
refreshCapabilities();
},
+ getDefaults() {
+ return { ...modelControlDefaults };
+ },
+ setDefaults(values = {}) {
+ setModelControlDefaults(values);
+ },
refreshCapabilities,
};
+ fetch('/api/auth/settings', { credentials: 'same-origin' })
+ .then(res => res.ok ? res.json() : null)
+ .then(settings => {
+ if (settings) setModelControlDefaults(settings);
+ })
+ .catch(() => {});
+
document.addEventListener('odysseus:model-picked', e => {
const detail = (e && e.detail) || {};
refreshCapabilities({ model: detail.mid, endpointUrl: detail.url });
diff --git a/static/index.html b/static/index.html
index 441512177..7db8b179a 100644
--- a/static/index.html
+++ b/static/index.html
@@ -1523,6 +1523,34 @@
+
+
Model Response Defaults
+
Default controls for newly created chat sessions. Existing chats keep their saved settings.
+
+
+
+
+
+
+
+
+
+
+
+
Utility Model (Recommended: Local Endpoint)
Runs background tasks (compaction, cleanup, auto-naming, retrieving memories from files) on a small/local model instead of your chat model. Leave blank to use the chat model.