Skip to content

Add EFFORT_LEVEL config var to control inner Claude --effort flag #378

@dcellison

Description

@dcellison

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:

  1. 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).
  2. 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.
  3. 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).
  4. 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.
  5. 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.
  6. Documented in .env.example near DEFAULT_MODEL (both shape inner Claude behavior).
  7. 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.
  8. 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

  • EFFORT_LEVEL env var is accepted, validated, and reflected in Config.effort_level
  • Inner Claude subprocess receives --effort <value> in its command line for every invocation
  • Invalid values raise SystemExit at config-load time with a message naming the env var
  • make config prompts for the value with the five valid choices and persists it to install.conf only when non-default
  • .env.example documents the variable with allowed values and interaction notes
  • Test coverage in tests/test_config.py (parsing, validation, normalization) and tests/test_claude.py (flag appears in command)
  • No silent fallback when the env var is unparseable; the operator must see the failure

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions