Motivation
kuku's thinking-level system has four problems today:
- Hardcoded mapping does not cover provider differences.
ThinkLevel (off/low/medium/high) is mapped per provider with three hardcoded match tables (openai_compat.rs: low→low / medium→medium / high→xhigh; openai_responses.rs: same; anthropic.rs: adaptive thinking + output_config.effort). But openai_compat is shared by DeepSeek, Qwen, Kimi, xAI, Gemini, etc., and each vendor accepts a different thinking parameter shape (reasoning_effort / enable_thinking / thinking:{type} / thinkingLevel). A single mapping does not work.
- Three levels are not enough. The industry now commonly supports 6–9 levels (minimal→max), and supported subsets vary per model (e.g. Gemini 3 Pro only low/high, GLM only high/max). Hardcoding cannot express this.
- Absolute level semantics add cognitive load. Users face absolute names like
low/medium/high without knowing what they mean relative to the model's default. And by default kuku force-sends an effort (e.g. high→xhigh), overriding the model's native behavior on the user's behalf.
- No max_tokens / thinking-budget coupling. Budget-style dialects (legacy Anthropic thinking) require
max_tokens >= budget + buffer, which is currently missing. (Context/max_tokens strategy is tracked separately; see the follow-up note in Proposal.)
Five reference projects were studied (codex, hermes-agent, oh-my-pi, opencode, pi — see .superpowers/reference-src/). The consensus: unified user levels + per-model/per-provider mapping + metadata-driven adaptation. None of them hardcodes three levels at the provider layer.
Goals:
- Zero-config default: follow the model's native behavior
- Low-cognitive-load config: relative 4-level user model
- Advanced users can override precisely (per-model overrides + escape hatch)
- All mapping is data-driven
Proposal
1. User-facing: relative 4-level mental model
| User config |
Mental meaning |
Internal action |
| (unset) |
Don't care |
Do not send effort; follow the model's native default |
off |
Turn thinking off |
Send disabled (clamped to lowest level for models that require thinking) |
low |
Think less |
Clamp to the lowest supported level of the model |
medium |
Standard |
Follow the model default (do not send) |
high |
Think more |
Clamp to the highest supported level of the model |
The ThinkLevel enum stays as-is (off/low/medium/high); only the semantics change from "absolute level" to "relative to default". Existing configs remain fully compatible.
2. Internal: 6-level effort enum + per-model thinking metadata
The wire layer uses the industry-standard six levels minimal/low/medium/high/xhigh/max (identical to the models.dev effort enum — zero conversion). Each model/provider resolves to three metadata fields:
struct ModelThinking {
mode: ThinkingMode, // dialect: effort | responses | adaptive | google-level
supported: Vec<Effort>, // supported levels (default: all; builtin table for mainstream models)
default_effort: Option<Effort>, // model's native default (None = do not send)
}
3. Mapping pipeline
user config (relative 4-level) + ModelThinking → intent resolution → clamping → dialect mapping → request body
- Intent resolution: unset/
medium → do not send; low/high → endpoint semantics (lowest/highest supported level); absolute levels (minimal/xhigh/max, reachable via config) → clamp down to the nearest supported level
- Dialect mapping:
effort→reasoning_effort; responses→reasoning:{effort}; adaptive→thinking:{type:"adaptive"}+output_config:{effort}; google-level→thinkingLevel:"LOW" (xhigh/max fold to HIGH)
- Clamping and dialects are decoupled: adding a dialect never touches clamping logic
4. Dialects: 4 built-in, the rest user-defined
Built-in: effort (OpenAI-compatible family) / responses (OpenAI Responses) / adaptive (Claude 4.6+, Kimi) / google-level (Gemini 3). Legacy Anthropic budget_tokens mode and niche dialects (qwen-toggle, etc.) are not built in — configured on demand (mode = "budget" + single value budget = 8000, auto-scaled low=budget/2, medium=budget, high=budget×2).
5. Config: optional thinking sub-table under the existing [model.<tier>]
Follows kuku's tier convention; all fields optional; zero config by default:
# ① Mainstream: no new config
[model.balanced]
provider = "anthropic"
model = "claude-sonnet-4-6"
think = "high"
# ② Custom model: 1-2 lines
[model.local]
provider = "local"
model = "qwen3-32b"
thinking.mode = "qwen-toggle"
# ③ Legacy Claude budget: single value
[model.legacy]
provider = "anthropic"
model = "claude-3-5-sonnet"
thinking.mode = "budget"
thinking.budget = 8000
Optional fields: mode (dialect), supported (levels), budget (single value), map (level→wire-value escape hatch, mirroring pi's thinkingLevelMap). All settable via kuku config set dot-notation.
6. Metadata sources
Dialect is inferred from provider format + model-name prefix (unknown models default to the "latest contract", e.g. unknown Claude → adaptive — the approach hermes-agent validates). A builtin table for mainstream models' supported levels (aligned with models.dev); unknown models default to all levels. Remote catalog refresh is deferred (possible follow-up).
7. Follow-up
max_tokens / thinking-budget coupling (max_tokens >= budget + buffer) and context-window management strategy will be tracked as a separate issue.
Alternatives considered
- 9 levels (codex/hermes pattern):
ultra is only supported by new OpenAI models; kuku is multi-provider and most vendors cannot map it. More levels = more cognitive load. Rejected.
- Free per-model levels (opencode pattern): inconsistent user surface, dozens-of-lines model config JSON — conflicts with the low-cognitive-load goal. Rejected.
- Large provider-level mapping tables (hermes pattern): most detailed but highest maintenance cost (per-endpoint special cases); less elegant than "dialect inference rules + sparse overrides". Rejected.
- Builtin legacy Claude budget dialect: explicitly not needed; demoted to user config to keep the builtin surface lean.
auto level (oh-my-pi's classifier): requires a small-model classifier, high cost; deferred, reserved as a future extension point.
Affected scope
SDK + CLI
Motivation
kuku's thinking-level system has four problems today:
ThinkLevel(off/low/medium/high) is mapped per provider with three hardcoded match tables (openai_compat.rs: low→low / medium→medium / high→xhigh;openai_responses.rs: same;anthropic.rs: adaptive thinking +output_config.effort). Butopenai_compatis shared by DeepSeek, Qwen, Kimi, xAI, Gemini, etc., and each vendor accepts a different thinking parameter shape (reasoning_effort/enable_thinking/thinking:{type}/thinkingLevel). A single mapping does not work.low/medium/highwithout knowing what they mean relative to the model's default. And by default kuku force-sends an effort (e.g. high→xhigh), overriding the model's native behavior on the user's behalf.max_tokens >= budget + buffer, which is currently missing. (Context/max_tokens strategy is tracked separately; see the follow-up note in Proposal.)Five reference projects were studied (codex, hermes-agent, oh-my-pi, opencode, pi — see
.superpowers/reference-src/). The consensus: unified user levels + per-model/per-provider mapping + metadata-driven adaptation. None of them hardcodes three levels at the provider layer.Goals:
Proposal
1. User-facing: relative 4-level mental model
offlowmediumhighThe
ThinkLevelenum stays as-is (off/low/medium/high); only the semantics change from "absolute level" to "relative to default". Existing configs remain fully compatible.2. Internal: 6-level effort enum + per-model thinking metadata
The wire layer uses the industry-standard six levels
minimal/low/medium/high/xhigh/max(identical to the models.dev effort enum — zero conversion). Each model/provider resolves to three metadata fields:3. Mapping pipeline
medium→ do not send;low/high→ endpoint semantics (lowest/highest supported level); absolute levels (minimal/xhigh/max, reachable via config) → clamp down to the nearest supported leveleffort→reasoning_effort;responses→reasoning:{effort};adaptive→thinking:{type:"adaptive"}+output_config:{effort};google-level→thinkingLevel:"LOW"(xhigh/max fold to HIGH)4. Dialects: 4 built-in, the rest user-defined
Built-in:
effort(OpenAI-compatible family) /responses(OpenAI Responses) /adaptive(Claude 4.6+, Kimi) /google-level(Gemini 3). Legacy Anthropicbudget_tokensmode and niche dialects (qwen-toggle, etc.) are not built in — configured on demand (mode = "budget"+ single valuebudget = 8000, auto-scaled low=budget/2, medium=budget, high=budget×2).5. Config: optional
thinkingsub-table under the existing[model.<tier>]Follows kuku's tier convention; all fields optional; zero config by default:
Optional fields:
mode(dialect),supported(levels),budget(single value),map(level→wire-value escape hatch, mirroring pi'sthinkingLevelMap). All settable viakuku config setdot-notation.6. Metadata sources
Dialect is inferred from provider format + model-name prefix (unknown models default to the "latest contract", e.g. unknown Claude → adaptive — the approach hermes-agent validates). A builtin table for mainstream models' supported levels (aligned with models.dev); unknown models default to all levels. Remote catalog refresh is deferred (possible follow-up).
7. Follow-up
max_tokens / thinking-budget coupling (
max_tokens >= budget + buffer) and context-window management strategy will be tracked as a separate issue.Alternatives considered
ultrais only supported by new OpenAI models; kuku is multi-provider and most vendors cannot map it. More levels = more cognitive load. Rejected.autolevel (oh-my-pi's classifier): requires a small-model classifier, high cost; deferred, reserved as a future extension point.Affected scope
SDK + CLI