Problem
Inner Claude (the claude subprocess Kai launches per chat) currently has no operator-controlled way to set the --effort CLI flag. Verified via claude --help: the flag accepts low | medium | high | xhigh | max and controls how many reasoning tokens Claude spends per turn.
Right now every inner-Claude invocation runs at whatever the binary picks as its default effort, with no way to dial it up for harder work or down to save budget. As cost vs. quality tradeoffs evolve we have no knob to turn.
Outer Claude exposes the same control via the operator's global Claude Code settings.json. There is no equivalent for inner Claude.
Proposed solution
Add EFFORT_LEVEL as a global config var following the existing "Adding a New Env Var" pattern documented in .claude/CLAUDE.md:
- New field
effort_level: str = "high" on the main Config dataclass in src/kai/config.py, placed near memory_extraction_model since it is similar in shape (single global string with allow-list validation, not per-user).
- Validated parsing in
load_config() against the allow-list {low, medium, high, xhigh, max}. Invalid values raise SystemExit with a clear message naming the env var, mirroring the DEFAULT_MODEL validation path.
- Threaded through
src/kai/pool.py to src/kai/claude.py.__init__ (both ClaudeProcess instantiation sites in pool.py must be updated; the constructor gains an effort_level kwarg).
- Appended to the inner-claude
claude_cmd list as --effort <value>. Important: this must go on the CLI, NOT into the --settings JSON blob. settings.json silently drops unknown keys, so a typo in a settings-key path produces a no-op; an invalid CLI flag fails loudly at subprocess startup.
- Wizard prompt added to
_cmd_config() in src/kai/install.py using the existing _prompt_choice helper, exposing the five valid values. Written to env only if non-default, matching the prevailing "delta from default" convention so install.conf stays minimal.
- Documented in
.env.example near DEFAULT_MODEL (both shape inner Claude behavior).
- Added to
_CONFIG_ENV_VARS in tests/test_config.py plus a new TestEffortLevel class covering: default applied when unset, valid value parses, invalid value raises SystemExit, uppercase normalized via .lower(), surrounding whitespace stripped.
- New test in
tests/test_claude.py (or wherever claude_cmd is currently tested) asserting --effort <value> appears as adjacent elements in the constructed command list.
Default value
Proposed: high. Rationale: the operator's existing outer Claude default is high, and matching it keeps inner Claude behavior continuous on first deploy. Choosing medium as the default would silently downgrade existing reasoning quality, which is a behavior change disguised as a config addition.
Out of scope
- Per-user
effort_level via users.yaml. The plumbing for per-user values already exists for model and budget; promoting effort_level to per-user later is a parallel change and should be its own issue.
- Per-workspace overrides. Same reasoning. Add when there is a real use case.
- Any change to the
--settings JSON path Kai already passes to inner Claude. Out of scope here even though that path has a related, separate bug worth a follow-up issue.
Acceptance criteria
Problem
Inner Claude (the
claudesubprocess Kai launches per chat) currently has no operator-controlled way to set the--effortCLI flag. Verified viaclaude --help: the flag acceptslow | medium | high | xhigh | maxand controls how many reasoning tokens Claude spends per turn.Right now every inner-Claude invocation runs at whatever the binary picks as its default effort, with no way to dial it up for harder work or down to save budget. As cost vs. quality tradeoffs evolve we have no knob to turn.
Outer Claude exposes the same control via the operator's global Claude Code settings.json. There is no equivalent for inner Claude.
Proposed solution
Add
EFFORT_LEVELas a global config var following the existing "Adding a New Env Var" pattern documented in.claude/CLAUDE.md:effort_level: str = "high"on the mainConfigdataclass insrc/kai/config.py, placed nearmemory_extraction_modelsince it is similar in shape (single global string with allow-list validation, not per-user).load_config()against the allow-list{low, medium, high, xhigh, max}. Invalid values raiseSystemExitwith a clear message naming the env var, mirroring theDEFAULT_MODELvalidation path.src/kai/pool.pytosrc/kai/claude.py.__init__(bothClaudeProcessinstantiation sites inpool.pymust be updated; the constructor gains aneffort_levelkwarg).claude_cmdlist as--effort <value>. Important: this must go on the CLI, NOT into the--settingsJSON blob. settings.json silently drops unknown keys, so a typo in a settings-key path produces a no-op; an invalid CLI flag fails loudly at subprocess startup._cmd_config()insrc/kai/install.pyusing the existing_prompt_choicehelper, exposing the five valid values. Written to env only if non-default, matching the prevailing "delta from default" convention soinstall.confstays minimal..env.examplenearDEFAULT_MODEL(both shape inner Claude behavior)._CONFIG_ENV_VARSintests/test_config.pyplus a newTestEffortLevelclass covering: default applied when unset, valid value parses, invalid value raisesSystemExit, uppercase normalized via.lower(), surrounding whitespace stripped.tests/test_claude.py(or whereverclaude_cmdis currently tested) asserting--effort <value>appears as adjacent elements in the constructed command list.Default value
Proposed:
high. Rationale: the operator's existing outer Claude default ishigh, and matching it keeps inner Claude behavior continuous on first deploy. Choosingmediumas the default would silently downgrade existing reasoning quality, which is a behavior change disguised as a config addition.Out of scope
effort_levelviausers.yaml. The plumbing for per-user values already exists formodelandbudget; promotingeffort_levelto per-user later is a parallel change and should be its own issue.--settingsJSON path Kai already passes to inner Claude. Out of scope here even though that path has a related, separate bug worth a follow-up issue.Acceptance criteria
EFFORT_LEVELenv var is accepted, validated, and reflected inConfig.effort_level--effort <value>in its command line for every invocationSystemExitat config-load time with a message naming the env varmake configprompts for the value with the five valid choices and persists it toinstall.confonly when non-default.env.exampledocuments the variable with allowed values and interaction notestests/test_config.py(parsing, validation, normalization) andtests/test_claude.py(flag appears in command)