@@ -313,6 +313,26 @@ func (c *coordinator) run(ctx context.Context, accept *AcceptedRun, sessionID st
313313 return result, originalErr
314314}
315315
316+// effectiveReasoningEffort returns the reasoning effort to apply for provider calls.
317+// It prefers the user-selected effort when valid, otherwise the model default when
318+// valid, and finally falls back to the first configured reasoning level.
319+func effectiveReasoningEffort(model Model) string {
320+ if !model.CatwalkCfg.CanReason {
321+ return ""
322+ }
323+
324+ if effort := model.ModelCfg.ReasoningEffort; effort != "" && slices.Contains(model.CatwalkCfg.ReasoningLevels, effort) {
325+ return effort
326+ }
327+ if effort := model.CatwalkCfg.DefaultReasoningEffort; effort != "" && slices.Contains(model.CatwalkCfg.ReasoningLevels, effort) {
328+ return effort
329+ }
330+ if len(model.CatwalkCfg.ReasoningLevels) > 0 {
331+ return model.CatwalkCfg.ReasoningLevels[0]
332+ }
333+ return ""
334+}
335+
316336func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.ProviderOptions {
317337 options := fantasy.ProviderOptions{}
318338
@@ -361,14 +381,16 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
361381 return options
362382 }
363383
384+ reasoningEffort := effectiveReasoningEffort(model)
364385 shouldSetEffort := model.CatwalkCfg.CanReason &&
365− slices.Contains(model.CatwalkCfg.ReasoningLevels, model.ModelCfg.ReasoningEffort)
386+ reasoningEffort != "" &&
387+ slices.Contains(model.CatwalkCfg.ReasoningLevels, reasoningEffort)
366388
367389 switch providerCfg.Type {
368390 case openai.Name, azure.Name:
369391 _, hasReasoningEffort := mergedOptions["reasoning_effort"]
370392 if !hasReasoningEffort && shouldSetEffort {
371− mergedOptions["reasoning_effort"] = model.ModelCfg.ReasoningEffort
393+ mergedOptions["reasoning_effort"] = reasoningEffort
372394 }
373395 if openai.IsResponsesModel(model.CatwalkCfg.ID) {
374396 if openai.IsResponsesReasoningModel(model.CatwalkCfg.ID) {
@@ -396,7 +418,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
396418 case string(catwalk.InferenceProviderAlibabaSingapore):
397419 switch {
398420 case !hasEffort && shouldSetEffort:
399− extraBody["reasoning_effort"] = model.ModelCfg.ReasoningEffort
421+ extraBody["reasoning_effort"] = reasoningEffort
400422 case !hasThink && model.CatwalkCfg.CanReason:
401423 if model.ModelCfg.Think {
402424 extraBody["thinking"] = map[string]any{"type": "enabled"}
@@ -409,7 +431,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
409431 default:
410432 switch {
411433 case !hasEffort && shouldSetEffort:
412− mergedOptions["effort"] = model.ModelCfg.ReasoningEffort
434+ mergedOptions["effort"] = reasoningEffort
413435 case !hasThink && model.ModelCfg.Think:
414436 mergedOptions["thinking"] = map[string]any{"budget_tokens": 2000}
415437 }
@@ -425,7 +447,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
425447 if !hasReasoning && shouldSetEffort {
426448 mergedOptions["reasoning"] = map[string]any{
427449 "enabled": true,
428− "effort": model.ModelCfg.ReasoningEffort,
450+ "effort": reasoningEffort,
429451 }
430452 }
431453 parsed, err := openrouter.ParseOptions(mergedOptions)
@@ -437,7 +459,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
437459 if !hasReasoning && shouldSetEffort {
438460 mergedOptions["reasoning"] = map[string]any{
439461 "enabled": true,
440− "effort": model.ModelCfg.ReasoningEffort,
462+ "effort": reasoningEffort,
441463 }
442464 }
443465 parsed, err := vercel.ParseOptions(mergedOptions)
@@ -454,7 +476,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
454476 }
455477 } else {
456478 mergedOptions["thinking_config"] = map[string]any{
457− "thinking_level": model.ModelCfg.ReasoningEffort,
479+ "thinking_level": reasoningEffort,
458480 "include_thoughts": true,
459481 }
460482 }
@@ -470,9 +492,9 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
470492 if !hasReasoningEffort && shouldSetEffort {
471493 switch providerCfg.ID {
472494 case string(catwalk.InferenceProviderIoNet):
473− extraBody["reasoning"] = map[string]string{"effort": model.ModelCfg.ReasoningEffort}
495+ extraBody["reasoning"] = map[string]string{"effort": reasoningEffort}
474496 default:
475− mergedOptions["reasoning_effort"] = model.ModelCfg.ReasoningEffort
497+ mergedOptions["reasoning_effort"] = reasoningEffort
476498 }
477499 }
478500
@@ -492,7 +514,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
492514 }
493515 }
494516 case string(catwalk.InferenceProviderZAI), string(catwalk.InferenceProviderDeepSeek):
495− if model.ModelCfg.Think || model.ModelCfg.ReasoningEffort != "" {
517+ if model.ModelCfg.Think || reasoningEffort != "" {
496518 extraBody["thinking"] = map[string]any{
497519 "type": "enabled",
498520 }
@@ -503,7 +525,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
503525 }
504526 case string(catwalk.InferenceProviderFireworks):
505527 // NOTE: Fireworks break if we set both `reasoning_effort` and `thinking`.
506− if model.ModelCfg.ReasoningEffort == "" {
528+ if reasoningEffort == "" {
507529 if model.ModelCfg.Think {
508530 extraBody["thinking"] = map[string]any{"type": "enabled"}
509531 } else {